用 Cloudbase 云函数定时触发器跑 cron 任务
一句话定义:在
cloudbaserc.json的functions[].triggers里写一条type: timer配置和 7 字段 cron 表达式,然后tcb fn deploy,就能让一个云函数按时触发,典型用途是日报、数据清洗、缓存预热。预计耗时:25 分钟 | 难度:基础
适用场景
- 适用:每天 / 每小时 / 每周固定时间跑一次的批处理(日报、对账、清理过期数据、预热缓存)
- 适用:把外部异步事件汇总成日级别报告
- 不适用:秒级 / 毫秒级实时调度(用消息队列或长连接更合适)
- 不适用:需要严格「全局只跑一次」语义的关键交易(本身定时器在边缘场景下可能重复触发,看「第四步:防重幂等」)
环境要求
| 依赖 | 版本 |
|---|---|
| Node.js(云函数运行时) | ≥ 16.13 |
@cloudbase/cli | latest |
@cloudbase/node-sdk | 3.18.1(如果定时任务里要读写数据库) |
另外需要:
- 一个已开通的 Cloudbase 环境 ID
- 项目根目录有
cloudbaserc.json,如果没有可以tcb init生成
第一步:写一个最小的定时函数
新建 cloudfunctions/dailyReport/index.js:
const cloudbase = require('@cloudbase/node-sdk');
const app = cloudbase.init({
env: process.env.TCB_ENV || cloudbase.SYMBOL_CURRENT_ENV,
});
const db = app.database();
exports.main = async (event, context) => {
// 定时触发时,event 里会有 Type / Time 字段(具体字段以触发 日志为准)
console.log('[cron] triggered at', new Date().toISOString(), 'event:', event);
const since = new Date(Date.now() - 24 * 3600 * 1000);
const orders = await db
.collection('orders')
.where({
createdAt: db.command.gte(since),
})
.count();
await db.collection('daily_reports').add({
date: new Date().toISOString().slice(0, 10),
orderCount: orders.total,
generatedAt: db.serverDate(),
});
return { ok: true, orderCount: orders.total };
};
cloudfunctions/dailyReport/package.json:
{
"name": "dailyReport",
"version": "1.0.0",
"main": "index.js",
"dependencies": {
"@cloudbase/node-sdk": "^3.18.1"
}
}
第二步:配 timer 触发器
在项目根目录的 cloudbaserc.json 里加一段:
{
"envId": "your-env-id",
"functionRoot": "./cloudfunctions",
"functions": [
{
"name": "dailyReport",
"timeout": 60,
"memorySize": 256,
"runtime": "Nodejs16.13",
"handler": "index.main",
"triggers": [
{
"name": "every-day-9am",
"type": "timer",
"config": "0 0 9 * * * *"
}
]
}
]
}
config 是 7 字段的 cron 表达式,顺序是「秒 分 时 日 月 周 年」,这点和 Linux 的 5 字段 cron(分 时 日 月 周)不一样,容易踩。
| 含义 | cron |
|---|---|
| 每天上午 9 点整 | 0 0 9 * * * * |
| 每小时整点 | 0 0 * * * * * |
| 每 5 分钟 | 0 */5 * * * * * |
| 工作日 9-18 点每小时 | 0 0 9-18 * * 1-5 * |
| 每月 1 号凌晨 2 点 | 0 0 2 1 * * * |
| 每周一上午 10 点 | 0 0 10 * * 1 * |
时区以控制台和文档为准。Cloudbase 触发器在控制台「云函数 → 触发器」页有时区标注,部署前在控制台确认一次,避免按 UTC 配出每天「凌晨 5 点」的尴尬。
第三步:部署
tcb login --apiKeyId your-key-id --apiKey your-key
tcb fn deploy dailyReport --httpFn -e your-env-id
部署完到控制台 → 云函数 → dailyReport → 触发器 看一下,刚才写在 cloudbaserc.json 里的 every-day-9am 应该已经出现。
如果触发器没自动创建出来,可以先单独同步触发器:
tcb fn trigger create -e your-env-id
(命令以 Cloudbase CLI 文档 当前版本为准。)
要临时关停,在控制台点「停用」,不需要重新部署。
第四步:防重幂等
定 时触发器在大多数情况下是「在指定时间触发一次」,但极端情况下(发布升级、节点切换)可能短时间内触发两次。如果业务不允许重复执行,加一道幂等门:
exports.main = async (event, context) => {
const today = new Date().toISOString().slice(0, 10);
// 1. 用日期 + 任务名做幂等 key,先 set 一条 lock
try {
await db.collection('cron_locks').add({
_id: `dailyReport-${today}`, // _id 已存在会报错
acquiredAt: db.serverDate(),
});
} catch (e) {
if (e.code === 'DOC_ALREADY_EXISTS' || e.code === -502002) {
console.log('[cron] already ran today, skip');
return { ok: true, skipped: true };
}
throw e;
}
// 2. 业务主体
// ...
return { ok: true };
};
要点:
_id用「任务名 + 时间窗口」可以唯一,也保证 lock 自然按天滚动cron_locks集合保留期可以设短一些(7 天),控制台「数据库 → 集合 → 自动过期」配置,避免老数据累积- 上面
e.code字段名以实际报错对象为准,真出错时先console.log(e)看下结构
第五步:失败告警
定时任务跑挂了大概率没人看日志,所以要让失败主动找上来。最简单的接入方式是企业微信群机器人 webhook:
const https = require('https');
async function notifyWecom(message) {
const webhook = process.env.WECOM_WEBHOOK;
if (!webhook) return;
const body = JSON.stringify({
msgtype: 'text',
text: { content: message },
});
return new Promise((resolve) => {
const req = https.request(
webhook,
{ method: 'POST', headers: { 'Content-Type': 'application/json' } },
(res) => res.on('data', () => {}).on('end', resolve),
);
req.write(body);
req.end();
});
}
exports.main = async (event) => {
try {
// 业务主体
} catch (err) {
await db.collection('cron_failures').add({
task: 'dailyReport',
error: err.message,
stack: err.stack,
failedAt: db.serverDate(),
});
await notifyWecom(`[cron 失败] dailyReport: ${err.message}`);
throw err; // 抛出去让平台日志也记下
}
};
完整的企业微信 webhook 集成看 connect-wecom-webhook-cloud-function。
运行验证
- 部署完后,控 制台「云函数 → dailyReport → 触发器」能看到触发器配置
- 不想等到 9 点,可以临时把 cron 改成
0 */1 * * * * *(每分钟),重新部署,看一两次能跑就改回去 - 控制台「云函数 → dailyReport → 调用日志」过滤「定时触发」的来源,应该看到对应时间点的执行记录
- 控制台「数据库 → daily_reports」应该多一条
date等于今天的记录 - 故意在函数里
throw new Error('test')部署一次,确认企业微信群收到告警,再回滚
常见错误
| 错误现象 | 原因 | 修复 |
|---|---|---|
| 部署后触发器没出现 | cloudbaserc.json 里 triggers 字段位置写错(放到了顶层而不是 functions[] 里) | triggers 必须挂在 functions 数组的元素下 |
| cron 表达式部署失败 | 误用了 5 字段 Linux cron | 改成 7 字段秒分时日月周年 |
| 时间不对(差 8 小时) | 没确认时区 | 在控制台触发器页看时区标注,如果是 UTC 就把表达式按 UTC 重算 |
| 同一时刻执行了两次 | 部署/伸缩切换期间的边缘情况 | 加幂等 lock(第四步) |
| 跨天的统计在 0 点附近抖动 | new Date() 拿的时间和数据库 serverDate() 时区不一致 | 关键时间字段统一用 db.serverDate() 写,业务侧只用日期字符串(YYYY-MM-DD)做 key |
| 函数运行超过 60 秒被截断 | timeout 默认值偏小 | 在 cloudbaserc.json 里把 timeout 调大,最长可设到 900 秒 |
相关文档
- 定时触发器配置 —
cloudbaserc.json字段和 cron 字段语义 - Cloudbase CLI 触发器命令 —
tcb fn trigger子命令 - 云函数日志和告警 — 调用日志查询
- connect-wecom-webhook-cloud-function — 失败告警接企业微信
下一步
- 把告警渠道做完整:connect-wecom-webhook-cloud-function
- 定时给用户发订阅消息:add-subscribe-message-cloud-function
- 多租户隔离的批处理:secure-database-multi-tenant-rules