定时任务
2023/5/5大约 6 分钟
定时任务
在实际项目开发中 Web 应用有一类不可缺少的,那就是定时任务。定时任务的场景可以说非常广泛:保证最终一致性的数据比对、定时生成报表/邮件、定时清理数据等。本项目提供方便友好的 Web 界面,实现动态管理任务,支持启动、暂停、重启、删除、添加、修改、立即执行等操作。
架构概览
┌──────────────────────────────────────────────┐
│ Quartz.NET 3.x 调度引擎 │
│ ┌─────────────────────────────────────────┐ │
│ │ Job_Dispatcher(统一入口) │ │
│ │ ┌────────────┬──────────┬───────────┐ │ │
│ │ │ 程序集任务 │ HTTP任务 │ SQL任务 │ │ │
│ │ │ (反射调用) │ (get/post)│ (Ado执行) │ │ │
│ │ └────────────┴──────────┴───────────┘ │ │
│ └─────────────────────────────────────────┘ │
│ ↓ SaaS 多租户支持 ↓ │
│ TenantId: "tenant_a" / "a,b" / "*" (全租户) │
│ 每个租户独立记录日志 + 独立上下文执行 │
└──────────────────────────────────────────────┘
新建任务
前端打开菜单(系统监控 -> 定时任务),点击新增:
| 配置项 | 说明 |
|---|---|
| 任务类型 | 程序集(反射执行类)、API(HTTP 请求)、SQL(直接执行 SQL 语句) |
| 触发器类型 | Cron 表达式 或 简单间隔(Simple,按秒执行) |
| 任务名称 | 自定义,如"定时查询任务状态" |
| 任务分组 | 根据字典 sys_job_group 配置 |
| 程序集名称 | 默认 ZR.Tasks(程序集任务时) |
| 任务类名 | 程序集任务:TaskScheduler.Job_SyncTestAPI 任务:填写请求 URL SQL 任务:填写 SQL 语句 |
| 执行表达式 | Cron 表达式,可在线生成(见下方) |
| 租户ID | 仅主租户可见:单租户ID / tenant_a,tenant_b 多租户 / * 全租户执行 |
关键说明
- 创建后默认关闭,需点击「更多 -> 启动」才会按 Cron 调度
- 未启动的任务也能执行一次:点击「更多 -> 运行一次」可手动触发测试
- 手动执行会记录
IsManual=1,调度触发IsManual=0,方便区分 - SaaS 模式:非主租户只能操作本租户的任务,租户ID 自动锁定
Cron 表达式语法
[秒] [分] [小时] [日] [月] [周] [年]
| 名称 | 必填 | 值范围 | 允许的通配符 |
|---|---|---|---|
| 秒 | 是 | 0-59 | , - * / |
| 分 | 是 | 0-59 | , - * / |
| 时 | 是 | 0-23 | , - * / |
| 日 | 是 | 1-31 | , - * / |
| 月 | 是 | 1-12 | , - * ? / L W |
| 周 | 是 | 1-7 | , - * ? / L # |
| 年 | 是 | 1970-2099 | , - * / |
常用示例:
| 表达式 | 说明 |
|---|---|
0 0 2 * * ? | 每天凌晨 2 点 |
0 0/5 * * * ? | 每 5 分钟 |
0 0 9 ? * MON-FRI | 工作日早 9 点 |
0 30 10 1 * ? | 每月 1 号 10:30 |
通配符说明:
*所有值。分字段设*表示每分钟触发?不指定值。如每月 10 号触发不关心周几:0 0 0 10 * ?-区间。小时设10-12表示 10、11、12 点触发,多个值。周字段设MON,WED,FRI表示周一、三、五触发/递增。秒字段设5/15表示 5、20、35、50 秒触发L最后。日字段表示月末,周字段6L表示本月最后一个周五W最近工作日。15W表示离 15 号最近的工作日#第几个。6#3表示每月第三个周六
任务类型
程序集任务
所有程序集任务存放在 ZR.Tasks.TaskScheduler 目录下。
创建任务
using Infrastructure.Attribute;
using Quartz;
using System.Threading.Tasks;
namespace ZR.Tasks.TaskScheduler
{
/// <summary>
/// 定时任务测试
/// 使用 [AppService] 注册后无需在 TasksExtension 手动注册
/// </summary>
[AppService(ServiceType = typeof(Job_SyncTest), ServiceLifetime = LifeTime.Scoped)]
public class Job_SyncTest : JobBase, IJob
{
public async Task Execute(IJobExecutionContext context)
{
await ExecuteJob(context, async () => await Run());
}
public async Task Run()
{
await Task.Delay(1);
//TODO 业务逻辑
System.Console.WriteLine("job test");
}
}
}
注册方式
- 推荐:使用
[AppService]特性自动注册 — 类上标注后自动注入 DI 容器,无需手动注册 - 手动注册:在
TasksExtension.AddTaskSchedulers()中添加services.AddTransient<Job_SyncTest>()
执行机制
新架构采用 Job_Dispatcher 统一分发:
Quartz触发 → Job_Dispatcher → 解析租户列表 → 按租户依次执行 → 独立日志
- 所有任务统一注册为
Job_Dispatcher,由其根据TaskType路由 - 自动解析
TenantId表达式(*/a,b/ 单ID),切换TenantContext后执行 - 每个租户独立记录
SysTasksLog,含执行人、耗时、机器名、追踪 ID
API 任务(推荐)
任务类型选择「执行 URL」,支持 GET 和 POST。
API 地址: http://localhost:8888/home/xxxx
GET 参数格式: token=abc&name=xxxx
POST 参数格式: { "token": "abc", "name": "xxx" }
SQL 任务
任务类型选择「执行 SQL 语句」,直接填入 SQL:
use zradmin;
select getdate();
通过 SqlSugar.Ado 直接执行,适用于定时数据统计、清理过期数据等场景。
任务管理 API
基础路径:
/system/tasks,权限前缀:monitor:job:*
| 操作 | 方法 | 路由 | 权限 | 说明 |
|---|---|---|---|---|
| 任务列表 | GET | /list | monitor:job:list | 含成功率/失败率统计 |
| 任务详情 | GET | /get?id= | - | 单条查询 |
| 新增任务 | POST | /create | monitor:job:add | |
| 修改任务 | POST | /update | monitor:job:edit | |
| 删除任务 | DELETE | /delete?id= | monitor:job:delete | |
| 启动任务 | GET | /start?id= | monitor:job:start | 开始按 Cron 调度 |
| 停止任务 | GET | /stop?id= | monitor:job:stop | 从调度器中移除 |
| 立即执行 | GET | /run?id= | monitor:job:run | 手动触发一次(未启动也可执行) |
| 导出任务 | GET | /export | monitor:job:export |
任务日志 API
基础路径:
/monitor/jobLog
| 操作 | 方法 | 路由 | 说明 |
|---|---|---|---|
| 日志列表 | GET | /list | 支持时间/状态/JobId 筛选,含执行人 |
| 删除日志 | DELETE | /{jobIds} | 按 ID 删除指定日志 |
| 清空日志 | DELETE | /clean | 清空全部日志 |
| 导出日志 | GET | /export | Excel 导出 |
日志字段
| 字段 | 说明 |
|---|---|
| JobName | 任务名称 |
| Status | 0=成功, 1=失败 |
| Elapsed | 执行耗时(毫秒) |
| Operator | 执行人(手动/系统触发) |
| IsManual | 是否手动触发:0=系统, 1=手动 |
| TriggerSource | 触发来源:cron / manual / api / retry |
| TenantId | 所属租户 |
| ServerName | 执行机器名 |
| TraceId | 链路追踪 ID |
SaaS 多租户任务
租户表达式 是 v3.x 的新特性,支持在任务层面控制多租户执行:
| TenantId 值 | 行为 | 适用场景 |
|---|---|---|
"tenant_a" | 仅对单个租户执行 | 租户专属任务 |
"a,b,c" | 逗号分隔,对多个租户执行 | 指定批次租户 |
"*" | 对全部启用租户循环执行 | 全平台清理/统计任务 |
- 非主租户用户无法修改
TenantId,自动锁定为当前租户 - 每个租户的执行结果独立记录一条
SysTasksLog,含各自的耗时和结果 - 所有租户执行完毕后统一更新
SysTasks的聚合统计
注意事项
- 开发环境默认不启用调度:防止与生产环境重复执行。具体在
Program.cs中通过UseAddTaskSchedulers()控制,仅非开发环境自动启动已启用的任务 - 创建后默认关闭:新建任务需手动点击「启动」,或调用
/start接口才会调度执行 - SaaS 权限隔离:非主租户仅能操作本租户的任务和日志
- 日志自动截断:任务消息超过 2000 字符自动截断
- 失败微信通知:
JobBase中任务执行异常会自动通过企业微信发送通知
