从本地轮询到 Serverless:抖抖先知的开源实践
开播提醒的关键不是定时请求,而是状态、去重,以及别人能否真正部署起来。
我想解决一个很小的需求:关注的抖音主播开播时,尽早收到提醒。
抖音本身有开播通知,但我的实际体验是,有时刷了一会儿视频才收到推送,容易错过开场。这是个人使用体验,不代表平台对所有用户的通知规律。于是我做了“抖抖先知”,把是否开播变成一个可持续观测的状态。
现在它有两个入口:在微信搜索“抖抖先知”小程序直接使用,或部署 douyin-live-alert 开源项目,让飞书机器人发私聊提醒。开源仓库当前提供的是飞书 + Cloudflare 版,不是微信小程序的完整源码。 小程序的授权与多用户链路另见 此前的复盘。
从“本机能跑”到“不用守着电脑”
最初的方案很直观:定时打开浏览器,读取主播页面,再发通知。它验证了需求,却也把浏览器进程、桌面计划任务和机器开关机绑进了服务的可用性。
这次开源版把主链路改成 HTTP 状态采集,用 Cloudflare Workers 执行定时任务,D1 保存主播状态与通知事件,飞书负责最后一公里的消息。运行时不需要 Chromium,也不要求登录抖音。这里“不登录”不等于接口长期稳定:它仍然依赖非官方接口,需要持续维护。
Cron 每分钟触发
→ D1 选取到期主播、获取调度租约
→ 有界并发检查抖音直播状态
→ 判断状态变化,写入唯一通知事件
→ 飞书机器人私聊;失败后退避重试
Cloudflare 的免费额度降低了个人部署门槛,但“可在免费额度内运行”不是“任意规模永久免费”。主播数量、调度频率与平台政策变化都可能改变成本。
定时器不难,状态语义才是核心
一个常见错误是:查到主播正在直播,就发通知。这样每轮检查都会发送同一条消息。
我需要识别的是变化:离线变为开播,或者直播间 ID 发生变化。首次成功观察只建立基线,所以部署时主播正好在直播,也不会立即生成一个历史开播提醒。这个取舍减少误报,但也意味着刚添加一个已经开播的主播,不会马上收到开播通知。
另外,请求失败不能当成“主播离线”。超时、风控响应或字段变化描述的是采集失败,不是业务状态。否则一次网络抖动就可能制造下一轮的假“重新开播”。健康信息与主播状态需要分开理解。
默认调度周期是一分钟,单次检查通常数秒完成。因此更准确的说法是:正常条件下通常在一个轮询周期内发现开播,整体分钟级、检查过程秒级。消息平台延迟、调度和重试会进一步影响到达时间,不能承诺绝对实时。
防重复要处理两种并发
第一种是调度重叠:上一轮还没结束,下一轮已经开始。项目通过租约协调到期任务,以 sec_uid 归一化主播,并限制并发,避免重复配置引起重复轮询,也避免把 Serverless 的外连资源耗尽。
第二种是通知重复:同一个状态变化被重复观察或任务重试。唯一事件键约束事件创建,发送失败采用指数退避,而不是立刻无限重试。
不过,数据库中的事件去重并不自动等于跨平台的“恰好一次”送达。如果外部平台已经接收消息,而本地还没记录成功就中断,重试仍可能有重复风险。对提醒工具而言,我更愿意明确这个边界,而不是把“没有观测到重复”写成绝对保证。
为什么保留两个通知入口
微信小程序适合不想部署的用户,但一次性订阅消息依赖用户主动授权,成功发送后会消耗额度。它不是无限通知通道。
飞书版面向愿意配置自己应用的用户,由机器人发送私聊。接收人要使用正确的 open_id,应用权限也需要正确配置;不能把邮箱或其他用户标识直接当成收件地址。
它们服务于同一个需求,却有不同的产品边界。开源版不是“小程序的升级替代品”,而是给自托管用户增加一种选择。
这是微信小程序版的界面,不是开源 Worker 的管理界面。点击可查看原图。
AI 可部署,首先得有部署契约
这次我不想只公开代码,还想降低复现成本。仓库包含 AGENTS.md、config.schema.json、deploy.manifest.json 和 AI 部署说明,让 Agent 能区分必填配置、迁移步骤、健康检查与需要授权的外部动作。
使用者可以让能操作终端的 Agent 先读文档、跑测试与 doctor,再只询问缺少的配置。应用密钥放进 Worker Secret;生产数据库标识和本地配置不混进公开模板。
有一条规则尤其重要:未经允许不发送测试通知,也不伪造生产开播状态。 验证代码可以用测试夹具;验证生产通知则涉及真实用户,必须是明确授权的动作。让 AI 帮忙部署,不等于把所有副作用一并交给它决定。
这次开源留下的经验
我最在意的不是“换成了 Serverless”,而是三件事:把状态语义说清楚,把失败变得可观测,把部署过程变得可复现。简单需求真正难的地方,往往藏在首次运行、重试、权限和平台限制里。
想自己部署,可以从 仓库 README 开始;不想折腾环境,可以在微信搜索“抖抖先知”。两条路径都应该让使用者知道自己得到了什么,以及没有得到什么。
本项目是非官方工具,与抖音、飞书、Cloudflare 没有隶属或背书关系。接口与平台额度可能变化,使用时需要遵守相应平台规则。