Add XCode skills for entity caching, ORM, and sharding ETL
|
---
name: cube-jobs
description: >
使用 NewLife.Cube 内置定时作业体系(ICubeJob/CubeJobBase/CronJobAttribute)
å¼€å‘和管ç†å‘¨æœŸæ€§åŽå°ä»»åŠ¡ï¼Œæ¶µç›–æŽ¥å£å®šä¹‰ã€å¼ºç±»åž‹å‚æ•°ã€Cron 表达å¼ã€
ä¾èµ–æ³¨å…¥æž„é€ ã€åˆ†å¸ƒå¼é”防é‡ã€Job.Data çŠ¶æ€æŒä¹…化ã€å†…置作业(HttpService/SqlService/BackupDbService),
ä»¥åŠ AddCubeJob() 注册与管ç†åŽå°çƒä¿®æ”¹ã€‚
适用于定时数æ®ç»Ÿè®¡ã€å®šæ—¶åŒæ¥ã€HTTP 回调ã€SQL 批处ç†ã€æ•°æ®åº“备份ç‰åŽå°ä»»åŠ¡åœºæ™¯ã€‚
argument-hint: >
说明任务类型和触å‘频率:是纯计算任务ã€HTTP 请求ã€SQL æ“作还是 XCode æ•°æ®åº“读写;
是å¦éœ€è¦æ³¨å…¥å…¶ä»–æœåŠ¡ï¼›æ˜¯å¦éœ€è¦è·¨æ‰§è¡ŒæŒä¹…化状æ€ï¼ˆæ—¶é—´æŒ‡é’ˆç‰ï¼‰ã€‚
---
# Cube 定时作业
## 适用场景
- 定期执行数æ®ç»Ÿè®¡ã€æ—¥å¿—清ç†ã€æ•°æ®åŒæ¥ã€æ¶ˆæ¯æŽ¨é€ç‰åŽå°ä»»åŠ¡ã€‚
- 利用内置 HTTP 作业定时调用外部接å£ï¼ˆå¦‚ Webhookã€å¥åº·æ£€æŸ¥ï¼‰ã€‚
- 利用内置 SQL 作业定时执行数æ®åº“维护è¯å¥ã€‚
- 在管ç†åŽå°ç•Œé¢çƒä¿®æ”¹ Cron 表达å¼ã€å‚数或å¯åœçжæ€ï¼Œæ— 需é‡å¯æœåŠ¡ã€‚
- 分布å¼éƒ¨ç½²æ—¶é€šè¿‡é”æ–¹å†…ç½®é”æœºåˆ¶é˜²æ¢å¤šèŠ‚ç‚¹é‡å¤æ‰§è¡Œã€‚
---
## æ ¸å¿ƒæŽ¥å£
### ICubeJob — 作业接å£
```csharp
public interface ICubeJob
{
/// <summary>执行定时作业</summary>
/// <param name="argument">JSON æ ¼å¼å‚æ•°å—符串</param>
/// <returns>执行结果日志消æ¯</returns>
Task<String> Execute(String argument);
}
```
### CubeJobBase / CubeJobBase<TArgument>
```csharp
// 基础基类(手动解æžå‚数)
public abstract class CubeJobBase : ICubeJob
{
public CronJob Job { get; set; } // 当å‰ä½œä¸šå®žä½“ï¼ŒåŒ…å« Data/NextTime ç‰å—段
public abstract Task<String> Execute(String argument);
}
// 泛型基类(自动 JSON ååºåˆ—åŒ–å‚æ•°ï¼ŒæŽ¨è使用)
public abstract class CubeJobBase<TArgument> : CubeJobBase where TArgument : class, new()
{
public override async Task<String> Execute(String argument)
{
var arg = argument.IsNullOrEmpty() ? new TArgument()
: argument.ToJsonEntity<TArgument>();
return await OnExecute(arg);
}
protected abstract Task<String> OnExecute(TArgument argument);
}
```
### CronJobAttribute — å£°æ˜Žå¼æ³¨å†Œ
```csharp
[AttributeUsage(AttributeTargets.Class)]
public class CronJobAttribute(String name, String cron) : Attribute
{
public String Name { get; set; } = name; // æ•°æ®åº“唯一å,修改åŽè§†ä¸ºæ–°ä½œä¸š
public String Cron { get; set; } = cron; // åˆå§‹ Cron,åŽç»ä»¥æ•°æ®åº“为准
public Boolean Enable { get; set; } // 首次创建时是å¦è‡ªåЍå¯ç”¨
}
```
---
## å¿«é€Ÿè‡ªå®šä¹‰ä½œä¸šï¼ˆæŽ¨èæ¨¡å¼ï¼‰
```csharp
using NewLife.Cube.Jobs;
/// <summary>用户活跃度统计</summary>
[DisplayName("用户活跃度统计")]
[Description("æ¯å°æ—¶ç»Ÿè®¡è¿‘30分钟的活跃用户数")]
[CronJob("StatActiveUsers", "0 0 * * * ? *", Enable = true)] // æ¯å°æ—¶æ•´ç‚¹
public class StatActiveUsersJob : CubeJobBase<StatArgument>
{
private readonly ITracer _tracer;
// æ”¯æŒæž„é€ å‡½æ•°ä¾èµ–注入
public StatActiveUsersJob(ITracer tracer)
{
_tracer = tracer;
}
protected override async Task<String> OnExecute(StatArgument arg)
{
using var span = _tracer?.NewSpan(nameof(StatActiveUsersJob), arg);
// 从 Job.Data 读å–上次时间指针(跨执行æŒä¹…化)
var lastTime = Job.Data.IsNullOrEmpty()
? DateTime.Now.AddMinutes(-arg.MinutesBack ?? -30)
: Job.Data.ToDateTime();
var count = User.FindCount(User._.LastLoginTime > lastTime);
// 更新时间指针供下次执行使用
Job.Data = DateTime.Now.ToString("O");
Job.Update();
return $"统计完æˆï¼Œæ´»è·ƒç”¨æˆ·æ•°ï¼š{count},时间范围:{lastTime:t}~{DateTime.Now:t}";
}
}
public class StatArgument
{
/// <summary>统计时间窗å£ï¼ˆåˆ†é’Ÿï¼‰ï¼Œé»˜è®¤ 30</summary>
public Int32? MinutesBack { get; set; } = 30;
}
```
---
## 注册作业
### AddCubeJob() — æœåŠ¡æ³¨å†Œ
```csharp
// Program.cs
builder.Services.AddCube();
builder.Services.AddCubeJob(); // 注册作业调度åŽå°æœåŠ¡ + 自动扫æ ICubeJob 实现类
```
`AddCubeJob()` 完æˆï¼š
1. 注册 `JobService` 为 `IHostedService`(在åŽå°è°ƒåº¦ä½œä¸šï¼‰ã€‚
2. å¯åŠ¨æ—¶è°ƒç”¨ `BackupDbService.Init()`(注册内置备份作业)。
3. 调用 `JobService.ScanJobs()` åå°„æ‰«ææ‰€æœ‰ `[CronJob]` 特性类,自动创建/æ›´æ–°æ•°æ®åº“记录。
### æ‰«ææœºåˆ¶
- 扫æèŒƒå›´ï¼šå½“å‰ AppDomain 䏿‰€æœ‰å®žçް `ICubeJob` 的类型。
- 若数æ®åº“ä¸å·²å˜åœ¨åŒå(`Name`)作业,**ä»…æ›´æ–° DisplayName/Method/Remark**,ä¸é‡ç½® Cron/傿•°/å¯ç”¨çжæ€ã€‚
- è‹¥ä¸å˜åœ¨ï¼Œåˆ™æ–°å¢žè®°å½•,使用 `CronJobAttribute` ä¸çš„åˆå§‹å€¼ã€‚
### ä¼ ç»Ÿé™æ€æ–¹æ³•注册(兼容)
```csharp
// 在 Program.cs 或 BackupDbService.Init() ä¸
CronJob.Add(null, MyClass.MyStaticMethod, "5 0 0 * * ? *", enable: false);
// 傿•°1:DisplayName(null å–æ–¹æ³• DisplayName 特性)
// 傿•°2ï¼šé™æ€æ–¹æ³•委托
// 傿•°3:Cron 表达å¼
// 傿•°4:是å¦å¯ç”¨
```
---
## 内置作业
### HttpService — HTTP 请求作业
```csharp
// 注册å:"RunHttp",默认 Cron:"25 0 0 * * ? *"(æ¯å¤© 00:00:25),默认ç¦ç”¨
// 傿•°ï¼ˆåœ¨ç®¡ç†åŽå°çš„"傿•°"å—æ®µå¡« JSON):
{
"Method": "GET", // GET 或 POST
"Url": "https://api.example.com/trigger",
"Body": "{\"key\":\"value\"}" // POST 时的请求体
}
```
### SqlService — SQL 执行作业
```csharp
// 注册å:"RunSql",默认 Cron:"15 * * * * ? *"(æ¯åˆ†é’Ÿç¬¬15秒),默认ç¦ç”¨
// 傿•°ï¼š
{
"ConnName": "Master", // DAL 连接å
"Sql": "DELETE FROM AccessLog WHERE CreateTime < DATEADD(DAY,-30,GETDATE())"
}
```
### BackupDbService — SQLite 备份作业
```csharp
// 注册å:"BackupDb",默认 Cron:"5 0 0 * * ? *"(æ¯å¤© 00:00:05),默认ç¦ç”¨
// 傿•°ï¼ˆå—符串,逗å·åˆ†éš”连接å):
"Cube,Log" // 备份 Cube 库和 Log åº“ï¼ˆä»…æ”¯æŒ SQLite)
```
---
## CronJob å®žä½“å—æ®µ
```csharp
public class CronJob
{
public Int32 Id { get; set; }
public String Name { get; set; } // 唯一å(代ç 级固定)
public String DisplayName { get; set; } // 显示å(管ç†åŽå°å¯ç¼–辑)
public String Cron { get; set; } // Cron 表达å¼ï¼ˆç®¡ç†åŽå°å¯ä¿®æ”¹ï¼‰
public String Method { get; set; } // ICubeJob å®žçŽ°ç±»å…¨å æˆ– 陿€æ–¹æ³•å…¨å
public String Argument { get; set; } // JSON 傿•°ï¼ˆç®¡ç†åŽå°å¯ä¿®æ”¹ï¼‰
public String Data { get; set; } // 跨执行æŒä¹…化数æ®ï¼ˆç”± Job 自写)
public Boolean Enable { get; set; } // å¯åœï¼ˆç®¡ç†åŽå°å¯åˆ‡æ¢ï¼‰
public Boolean EnableLog { get; set; } // 是å¦è®°å½•æ¯æ¬¡æ‰§è¡Œæ—¥å¿—
public DateTime LastTime { get; set; } // 上次执行时间
public DateTime NextTime { get; set; } // 下次执行时间(由调度æœåŠ¡è®¡ç®—ï¼‰
}
```
---
## Cron 表达å¼é€ŸæŸ¥
锿–¹ä½¿ç”¨ Quartz é£Žæ ¼ 7 段 Cron(秒 分 æ—¶ æ—¥ 月 周 年):
| è¡¨è¾¾å¼ | å«ä¹‰ |
|--------|------|
| `0 0 * * * ? *` | æ¯å°æ—¶æ•´ç‚¹ |
| `0 0 0 * * ? *` | æ¯å¤© 00:00:00 |
| `0 0 2 * * ? *` | æ¯å¤©å‡Œæ™¨ 02:00 |
| `0 0 0 1 * ? *` | æ¯æœˆ1æ—¥ 00:00 |
| `0 0 0 * * SUN ? *` | æ¯å‘¨æ—¥ 00:00 |
| `0/30 * * * * ? *` | æ¯30秒一次 |
| `0 0/5 * * * ? *` | æ¯5分钟一次 |
| `0 30 8-18 * * ? *` | 工作时间æ¯å°æ—¶30分 |
| `5 0 0 * * ? *` | æ¯å¤© 00:00:05(é¿å¼€æ•´ç‚¹ï¼‰ |
| `15 * * * * ? *` | æ¯åˆ†é’Ÿç¬¬15ç§’ |
---
## åˆ†å¸ƒå¼æ‰§è¡Œ
- `JobService` åœ¨æ‰§è¡Œå‰æ£€æŸ¥åˆ†å¸ƒå¼é”(基于数æ®åº“ `CronJob.LastTime` 或 Redis)。
- 若检测到å¦ä¸€èŠ‚ç‚¹æ£åœ¨æ‰§è¡ŒåŒå作业(`CheckRunning` 返回 true),**本次跳过**,写入调试日志。
- æ— éœ€é¢å¤–é…置,内置自动防é‡å¤æ‰§è¡Œã€‚
---
## 管ç†åŽå°æ“作
| 功能 | 路径 |
|------|------|
| 查看所有作业列表 | ç³»ç»Ÿç®¡ç† â†’ 定时作业 |
| 修改 Cron è¡¨è¾¾å¼ | 编辑作业 → Cron å—æ®µ |
| ä¿®æ”¹å‚æ•° | 编辑作业 → 傿•°å—段(JSON) |
| æ‰‹åŠ¨ç«‹å³æ‰§è¡Œ | 列表 → 执行按钮 |
| 查看执行日志 | 列表 → 日志明细 |
---
## 常è§ä¾‹å¤–与注æ„事项
- `CronJobAttribute.Name` 是数æ®åº“唯一键,修改åŽè§†ä¸ºæ–°ä½œä¸šï¼Œæ—§è®°å½•ä¸ä¼šè‡ªåŠ¨åˆ é™¤ï¼ˆéœ€æ‰‹åŠ¨å¤„ç†ï¼‰ã€‚
- `CronJobAttribute.Cron` 仅在**首次创建**记录时有效,之åŽä»¥æ•°æ®åº“ä¸çš„值为准(é¿å…ä»£ç æ›´æ–°è¦†ç›–管ç†å‘˜çš„æ‰‹åЍé…置)。
- `Job.Data` 在 `OnExecute` ä¸å†™å…¥åŽéœ€æ‰‹åŠ¨è°ƒç”¨ `Job.Update()` æ‰èƒ½æŒä¹…化。
- ä¾èµ–注入作用域:`JobService` 从 `IServiceProvider` 使用 `GetService` èŽ·å– Job 实例,默认以**å•例**æ–¹å¼è§£æžï¼ˆéž Scoped),如需 Scoped æœåŠ¡éœ€æ‰‹åŠ¨åˆ›å»º Scope。
- 陿€æ–¹æ³•æ–¹å¼ä¸æ”¯æŒä¾èµ–æ³¨å…¥ï¼ŒæŽ¨èæ–°ä»£ç 全部使用 `CubeJobBase<TArgument>`。
## æŽ¨èæ£€æŸ¥é¡¹
- [ ] 是å¦å·²è°ƒç”¨ `AddCubeJob()`(å¦åˆ™ Job ä¸ä¼šè¢«æ‰«æå’Œè°ƒåº¦ï¼‰
- [ ] `CronJobAttribute.Name` 是å¦å…¨å±€å”¯ä¸€ï¼ˆåŒå作业在åŒä¸€åº”用ä¸åªä¼šä¿ç•™ä¸€æ¡è®°å½•)
- [ ] 需è¦è·¨æ‰§è¡ŒæŒä¹…åŒ–çš„æ•°æ®æ˜¯å¦é€šè¿‡ `Job.Data + Job.Update()` ä¿å˜
- [ ] 首次å¯åŠ¨åŽæ˜¯å¦åœ¨ç®¡ç†åŽå°å¯ç”¨æ‰€éœ€ä½œä¸šï¼ˆå†…置作业默认 `Enable = false`)
- [ ] 需注入æœåŠ¡çš„ Job 是å¦å·²å°†æœåŠ¡æ³¨å†Œåˆ° DI 容器
|