Flutter+Firebase推送通知偏好系统构建:时间接收规则难题求助
解决方案建议
一、重构通知触发与发送流程
把原有的即时发送逻辑拆成「事件存储」和「定时分发」两步,解耦数据变更触发与用户时段控制:
存储待发送通知任务
在Firestore创建pending_notifications集合,每条文档包含:itemId: 关联的Item IDchangedField: 变更的字段名notificationContent: 通知标题、内容等核心信息targetTopic: 原item_${itemId}_${changedField}格式的主题标识(用来匹配订阅用户)createdAt: 任务创建时间戳status: 枚举值(pending/sent/failed)
当Cloud Functions捕获到Item数据变更时,不再直接调用FCM发送,而是往这个集合插入一条
status=pending的任务文档。定时执行发送任务
用Cloud Functions的定时触发器(建议每5分钟执行一次,可根据业务调整),每次执行逻辑:- 拉取所有
status=pending的任务 - 对每个任务,筛选出符合两个条件的用户:订阅了对应
targetTopic、当前时间在用户设置的接收时段内 - 收集这些用户的FCM令牌,调用FCM批量接口发送
- 更新任务
status为sent,或记录失败原因后设为failed
- 拉取所有
二、用户偏好数据结构设计
在users集合的用户文档中,新增两个核心字段:
subscribedTopics: 数组类型,存储用户订阅的主题标识(如["item_123_price", "item_456_expiry"])notificationSchedule: 对象类型,定义接收时段规则,示例:
其中{ "mode": "weekly", // 可选值:daily/weekly/custom "windows": [ {"dayOfWeek": 1, "start": "07:00", "end": "08:00"}, // 周一7-8点(dayOfWeek从1=周一到7=周日) {"dayOfWeek": null, "start": "22:00", "end": "23:59"} // 每天22点至午夜 ] }dayOfWeek=null表示该窗口适用于所有日期,custom模式可支持更复杂的规则(如仅工作日)。
三、高效匹配符合时段的用户
为避免定时任务全量扫描用户,做以下优化:
- 预计算用户可用时间窗口:当用户更新
notificationSchedule时,触发Cloud Functions,计算未来7天内所有可接收的时间区间,存入user_available_windows集合。每条文档包含:userId: 用户IDstartTimestamp: 窗口开始时间戳(UTC)endTimestamp: 窗口结束时间戳(UTC)
- 定时任务执行时,先获取当前UTC时间戳,查询
user_available_windows中startTimestamp <= 当前时间 <= endTimestamp的用户 - 再过滤这些用户的
subscribedTopics是否包含任务的targetTopic - 发送完成后,清理已过期的时间窗口文档
四、FCM发送优化
- 放弃主题发送,改用批量令牌发送:收集符合条件的用户FCM令牌,调用
admin.messaging().sendMulticast()接口,每次最多传500个令牌,超过则分批次处理。这种方式比主题发送更灵活,也避免了主题过多的维护负担 - 对发送失败的令牌(如设备卸载应用),及时从用户文档的
fcmTokens字段中移除,减少无效请求
五、客户端适配
- 用户订阅/退订Item字段时,直接更新
users文档的subscribedTopics数组,无需再手动订阅/退订FCM主题(因为现在发送逻辑不再依赖主题,仅用主题作为订阅标识) - 用户设置通知时段时,将规则提交到Firestore,触发后端的时间窗口预计算
六、异常与重试机制
- 给
pending_notifications任务添加重试次数限制,比如最多重试3次,超过则标记为failed并记录原因 - 用Cloud Logging记录发送日志,包括成功/失败数量、目标用户数等,方便排查问题
内容的提问来源于stack exchange,提问作者Triet Dao
相关产品推荐
相关产品推荐

