如何借助Cloud Tasks实现Firebase呼叫的定时通知调度与终止?
解决方案:用Cloud Tasks实现定时通知+状态触发终止
核心思路
放弃长驻定时器方案,改用单次延迟任务+循环调度的模式:每次发送通知后检查呼叫状态,若仍为open则创建下一个2分钟后的任务;若状态变为covered,则停止创建新任务。同时监听呼叫状态变更,一旦转为covered就批量取消该呼叫对应的待执行任务。
步骤1:配置Cloud Tasks环境
- 在Firebase控制台开启Cloud Tasks服务
- 在Cloud Functions项目中安装依赖:
npm install @google-cloud/tasks
步骤2:呼叫创建时启动首个延迟任务
编写数据库触发器,当新呼叫节点创建且状态为open时,立即生成第一个2分钟后执行的通知任务。
const functions = require("firebase-functions"); const admin = require("firebase-admin"); admin.initializeApp(); const { CloudTasksClient } = require("@google-cloud/tasks"); const client = new CloudTasksClient(); // 数据库触发器:呼叫创建时初始化任务 exports.onCallCreated = functions.database.ref("/calls/{callId}") .onCreate(async (snapshot, context) => { const callData = snapshot.val(); if (callData.status !== "open") return; const callId = context.params.callId; const project = process.env.GCLOUD_PROJECT; const location = "us-central1"; // 替换为你的Cloud Tasks区域 const queue = "call-notification-queue"; // 提前在控制台创建该队列 // 构建任务参数 const task = { httpRequest: { httpMethod: "POST", url: `https://${location}-${project}.cloudfunctions.net/sendCallNotification`, body: Buffer.from(JSON.stringify({ callId })).toString("base64"), headers: { "Content-Type": "application/json" }, }, scheduleTime: { seconds: Date.now() / 1000 + 120 }, // 2分钟后执行 name: client.taskPath(project, location, queue, `call-${callId}-${Date.now()}`), }; await client.createTask({ parent: client.queuePath(project, location, queue), task }); functions.logger.info(`初始化呼叫${callId}的通知任务`); });
步骤3:通知发送+循环调度逻辑
编写HTTP类型的Cloud Function,负责发送通知,之后检查呼叫状态,若仍为open则创建下一个延迟任务。
exports.sendCallNotification = functions.https.onRequest(async (req, res) => { try { const { callId } = req.body; const callRef = admin.database().ref(`/calls/${callId}`); const snapshot = await callRef.once("value"); const callData = snapshot.val(); // 状态非open则终止流程 if (callData.status !== "open") { functions.logger.info(`呼叫${callId}状态变更,停止通知`); return res.status(200).send("终止通知"); } // 执行通知发送逻辑(替换为你的FCM/短信等代码) await sendVolunteerNotification(callId); functions.logger.info(`已发送呼叫${callId}的通知`); // 创建下一个2分钟后的任务 const project = process.env.GCLOUD_PROJECT; const location = "us-central1"; const queue = "call-notification-queue"; const nextTask = { httpRequest: { httpMethod: "POST", url: `https://${location}-${project}.cloudfunctions.net/sendCallNotification`, body: Buffer.from(JSON.stringify({ callId })).toString("base64"), headers: { "Content-Type": "application/json" }, }, scheduleTime: { seconds: Date.now() / 1000 + 120 }, name: client.taskPath(project, location, queue, `call-${callId}-${Date.now()}`), }; await client.createTask({ parent: client.queuePath(project, location, queue), task: nextTask }); res.status(200).send("通知发送完成,已调度下一次任务"); } catch (error) { functions.logger.error("通知任务执行失败:", error); res.status(500).send("执行错误"); } }); // 示例:发送通知的辅助函数 async function sendVolunteerNotification(callId) { // 此处编写向志愿者推送通知的逻辑,比如查询志愿者列表、调用FCM API等 }
步骤4:状态变更时取消待执行任务
编写数据库触发器,当呼叫状态从open转为covered时,批量取消该呼叫对应的所有待执行任务。
exports.onCallStatusUpdated = functions.database.ref("/calls/{callId}") .onUpdate(async (change, context) => { const beforeStatus = change.before.val().status; const afterStatus = change.after.val().status; const callId = context.params.callId; if (beforeStatus === "open" && afterStatus === "covered") { const project = process.env.GCLOUD_PROJECT; const location = "us-central1"; const queue = "call-notification-queue"; // 筛选出该呼叫对应的所有待执行任务 const [tasks] = await client.listTasks({ parent: client.queuePath(project, location, queue), filter: `name:"call-${callId}"`, }); // 逐个取消任务 for (const task of tasks) { await client.deleteTask({ name: task.name }); functions.logger.info(`已取消呼叫${callId}的待执行任务:${task.name}`); } } });
关键注意事项
- 任务命名必须包含呼叫ID,确保后续能精准筛选并取消任务
- Cloud Tasks队列需提前在控制台创建,可配置合适的重试策略(如失败后重试3次)
- 确保Cloud Functions拥有Cloud Tasks的操作权限:Firebase部署的函数默认已配置权限,手动部署需添加
roles/cloudtasks.enqueuer和roles/cloudtasks.deleter角色 - 通知发送逻辑需做好错误捕获,避免因异常导致任务无限重试
内容的提问来源于stack exchange,提问作者Anas Ansari
相关产品推荐
相关产品推荐

