定时任务
定时任务插件提供通过代码定义“按固定时间触发”业务动作的能力,并让管理员在 UI 上查看排程、启停任务、追踪每次触发的结果。开发者通常不需要手写完整实现细节,而是把业务需求、可观测要求和验收条件交给应用 Agent,由 Agent 使用当前应用同步的 Scheduler Skill 完成代码、同步和验证。
什么时候使用
优先使用 Scheduler 的场景:
- 每天、每小时、每几分钟触发一次同步、汇总、清理、提醒或巡检;
- 管理员需要看到任务是否启用、下次运行时间、历史触发记录和失败原因;
- 触发动作需要幂等,不能因为进程重启或 worker 重试产生重复业务效果。
不需要管理员在 UI 上查看和启停的后台任务,不必接入 Scheduler;周期性的用应用自己的 ScheduleExecutor,一次性的后台工作用 JobExecutor,二者都来自应用的 jobs 服务。如果业务需要人工审批、版本化流程、路径观测和节点级运行记录,优先让 Agent 评估是否使用工作流。
使用 Agent 开发
Scheduler 插件随包发布 nocobase-app-plugin-scheduler Skill。插件启用后,应用会把它同步到 .agents/skills/;如果目录缺失或明显过期,在应用根目录运行:
通常不需要显式指定 Skill。只要需求包含“定时运行”和“管理员可在 UI 观测”,应用 Agent 应主动发现并使用它。开发者可以直接描述业务目标、处理规则和管理员需要看到的结果,例如:
我们需要一个“每日客户回访提醒”:每个工作日早上 9 点,找出昨天仍未联系的客户,为负责销售创建待办并发送站内提醒。管理员需要能看到任务是否启用、每次执行处理了多少客户以及失败原因;同一个客户在同一天不能重复创建提醒。请先了解当前应用已有的数据、通知和自动化能力,选择合适的实现方式并简要说明方案,确认后再完成开发和验证。
一个高质量需求应说明:
- 什么时候运行:Cron、人类时间描述、时区、起止时间或次数上限;
- 执行什么:调用哪个业务动作、工作流或外部系统;
- 管理员需要看什么:任务名称、说明、成功标准、失败时如何定位;
- 验收证据:需要跑哪些测试、是否要在 UI 中看到一次真实执行。
提示词示例
先用这个通用模板描述需求:
我需要一个管理员可观测的定时任务:
<什么时候运行>,按<时区>,执行<业务动作>。管理员需要看到<任务名称、说明、成功或失败依据>。请先了解当前应用和这项业务,提出实现方案并确认;确认后再完成开发、同步和验证。
生成经营报表
每天凌晨 2 点按
Asia/Shanghai时区生成经营报表。现有“经营报表”功能已经可以完成报表生成,管理员需要看到下次运行时间和每次执行是否成功。请先了解现有实现并提出方案;确认后完成开发,并告诉我如何在界面中验证。
库存检查与补货建议
每天 01:00 检查库存并生成补货建议。现在还没有这项自动化能力,请设计并实现完整方案。管理员需要知道本次检查处理了哪些商品、生成了多少补货建议,以及失败时如何定位;同一批库存数据重复处理不能产生重复建议。请先说明方案,确认后再开发和验证。
客户状态同步
每 10 分钟同步一次客户状态。请复用现有的客户同步能力,不要改变现有业务规则。管理员要看到每次同步是否成功、同步了多少客户以及失败原因;重复执行同一时间段的同步不能造成重复更新。请先说明方案,确认后再开发和验证。
大批量对账
每晚 23:30 执行一次大批量对账,数据量很大,通常需要几十分钟才能完成。管理员需要看到任务已经开始、当前是否仍在处理,最终能确认成功或失败,并能定位失败原因;同一批账单不能被重复对账。请先了解当前应用的对账实现和运行条件,提出方案并确认;确认后完成开发和验证。
修改已有定时任务
把现有“每日经营报表”的执行时间从每天 02:00 改为每天 03:30,时区仍然是
Asia/Shanghai。请先找到现有实现并修改它,不要创建重复的任务;完成后确认管理员界面中的下次运行时间已经更新。
审核 Agent 方案
先不要改代码。我们希望每周一早上生成上周的销售分析并通知各区域负责人。请根据当前应用已有能力给出实现方案,说明运行时间、业务步骤、管理员能看到的结果、失败后的处理方式、需要我确认的信息,以及完成后如何验证。
Agent 应报告它选择的执行模型、稳定 schedule key、时区、目标类型、同步命令、验证命令和未验证的运行边界。不要只接受“已实现”四个字;让 Agent 给出实际检查证据。
推荐开发流程
- 确认是否需要 Scheduler。 关键判断是管理员是否需要通过 UI 查看、启停和追踪执行记录。没有这个要求时,用应用自己的
ScheduleExecutor更合适。 - 根据业务选择执行任务类型。 需要多个流程步骤、节点级观测、人工介入或较长时间运行的任务,使用工作流类 job;单一动作、无需节点级观测和人工介入的任务,使用普通 job。是否使用工作流应由业务场景决定,而不是由任务是否已经存在工作流来决定。
- 定义任务。 在应用或业务插件 Provider 中调用
schedulerServiceToken.defineSchedule(definition),使用应用内全局唯一且稳定的key,建议使用业务命名空间,例如sales.daily-report。 - 同步并验证。 运行
pnpm nocobase scheduler sync --json,用管理员账号进入“设置 → 自动化 → 定时任务”确认任务、下次运行时间和执行记录。 - 发布时 finalize。 生产部署确认完整插件清单已加载后,每个应用运行一次
pnpm nocobase scheduler sync --finalize --json,软停用代码中已移除的定义。
常用服务 API
开发者最常用的是 Scheduler service 实例上的 defineSchedule()。它注册“这个应用应该有哪些定时任务”,Scheduler 在同步时把这些代码定义写入排程表,并保留管理员在 UI 上做过的启停状态。
key 是应用内全局唯一的稳定标识。应用名和 key 共同构成持久身份;改名不是原地更新,而是创建一个新定义。多个业务模块可以使用带命名空间的 key,例如 sales.daily-report 和 inventory.daily-report。
register() 和 boot() 都会在 Scheduler 自己的 start() 读取清单前执行完。只要在这两个生命周期里定义任务,插件之间的启动顺序通常不需要额外协调;在 start() 之后再定义的任务,要等下一次同步才会生效。
定义字段:
defineSchedule() 会校验 Cron、时区、敏感配置字段和目标类型,规范化定义并计算哈希。不要手写哈希,也不要直接修改 schedule_definitions 或 schedule_occurrences。
使用内置 Workflow 目标
多数“需要 UI 观测的复杂定时业务”可以拆成两层:Scheduler 负责“什么时候触发”,Workflow 负责“触发后做哪些步骤”。Workflow 插件注册了内置目标 workflow,所以定时任务只需要引用工作流源码目录名:
workflowKey 是工作流 source root 的目录名,不是标题或数据库里的版本 id。工作流目标会用本次定时任务 occurrence 作为幂等来源启动运行。工作流不存在、被禁用或输入不符合当前版本要求时,任务会在执行记录中显示失败或跳过原因。
让 Agent 同时处理工作流和定时任务时,应要求它分别报告:工作流目录、输入约定、定时任务 key、Cron、时区、同步结果,以及至少一次执行记录如何检查。
扩展开发:自定义目标
只有当内置 workflow 目标不能表达执行边界,或你要接入应用自有 Service、JobExecutor、外部执行系统时,才需要注册自定义 target。这个 API 是扩展开发入口,不是每个定时任务都要写。
目标类型要带命名空间,避免和其他插件冲突。app. 适合应用自有目标,插件目标可以使用插件名或包名前缀。重复注册同一个 type 会在启动时报错。
短任务可以在 start() 中直接返回 { state: 'completed', outcome: 'succeeded' }。长耗时任务应交给 JobExecutor 或外部系统执行,然后返回 accepted,并同时满足:
- 用
context.occurrenceId作为 reference 和幂等键:job 按 occurrence 记录执行状态,同一个 occurrence 重复分发时找到的是同一个业务执行; - 在实际成功、失败、取消或超时后调用
handle.reportCompletion(occurrenceId, reference, completion)。结果由 job 自己判定,判定后正常结束而不是抛错,因为 job 无法知道哪一次尝试是最后一次; - 实现
inspect(reference),让 Scheduler 在通知丢失、进程重启或通知早于接受记录落库时能补偿状态。
start() 可以返回四类结果:
完成回报的状态是 succeeded、failed、cancelled 或 timed_out。receipt、result、reason、reference 和展示信息都应保持简短、可控、无敏感信息。
同步和发布
新增或修改定义后,在目标应用根目录运行:
普通同步会加载完整应用、校验已注册目标、非破坏性 upsert 定义,并保留管理员在 UI 上做过的启停状态。应用正常启动时也会自动执行一次非破坏性同步,然后 Scheduler 开始按排程执行。
排程在哪里运行由应用的 jobs 配置决定。没有设置 jobs.default 时使用内置的 memory 适配器:状态保存在进程内存中,启动时从 storage/jobs 读取,应用停止时写回,进程被强制结束时会丢失启动以来的变化。这个适配器下,运行中的应用停止时会覆盖 scheduler sync 写入的内容,但不会因此丢失什么:每次启动都会从代码重新同步,并再次删除已停用和已失效定义的规则。部署多个实例前,在 config.yml 中把 jobs.default 设为 redis,每次触发只会在其中一个实例上执行。
如果要让 Scheduler 的排程使用单独的一套配置,在 scheduler.jobs(或环境变量 SCHEDULER_JOBS)中写出它的名字;不设置时跟随 jobs.default。排程使用这套配置的 concurrency 和 attempts。attempts 请保持为 1:失败的触发已经记录为该次执行的结果,重试时发现已有记录,不会再执行。
生产部署确认所有插件都已加载后,每个应用运行一次:
--finalize 会把代码清单中已不存在的定义软停用,并保留历史记录。不要在只加载了部分插件的进程里 finalize。
管理员操作
管理员只在界面上操作,不直接编辑代码定义。进入“设置 → 自动化 → 定时任务”后,可以查看任务列表、搜索、按状态和目标类型筛选、启停任务,并打开详情页查看定义摘要、目标信息、下次运行时间和最近 100 条执行记录。
列表状态含义:
- 运行中:任务启用,定义仍在代码清单中,目标可用;
- 已暂停:管理员通过 UI 禁用了任务;
- 已失效:
--finalize后,该定义已经从代码清单中移除; - 目标异常:目标缺失、禁用或配置无效。
执行记录常见状态:
pending/running:Scheduler 已领取或正在分发;waiting:目标已接受请求,正在等待最终完成;succeeded:目标成功完成;failed:分发或目标执行失败;skipped:目标主动跳过或不可运行;cancelled/timed_out:异步目标以取消或超时结束;triggered:旧记录只表示目标已接受请求,最终结果未知。
界面可以启停任务,但不能新建、编辑或删除代码定义。遇到目标异常、定义失效或持续失败,应由开发者或 Agent 修复源代码、重新同步,并确认目标自己的 worker 或工作流运行正常。
审核 Agent 的交付
开发者审核时重点看证据:
- 是否说明为什么使用 Scheduler,而不是应用自己的
ScheduleExecutor或工作流单独处理; - 是否读取了当前应用已安装插件、Provider、
jobs配置和权限入口; - 是否使用应用内全局唯一且稳定的
key、Cron 和 IANA 时区; - 是否区分了内置
workflow目标和自定义 target 扩展; - 是否说明幂等策略、异步完成回报和失败后的观测方式;
- 是否运行了类型检查、相关测试、构建或同步命令,并说明跳过项;
- 是否能在管理员 UI 中看到任务和一次执行记录,或说明为什么无法现场验证。

