3.4 定时触发器:每日自动摘要
评论和点赞接口上线后,每天早上我都要手动看一遍数据:昨天发了多少篇、哪篇最火、计数器有没有不一致。某天起晚了,中午才看,错过了上午该处理的一条违规评论。当天晚上我就给云函数挂上了定时触发器,让这些巡检和汇总每天凌晨自动跑。这一节记录定时触发器从配置到落地的全过程,以及调试过程中遇到的时区和间隔两个经典坑。
场景:哪些活适合交给定时器
不是所有巡检都值得自动化,先盘一下哪些是高频且无脑的:
- 每日数据摘要,汇总发文量、互动量推送给管理员
- 计数器对账,修正点赞数与明细数不一致的文章
- 冷启动预热,每十分钟空跑一次列表函数
- 过期内容下架,超过保留期的临时内容自动隐藏
- 证书与额度巡检,快到期或快超额时提前告警
前两类每天跑一次,第三类一天跑一百多次。触发器和函数是多对多的:一个函数可以挂多个触发器,不同函数也可以共用同一时间规则。理解这一点很重要——不要为每个定时任务都新建一个函数,触发器只是函数的一个调用入口。
配置触发器
项目里统一用 CLI 配置,函数目录下的 cloudbaserc.json 是入口。触发器配置长这样:
{
"functions": [
{
"name": "article",
"triggers": [
{
"name": "dailyDigest",
"type": "timer",
"config": "0 0 8 * * * *"
}
]
}
]
}
config 是 Cron 表达式,七位格式:秒 分 时 日 月 星期 年。0 0 8 * * * * 表示每天早上八点执行。部署时一行命令:
# 部署函数的同时创建触发器
tcb fn deploy article --triggers
通信扩展相关的推送能力,可以看云开发文档里的扩展能力说明,摘要推送最终就是走的这个通道。
常用 Cron 规则对照表,直接抄就能用:
| 规则 | 含义 | 项目里的用途 |
|---|---|---|
0 0 8 * * * * |
每天八点 | 每日摘要推送 |
0 30 2 * * * |
每天凌晨两点半 | 计数器对账 |
0 */10 * * * * * |
每十分钟 | 列表函数预热 |
0 0 1 * * * |
每月一号 | 月度成本汇总 |
0 0 11 * * 1 |
每周一上午十一点 | 周报数据 |
我第一次写 Cron 就写错了:把 0 0 8 * * * * 少写了一位变成 0 0 8 * * *,部署直接失败。踩过之后养成习惯,写完在控制台的触发器面板里确认下次执行时间,那里会显示下一次触发的绝对时刻,一眼能看出对不对。
控制台也能配触发器:函数详情页 → 触发器 → 新建。在线编辑适合临时调整,但项目里不推荐,原因是它不进版本库,换台电脑部署就丢了。所有触发器一律跟代码走 cloudbaserc.json,这是血泪教训。
每日摘要的实现
摘要函数的核心是把散落的数据聚成一小段可读文本。代码结构分三步:取数、汇总、推送。
// 每日摘要函数
exports.main = async (event, context) => {
const now = new Date()
const start = new Date(now.getTime() - 24 * 60 * 60 * 1000)
const [articles, likes, comments] = await Promise.all([
db.collection('articles').where({
createTime: cmd.gte(start.getTime())
}).count(),
db.collection('likes').where({
createTime: cmd.gte(start.getTime())
}).count(),
db.collection('comments').where({
createTime: cmd.gte(start.getTime())
}).count()
])
const hottest = await db.collection('articles')
.orderBy('likeCount', 'desc')
.field({ title: true, likeCount: true })
.limit(3)
.get()
return buildReport({ articles, likes, comments, hottest })
}
buildReport 把数字拼成一段纯文本,通过推送通道发给管理员。推送的内容里会带上热度最高的三篇,这也是我们最早的数据驱动产品迭代来源——每天看哪篇最火,就知道下一版该往哪倾斜,这比每周开会拍脑袋有效得多,后来整个运营节奏都按着每日摘要走,起步方法在选型与开工里讲过的快速验证思路一脉相承。
摘要里还有一个重要的健康指标:计数器差异条数。就是把对账结果附在摘要末尾,正常是零,出现非零说明有并发更新漏掉了。有这个数字之后,计数器问题从被动发现变成主动监控。对账的实现完整版在评论与点赞里讲过幂等层,这里补上最后一层。
踩坑一:时区
最经典的一个坑是时区。Cron 表达式默认是 UTC 时间,我配的八点实际是北京时间下午四点,摘要推了两天才反应过来为什么数据看着不对——它汇总的是下午四点前二十四小时的数据,把前一天的尾巴算进来了。
两种解法:一是配置时把北京时间换算成 UTC(北京时间是 UTC 加八,所以北京时间早上八点对应 UTC 零点),二是在云函数环境变量里设置时区(部分运行时支持)。项目里选了第一种,可配性差一点但确定性强,所有 Cron 都按 UTC 写再注释上换算:
{
"name": "dailyDigest",
"type": "timer",
"config": "0 0 0 * * * *",
"remark": "UTC 零点,即北京时间早八点"
}
第二个相关的坑是漏跑。定时触发器不保证精确,高峰期可能延迟几分钟甚至十几分钟。所以函数内部一定不要依赖它准点执行:取数的时间窗口用相对当前时刻计算(像上面代码里 now.getTime() - 24小时),而不是写死从昨天零点开始。这样无论函数延迟多久,二十四小时窗口永远是对的。
踩坑二:预热触发器的间隔
预热触发器踩的是另一个坑。前文列表获取那节提过冷启动问题,我配了每十分钟的预热,为了省事直接把预热函数和摘要函数通用化,让触发器调用同一个函数。结果某天早上一看日志,预热触发器也在发每日摘要推送——因为函数内部没区分调用来源。
定时触发器的 event 里带有触发器标识,函数可以根据它区分来源:
// 区分触发来源
exports.main = async (event, context) => {
const isTimer = event.TriggerName || event.Message
if (isTimer === 'keepWarm') {
return warmUp()
}
if (isTimer === 'dailyDigest') {
return dailyDigest()
}
// 普通调用
return handleApi(event)
}
第二个预热的坑更隐蔽:预热频率过高直接把免费额度跑光。每十分钟一次等于一天 144 次调用,如果峰值时段之外也在预热就是纯浪费。优化方案是只在业务时段预热:
| 预热策略 | 每日调用数 | 效果 | 额度消耗 |
|---|---|---|---|
| 全天每十分钟 | 144 次 | 全天无冷启动 | 中等 |
| 早八到晚十二每十分钟 | 96 次 | 高峰无冷启动 | 较低 |
| 仅早晚各一次 | 2 次 | 仅首屏无冷启动 | 极低 |
| 按需由首个用户承担 | 0 次 | 首个用户略慢 | 为零 |
项目最后选了第二种。免费额度这件事在前面费用与免费额度里算过明细,这里只强调一句:定时器的调用次数本身也计费,配间隔时要把调用量一起算进去,别省了冷启动的体验、亏了额度的预算。
监控触发器本身
定时任务最怕的是静默失败:触发器不触发了,你却一无所知。防范手段是给摘要函数加一道自检——每天摘要发完顺手记录一条执行日志到集合里:
// 执行留痕,用于自检
await db.collection('digest_logs').add({
date: new Date().toISOString(),
status: 'ok',
duration: Date.now() - startTime
})
再写一个每周一的巡检函数,检查过去七天 digest_logs 是不是正好七条,少了就立即告警。用定时器监控定时器,这个套娃是监控体系的常规操作。完整监控方案的搭建在后面监控与成本那一节,会连同云函数自身的告警配置一起讲。
还有一个排查小技巧:本地调试触发器要把 event 手动构造成定时器格式,传一个包含 TriggerName 的对象进去就行,这样可以在本地完整跑通摘要逻辑而不用等一晚上。云函数调试的整体方法见云函数创建那一节。
小结
定时触发器是让项目从手动走向自动的关键一步。核心记住四点:Cron 是七位格式且默认 UTC 时区、时间窗口用相对时刻计算、按触发来源分发逻辑、调用量计入成本预算。配置成本很低,但它把巡检、对账、摘要这些日常杂活彻底固化成了代码,我从此再没在早上手忙脚乱查过数据。
第三篇到这里就结束了。回顾一下:云函数创建打开了后端能力,列表分页处理了读多写少的主路径,原子计数器搞定了写操作的准确性,定时触发器补上了自动化运维的最后一块。下一章进入内容发布与存储,把发文流程、图片直传和内容审核串起来——那才是真正面向用户的部分。
延伸阅读
- Python 云开发实战目录:本书章节索引与存储发布章节
- Node 云函数实战:定时任务体系搭建同类案例
- 腾讯云实践:Cron 时区与监控的经典踩坑复盘
