定时任务
定时任务让插件在指定时刻被宿主唤起——包括用户当时并没有打开这个插件的情况。需要 schedule:request 权限(apiLevel 6 起)。
api.requestSchedule(schedule): Promise<PluginScheduleResult>
api.getSchedules(): Promise<PluginScheduleInfo[]>
api.removeSchedule(code): Promise<boolean>
api.onScheduleTrigger(callback): () => void
这是权限清单里最需要用户知情的一项:它让插件在用户没打开它的时候被自动唤起。只在用户明确需要「定期自动做某事」时才申请,任务名要说清干什么。
申请一个任务
const result = await api.requestSchedule({
code: 'daily-report', // 任务标识,必填,同 code 视为更新
title: '每天 9 点整理昨日数据', // 给人看的名字,可省(省了就回落成 code)
trigger: { cron: '0 9 * * *' } // 触发规则
})
if (!result.ok) {
api.toast(`创建定时任务失败:${result.message}`)
}
trigger 的两种形态
| 形态 |
含义 |
例子 |
| 毫秒时间戳(数字) |
只跑一次,到点执行后任务自动删除 |
Date.now() + 60 * 60 * 1000 |
{ cron, startTime?, endTime? } |
按 cron 周期重复 |
{ cron: '0 9 * * *' } |
cron 只支持标准 5 段式,字段顺序是 分 时 日 月 周:
0 9 * * * 每天 09:00
*/30 * * * * 每 30 分钟
0 9-18 * * * 每天 09:00 到 18:00 之间、每小时整点
0 0 1 * * 每月 1 号 00:00
0 0 * * 1 每周一 00:00
每个字段支持 *、单值(5)、范围(9-18)、列表(1,3,5)、步长(*/15)、范围步长(9-18/2)。
startTime / endTime 是毫秒时间戳:startTime 之前不触发,越过 endTime 后任务自动清除。
日与周同时受限时按 OR 语义(标准 cron 行为):0 0 1 * 1 是「每月 1 号或每周一」,不是「既是 1 号又是周一」。只写其中一个时按那个来。
接收触发
const removeTrigger = api.onScheduleTrigger((action) => {
// action.code 就是申请时给的那个 code,靠它分发到不同任务的逻辑
if (action.code === 'daily-report') {
void runDailyReport()
}
})
// 插件卸载时(或不再需要时)
// removeTrigger()
订阅跟着插件实例走:插件退出时宿主自动退订,不必在 onPluginOut 里额外收尾。
插件没运行时也会被触发
这是这个能力的关键语义,也是它需要单独授权的原因:
- 如果插件正在运行(页面还挂着),宿主直接把触发事件推给它;
- 如果插件没在运行,宿主会先把插件拉起来,等它挂载完成后再把这次触发补发过去。
所以插件的触发处理逻辑要写在加载阶段就会执行的地方(例如 setup() 里注册 onScheduleTrigger),而不是等某个用户操作之后。
就绪握手是自动的:宿主在插件挂载时会收到报到,据此决定「现在推」还是「等它起来再推」。插件不需要为此写任何代码。
查询与移除
// 列出本插件申请过的任务(含执行统计)
const tasks = await api.getSchedules()
// [{ code, title, trigger, successCount, failCount, lastRunAt, lastStatus }]
// 移除一个任务;任务不存在也返回 false,不抛错
const removed = await api.removeSchedule('daily-report')
与 uTools 的差异:uTools 的 removeSchedule 对不存在的 code 会抛错,ToolZen 只回 false。插件拿一个布尔值比接一个异常好用,而且「任务已经不在了」本来就是正常结果(可能刚被别的路径清掉)。
执行成败是怎么统计的
successCount / failCount / lastStatus 反映的是插件回调的执行结果,不是宿主有没有成功唤起插件。宿主在触发后会把结果记进任务记录,你在 getSchedules() 里能读到。
lastRunAt 是最近一次触发的毫秒时间戳,从未触发过时是 null。
使用边界
- 单个插件最多 20 个任务。
- 插件被卸载或停用后,它的任务会被自动清掉——留着一份「插件已经没了、任务还在」的记录,只会在调度器里空转。
- 任务表存在本地,客户端重启后仍然生效。
- 不要拿它做后台轮询。这个能力的定位是「到点做一件明确的事」(每天整理数据、每周提醒),不是「每隔几秒检查一次状态」。轮询请用页面内的定时器配合
api.holdSessionReset()。
老客户端上的行为
schedule:request 是 apiLevel 6 引入的。老客户端上这四个方法都不存在,调用会拿到安全的空值:
const supports = typeof api.hasCapability === 'function' && api.hasCapability('schedule:request')
if (!supports) {
api.toast('定时任务需要升级 ToolZen 客户端')
}
也可以直接在 manifest 里声明 requires: ["schedule:request"],让宿主在加载前就拦下并提示用户升级——详见版本与兼容性。