# 定时任务

定时任务用于定义系统中需要定时执行的相关任务或者逻辑,如发送合同过期通知,定期归档数据等。

# 目标读者

本文档的目标读者为:本系统的开发和实施人员

# 任务类型

系统支持以下两种类型的定时任务:

类型 下拉框显示 说明
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
1

不要修改任务名称

调度器按任务名称识别任务。修改名称后,平台按新名称重新登记调度,但旧名称的调度不会被移除;旧调度到点触发时找不到同名任务,只在后台日志中输出一条警告,不会执行。需要改名时,建议删除任务后按新名称重新创建。

# 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} 个"]
1
2
3
4
5

# 返回结果

返回结果是一个 Map 结构, 如下是定时任务执行后的返回结果的结构:

return [
  //执行结果,类型为文本
  //Execution result, type is text
  execResult: 'OK, Result' 
]
1
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(上图就是一次手动运行的记录)。

Last Updated: 2026/9/24 14:27:35