设备未接收Firebase通知时触发SMS通知的实现方案咨询
实现思路与方案
核心改进方向
要实现超时未送达触发SMS的逻辑,关键是把FCM推送行为与通知状态追踪绑定,通过服务端的延迟检查机制来触发兜底的SMS通知。以下是具体可落地的方案:
一、基础改造:绑定通知标识与状态
给每个通知生成唯一ID
在服务端处理React网页的POST请求时,先生成一个唯一的notificationId(可用UUID),同时将以下信息存入数据库:notificationId:唯一标识targetDeviceId:目标设备的FCM tokenphoneNumber:目标用户的手机号(用于SMS)content:通知内容status:初始值设为pending(待送达)expireAt:超时时间(比如当前时间+5分钟)
FCM推送时携带通知ID
调用Firebase的getMessaging().send()方法时,把notificationId放在通知的data字段中:// Node.js服务端代码示例 const message = { token: targetDeviceId, notification: { title: '交易通知', body: content }, data: { notificationId: 'xxx-xxx-xxx' } // 携带唯一ID }; await getMessaging().send(message);客户端接收后更新状态
Flutter客户端在onMessage和onBackgroundMessage回调中,提取notificationId,调用服务端接口将对应通知的status更新为delivered(已送达)。
二、服务端超时检查机制(两种可选方案)
方案1:延迟队列(推荐,精准控制超时时间)
用Node.js的队列库(如BullMQ、Agenda)实现延迟任务,确保超时时间到了就触发检查:
- 发送FCM的同时,将
notificationId加入延迟队列,延迟时间设为你指定的超时时长(比如5分钟)。 - 队列任务触发时,执行以下逻辑:
- 查询数据库中该
notificationId的status。 - 如果状态还是
pending,调用SMS服务商的API发送短信,然后将状态更新为sms_sent。 - 如果状态已经是
delivered,直接跳过任务。
- 查询数据库中该
示例代码(BullMQ):
const { Queue, Worker } = require('bullmq'); // 创建延迟队列 const notificationQueue = new Queue('notification-check'); // 发送FCM后添加延迟任务 await notificationQueue.add('check-status', { notificationId }, { delay: 5 * 60 * 1000 }); // 处理队列任务的Worker const worker = new Worker('notification-check', async job => { const { notificationId } = job.data; const notification = await db.collection('notifications').findOne({ notificationId }); if (notification?.status === 'pending') { // 调用SMS接口发送短信 await smsService.send(notification.phoneNumber, notification.content); // 更新状态 await db.collection('notifications').updateOne( { notificationId }, { $set: { status: 'sms_sent' } } ); } });
方案2:定时扫描数据库(实现简单,适合小流量场景)
用node-schedule等定时任务库,定期扫描数据库中超时且未送达的通知:
- 配置定时任务(比如每分钟执行一次),查询
status = pending且expireAt < 当前时间的通知。 - 对符合条件的通知批量触发SMS发送,并更新状态。
示例代码:
const schedule = require('node-schedule'); // 每分钟执行一次扫描 schedule.scheduleJob('* * * * *', async () => { const expiredNotifications = await db.collection('notifications').find({ status: 'pending', expireAt: { $lt: new Date() } }).toArray(); for (const notification of expiredNotifications) { await smsService.send(notification.phoneNumber, notification.content); await db.collection('notifications').updateOne( { notificationId: notification.notificationId }, { $set: { status: 'sms_sent' } } ); } });
三、替代方案(利用Firebase生态)
如果不想自己维护队列或定时任务,可以用Firebase Cloud Functions结合Firestore的TTL索引:
- 将通知数据存在Firestore,给
expireAt字段设置TTL索引(过期自动删除文档)。 - 编写Cloud Function监听Firestore的文档删除事件,当文档被删除时,检查该通知是否已标记为
delivered(可以在文档中存状态)。 - 如果未送达,调用SMS接口发送通知。
注意:Firestore的TTL触发可能有一定延迟(最长几小时),适合对超时时间要求不严格的场景。
四、关键容错处理
- SMS重试机制:如果SMS发送失败,设置最多3次重试,每次间隔1分钟,避免服务商接口波动导致漏发。
- 日志记录:记录FCM发送、状态更新、SMS发送的时间和结果,方便后续排查问题。
- 状态幂等性:服务端更新状态的接口要保证幂等,避免客户端重复调用导致状态混乱。
内容的提问来源于stack exchange,提问作者Ingine
相关产品推荐
相关产品推荐

