# 定时任务
定时任务用于定义系统中需要定时执行的相关任务或者逻辑,如发送合同过期通知,定期归档数据等。
# 目标读者
本文档的目标读者为:本系统的开发和实施人员
# 任务类型
系统支持以下两种类型的定时任务:
| 类型 | 下拉框显示 | 说明 |
|---|---|---|
CRON_TASK | Cron Task | 按 Cron 表达式周期执行,例如定期数据备份、到期提醒、统计监控 |
SCHEDULE_TASK | Schedule Task(One Time) | 在指定时间(scheduleDate)执行一次 |
1.0 起已移除"启动时执行(Run at Startup)"类型(需要在启动时执行的逻辑请放到插件的 onLoad() 回调中,见 插件开发 · 生命周期回调)。
一次性任务无法在界面上创建
当前版本的新建和编辑表单中没有"计划执行时间(scheduleDate)"字段,在界面上创建 SCHEDULE_TASK 会保存失败,提示 Schedule date is required for SCHEDULE_TASK type。需要一次性任务时,请通过 CSV 种子数据或数据 API 设置 scheduleDate。
# 定义任务
在 开发定制 > 定时任务 点击"创建":

表单中的分组标题(Basic Information、Schedule Information、Logics)和"Start date"字段名目前显示为英文,是当前版本的界面问题。
| 字段 | 说明 |
|---|---|
| 名称(name) | 必填,唯一,同时作为调度器中的任务标识。名称可以修改,但不建议修改,见下方说明 |
| 任务类型(dynamicTaskType) | Cron Task 或 Schedule Task(One Time) |
| 帮助信息(helpText) | 说明 |
| Cron 表达式(cronExpression) | CRON_TASK 必填,格式见下文 |
| Start date(startDate) | 可选,CRON_TASK 从该时间起才开始触发 |
| 任务过期时间(expiryDate) | 可选,CRON_TASK 在该时间后不再触发 |
| 执行参数(parameters) | 可选,JSON 格式的自定义参数 |
| 核心逻辑(coreLogic) | 必填,类型为 DYNAMIC_TASK_CORE_LOGIC 的动态逻辑 |
| 是否生效中(active) | 只在编辑表单中显示。关闭后到点不再执行 |
CSV 种子数据示例表头:
name(*),helpText,coreLogic.name,active,isSystem,startDate,expiryDate,cronExpression,parameters,dynamicTaskType,scheduleDate
不要修改任务名称
调度器按任务名称识别任务。修改名称后,平台按新名称重新登记调度,但旧名称的调度不会被移除;旧调度到点触发时找不到同名任务,只在后台日志中输出一条警告,不会执行。需要改名时,建议删除任务后按新名称重新创建。
# Cron 表达式
定时任务使用 Quartz (opens new window) 的 Cron 表达式,包含"秒"位,共 6 或 7 段:秒 分 时 日 月 周 [年]。"日"和"周"两段中必须有一段写 ?。例如:
| 表达式 | 含义 |
|---|---|
0 0 8 * * ? | 每天 8:00 |
0 */10 * * * ? | 每 10 分钟 |
0 30 2 ? * MON | 每周一 2:30 |
# 调度机制
- 平台使用 Quartz 集群模式调度任务,调度信息保存在数据库中(表前缀
QRTZ_),多个后端实例同时运行时,同一次触发只会在一个实例上执行。 - 新建、修改、删除任务后,平台通过内置的对象客制化(
DynamicTask: after creation、DynamicTask: after updating、DynamicTask: after deletion)自动同步到调度器,无需重启。 - 插件导入期间,到点的
CRON_TASK会跳过本次触发,等待下一次;SCHEDULE_TASK会延后 5 秒再执行。
1.0 起定时任务不再支持启用逻辑(需要按条件跳过时,在核心逻辑中判断后直接返回)。
# 核心逻辑
核心逻辑是定时任务运行时执行的代码。
# 注入变量
| 变量名称 | 变量类型 | 描述 |
|---|---|---|
triggerDatetime | java.time.OffsetDateTime | 本次触发的计划时间 |
task | tech.muyan.task.DynamicTask | 当前任务,自定义参数可通过 task.parameters(JSON 字符串)读取 |
log | Closure | 打印执行日志,内容保存到执行记录 |
application | grails.core.GrailsApplication | 当前的 grails 应用上下文 |
示例:统计被锁定的账号
import tech.muyan.security.User
long lockedCount = User.countByAccountLocked(true)
log("触发时间:${triggerDatetime}")
return [execResult: "账号巡检完成,当前锁定账号 ${lockedCount} 个"]
2
3
4
5
# 返回结果
返回结果是一个 Map 结构, 如下是定时任务执行后的返回结果的结构:
return [
//执行结果,类型为文本
//Execution result, type is text
execResult: 'OK, Result'
] 2
3
4
执行状态的判定:
- 正常返回:
SUCCESS,execResult保存为执行结果。 - 抛出任何异常(包括
CustomLogicWarningException):FAILED,堆栈写入"错误堆栈"。执行结果不是原始的异常消息,而是平台包装后的文字:Failed to run task[<任务 id>/(<任务名称>)]: <异常消息>,下一行为Plesae refer to stackTrace column for detail(原文如此)。定时任务没有"成功但有警告"状态。
# 手动运行任务
在 开发定制 > 定时任务 列表中,展开某个任务所在行末尾的下拉菜单,点击"手动运行任务"。确认框目前显示为英文(标题 Run Task Manually,内容为 "Run this schedule task manually? schedule time parameter will be set to when the task been called"),点击"确定"后任务立即执行一次。该操作要求当前用户具有 DEVELOPER 角色。

手动运行时,triggerDatetime 为点击运行的时间,而不是某次计划的触发时间。
# 执行记录
每次执行都会写入一条 DynamicTaskExecRecord,在 执行记录 > 定时任务 中查看,包括关联任务、状态、触发方式、计划时间、开始和完成时间、执行参数、执行日志、执行结果、错误堆栈等。下图为了显示完整,收起了左侧菜单,并隐藏了执行参数、执行日志、错误堆栈三列:

执行记录的"触发方式"(triggerMethod)有三种取值:AUTO(按计划触发)、MANUAL(手动运行)、RERUN(调度器故障恢复后补跑)。
注意
当前版本手动运行任务产生的执行记录,触发方式也显示为 AUTO(上图就是一次手动运行的记录)。