跳到主要内容

用 Cloudbase 云函数定时触发器跑 cron 任务

一句话定义:在 cloudbaserc.jsonfunctions[].triggers 里写一条 type: timer 配置和 7 字段 cron 表达式,然后 tcb fn deploy,就能让一个云函数按时触发,典型用途是日报、数据清洗、缓存预热。

预计耗时:25 分钟 | 难度:基础

适用场景

  • 适用:每天 / 每小时 / 每周固定时间跑一次的批处理(日报、对账、清理过期数据、预热缓存)
  • 适用:把外部异步事件汇总成日级别报告
  • 不适用:秒级 / 毫秒级实时调度(用消息队列或长连接更合适)
  • 不适用:需要严格「全局只跑一次」语义的关键交易(本身定时器在边缘场景下可能重复触发,看「第四步:防重幂等」)

环境要求

依赖版本
Node.js(云函数运行时)≥ 16.13
@cloudbase/clilatest
@cloudbase/node-sdk3.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

运行验证

  1. 部署完后,控制台「云函数 → dailyReport → 触发器」能看到触发器配置
  2. 不想等到 9 点,可以临时把 cron 改成 0 */1 * * * * *(每分钟),重新部署,看一两次能跑就改回去
  3. 控制台「云函数 → dailyReport → 调用日志」过滤「定时触发」的来源,应该看到对应时间点的执行记录
  4. 控制台「数据库 → daily_reports」应该多一条 date 等于今天的记录
  5. 故意在函数里 throw new Error('test') 部署一次,确认企业微信群收到告警,再回滚

常见错误

错误现象原因修复
部署后触发器没出现cloudbaserc.jsontriggers 字段位置写错(放到了顶层而不是 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 秒

相关文档

下一步