如何基于Firestore的到期日期在Flutter中实现提前15天推送通知?
到期前15天自动推送通知实现方案(Flutter + Firebase 技术栈)
你写的
if(exp_date - Date.now() == 15)判断逻辑无法落地:时间戳精度到毫秒级,不管是客户端还是服务端都不可能持续每毫秒扫描全量数据做相等判断,100%会漏触发。生产环境必须用时间区间匹配+已发送标记+服务端定时任务的方案实现,所有触发逻辑放在Cloud Functions侧,不要依赖客户端状态。
前置准备
- Flutter项目已集成
firebase_core、cloud_firestore、firebase_messaging依赖,完成Android/iOS端推送权限基础配置 - Firebase控制台已开通Cloud Firestore、Cloud Functions、Firebase Cloud Messaging(FCM)服务
- Firestore中存储到期信息的集合,单条文档必须包含3个核心字段:
exp_date:Firestore Timestamp类型,存储精确的到期时间,不要存字符串或本地时区时间device_tokens:数组类型,存储对应用户所有登录设备的FCM推送令牌notified_15d:布尔类型,默认值false,标记是否已发送过到期前15天的提醒,避免重复推送
核心逻辑:Cloud Functions 定时任务实现
不建议用文档写入触发器做判断——如果用户在到期前15天内没有修改过对应文档,写入触发器永远不会执行。直接用Pub/Sub定时触发器,每天固定扫描一次全量符合条件的文档即可,成本低、稳定性高。
步骤1:初始化Cloud Functions项目
本地安装Firebase CLI,初始化Functions目录,选择Node.js运行时,安装依赖:
npm install firebase-functions firebase-admin
步骤2:编写定时触发函数
代码直接写在functions/index.js中:
const functions = require('firebase-functions/v2'); const admin = require('firebase-admin'); admin.initializeApp(); const db = admin.firestore(); const messaging = admin.messaging(); // 每天凌晨2点执行一次任务,cron表达式按你所在时区调整 exports.send15DayExpireReminder = functions.scheduler.onSchedule( { schedule: '0 2 * * *', timeZone: 'Asia/Shanghai' }, async () => { const now = new Date(); // 计算15天后的时间区间:当天0点到次日0点 const targetStart = new Date(now.getTime() + 15 * 24 * 60 * 60 * 1000); targetStart.setHours(0, 0, 0, 0); const targetEnd = new Date(targetStart.getTime() + 24 * 60 * 60 * 1000); // 查询所有符合条件的文档:到期时间在目标区间内,且未发送过提醒 const querySnap = await db.collection('替换成你的到期记录集合名') .where('exp_date', '>=', targetStart) .where('exp_date', '<', targetEnd) .where('notified_15d', '==', false) .get(); const batch = db.batch(); const sendTasks = []; querySnap.forEach(doc => { const data = doc.data(); const tokens = data.device_tokens || []; if (tokens.length === 0) return; // 构造推送内容,可按业务需求自定义 const message = { notification: { title: '到期提醒', body: '您的订阅/服务还有15天到期,请及时处理' }, tokens: tokens }; // 加入发送队列 sendTasks.push( messaging.sendEachForMulticast(message) .then(res => { // 自动清理失效的设备token const invalidTokens = []; res.responses.forEach((resp, idx) => { if (!resp.success && resp.error.code === 'messaging/invalid-argument') { invalidTokens.push(tokens[idx]); } }); // 标记为已发送,同时移除失效token batch.update(doc.ref, { notified_15d: true, device_tokens: admin.firestore.FieldValue.arrayRemove(...invalidTokens) }); }) ); }); await Promise.all(sendTasks); // 批量提交所有文档更新 await batch.commit(); return null; } );
步骤3:部署函数
在Functions目录下执行部署命令:
firebase deploy --only functions:send15DayExpireReminder
部署完成后Firebase会自动按配置的cron规则执行任务,不需要额外维护服务器。
Flutter端配套配置
- 应用首次启动时申请通知权限:Android 13及以上版本需申请
POST_NOTIFICATIONS运行时权限,iOS端需调用requestPermission方法申请通知授权 - 初始化Firebase Messaging后,获取当前设备的FCM token,在用户登录后将token存入对应Firestore文档的
device_tokens数组中;用户退出登录时记得将对应token从数组中移除 - 配置Firebase Messaging的前台、后台、应用终止状态的消息监听回调,处理通知点击跳转至对应详情页的逻辑
- 客户端不需要做任何到期时间判断、定时触发逻辑,所有触发规则全在Cloud Functions侧实现,避免应用被杀、断网、权限被禁导致的漏发问题
常见坑点说明
- 不要尝试精确匹配“剩余15天整”的时间点:定时任务最小粒度为分钟级,加上网络延迟、函数执行延迟,精确相等判断完全不可用,按天做区间匹配完全满足业务需求
- 必须加
notified_15d标记位:避免定时任务重复执行时给用户重复发推送 - 所有时间统一用UTC时间戳/Firestore Timestamp存储,不要用客户端本地时间做计算,避免时区差导致触发时间错误
- 如果单集合数据量超过10万条,给
exp_date和notified_15d字段建复合索引,提升查询效率,避免函数执行超时
内容的提问来源于stack exchange,提问作者Ashish Shingade
相关产品推荐
相关产品推荐

