# 定时任务 底座 v0.18.0 参考 RuoYi 的任务名称、分组、Cron、错过策略、并发策略、启停、立即执行和执行日志设计。后台入口为“定时任务”。调度器调用现有模块动作,复用账号、Key 限制、数据范围、RPC、Docker 和监控体系。 ## 创建和使用 1. 进入“定时任务”,点击“新增任务”。新任务默认暂停,不会因打开页面或部署升级而自动创建业务任务。 2. 设置名称、分组、Cron 和 IANA 时区。默认 UTC;中国本地业务可选择 Asia/Shanghai。使用“预览最近5次执行”确认时区及时间。 3. 选择“底座健康检查”或已注册模块的一个动作,提供 JSON 对象参数。底座检查只验证调度执行链路,不表示所有业务服务健康。模块动作由其后端实现;当前支持 list/create/edit/submit/publish/reject,不执行任意函数、shell 或 URL。 4. 先“执行一次”,查看日志,再启用自动计划。手动执行不改变定时计划,暂停的任务也可以手动执行。 5. 暂停只阻止未来触发,不取消已经开始的业务;编辑或删除执行中的任务会被拒绝。删除保留历史日志。 ## Cron 和时区 支持5字段“分 时 日 月 周”或6字段“秒 分 时 日 月 周”。星期0为周日,可以使用 MON-FRI 等名称。日期或星期字段的单独 `?` 等价于 `*`。解析器为 robfig/cron v3,支持其标准列表、范围和步长语法;这不是完整的 Quartz 表达式兼容层。 | 表达式 | 含义 | |---|---| | `0 * * * * *` | 每分钟开始 | | `0 */5 * * * *` | 每5分钟 | | `0 0 * * * *` | 每小时 | | `0 0 9 * * ?` | 指定时区每天09:00 | | `0 0 9 * * MON-FRI` | 指定时区工作日09:00 | | `*/5 * * * *` | 5字段,每5分钟 | 不支持年份字段、Quartz 的 L/W/# 特殊语法、@every 等描述符。日与星期同时约束时遵循标准 Cron 语义,建议只约束其中一项,并先预览;跨夏令时按 IANA 时区与解析器计算绝对触发时刻。 ## 执行身份和权限 - 平台权限为 `jobs.read` 和 `jobs.manage`。系统管理员动态获得;普通角色需单独授予。非管理员只看管自己的任务,管理员可管理全部任务,但仍需具备目标动作权限。 - 每次保存将执行账号绑定为当前编辑人,保存当时的有效权限作为上限。再次编辑别人创建的任务会改为编辑人的执行身份,界面有明确说明。任务不能指定其他 run_as、账号或任意系统方法。 - 每次触发重新检查账号启用状态、当前任务管理权限、目标模块动作权限,并与保存时权限取交集;不能因为后台无登录页面就以系统最高身份执行。 - 若通过 API Key 保存,任务还绑定该 Key 的 ID;撤销、到期、账号停用或权限收回后,之后触发记录授权失效,不调用业务。保存的 Key 不包含明文密钥。可由有权限的人重新编辑保存以更新授权。 - 浏览器管理接口仍检查会话、Origin/Host 和 CSRF。个人 Key 调用同样受限;任务不能绕过模块批准的平台能力或自身业务状态检查。 - 任务业务参数保存在受保护配置文件中,不应放入密码、Key 或数据库凭据。执行日志不记录参数及模块返回正文,避免将通讯录、Token 等业务内容复制到日志。 ## 并发 错过与超时 禁止重叠是默认策略:同任务仍有执行在途时,新触发记录跳过。允许重叠时每任务最多4个在途,全调度器最多8个,超过则跳过并记录。不同任务也继续受底座全局/用户/模块准入保护;同模块底层当前仍串行,允许重叠不保证模块内部并行。 调度扫描间隔1秒,触发迟到超过5秒按错过策略处理。skip 跳过历史积压并把下次时间推进到未来;once 对积压补跑一次,再推进到未来,不逐条追赶。暂停后重新启用从当前时刻重新计算,不补跑暂停期间的计划。 任务可设置1—10秒总超时,底层模块也有10秒执行预算。本版不适合直接运行长时间批处理;可由应用短请求提交自己的异步业务任务,另行管理其进度。失败、限流、繁忙、超时都不自动重试写操作。 ## 持久化和故障语义 单机执行器使用服务数据目录下 `scheduled-jobs.json`,文件权限0600,随同数据目录备份。任务配置、执行记录、运行前声明和调度时间推进原子提交;持久化失败时不派发新任务。任务最多200个,保留最近2000条执行记录和500条配置审计,正在运行的记录不被清理。 执行前先持久化 running 记录和本次稳定业务请求号,随后调用模块。底座重启会将未完成记录标为 interrupted/UNKNOWN_OUTCOME,提示业务结果可能已提交,不自动重放这次写入。完成日志写入失败也会显示存储错误,不能据此盲目补跑。本版不宣称 exactly-once 或保证每个触发都交付成功;需要业务模块使用 request_id 实现幂等并核对未知结果。 手动运行需要调用方提供稳定 request_id,相同账号/任务/请求号在日志保留期内只派发一次;结果返回原执行记录。改变任务版本却复用旧请求号会冲突。新一次手动操作使用新请求号,因此可能产生新的业务效果,应先核对原结果。 任务调用当前已注册的模块版本,应用升级后应复查任务动作和参数。模块停用时执行失败,不自动启动模块。调度器依赖当前单实例底座的数据目录锁;没有分布式锁、集群主选举或跨节点作业协调,不要让多个独立底座副本同时调度同一业务。 ## 日志与审计 执行日志展示任务及版本、分组、触发方式、执行主体、触发主体、计划与实际时间、耗时、状态、错误码、Trace ID、业务请求号。成功只表示模块按接口返回成功;timeout/interrupted 表示可能已有未知写入,需要业务核对。配置新增、修改、启停、删除和手动执行同时进入平台操作审计。 支持按任务、状态查询日志及分页。Trace ID 进入原有模块执行可观测链路;不增加含任务名/账号/请求正文的无限基数指标。新部署无默认启用任务,不能用上线后突然出现的写入来证明调度成功。 ## HTTP API | 方法与路径 | 用途 | |---|---| | `GET /api/jobs` | 任务列表、运行数、存储错误和限制 | | `POST /api/jobs` | 新建或编辑,编辑携带当前version | | `POST /api/jobs/preview` | Cron/时区最近5次时间 | | `POST /api/jobs/{id}/enable` | 启用,正文包含version | | `POST /api/jobs/{id}/disable` | 暂停,正文包含version | | `POST /api/jobs/{id}/run` | 执行一次,version和request_id必填 | | `POST /api/jobs/{id}/delete` | 删除,正文包含version,日志保留 | | `GET /api/jobs/logs?job_id=...&status=...&page=1&page_size=20` | 分页执行日志 | 创建健康检查示例(默认暂停): ```json {"name":"调度链路自检","group":"OPS","cron":"0 */5 * * * *","timezone":"Asia/Shanghai","target":"health","payload":{},"enabled":false,"concurrent":false,"misfire":"skip","timeout_seconds":10,"remark":"先手动验证,再启用"} ``` 改为模块任务时使用 `target:"module"`,并填入 `module:"directory"`、`action:"list"` 等已注册且有权调用的动作;业务参数使用 payload。任务ID、执行主体、权限快照、创建/更新时间等由底座生成,不能从请求正文注入。 参考:[RuoYi 后台手册的定时任务](https://doc.ruoyi.vip/ruoyi/document/htsc.html#定时任务);[robfig/cron 官方文档](https://github.com/robfig/cron)。本实现使用Go调度与既有模块协议,不引入Java Quartz或任意反射调用。