如何基于Firebase数据库用户设定日期实现提前一天锻炼推送通知
实现Firebase健身训练提前一天推送通知方案
核心思路
通过Firebase的Cloud Functions结合Cloud Messaging(FCM),配合Firestore/Realtime Database存储的训练数据,实现定时触发通知推送。以下是两种可行的实现方案:
方案一:每日定时扫描数据库
适合批量处理所有用户的训练记录,每天固定时间(如凌晨1点)自动检查次日有训练的用户并推送通知。
步骤1:数据库结构设计
在Firestore中创建trainings集合,每条文档存储用户训练信息:
// Firestore文档示例 { userId: "user_xxx", trainingDatetime: Timestamp.fromDate(new Date("2024-05-20T09:00:00")), // 训练时间 fcmToken: "用户设备的FCM推送令牌", notificationSent: false // 标记是否已发送通知,避免重复推送 }
步骤2:编写Cloud Functions定时触发器
使用Node.js编写云函数,每日定时扫描符合条件的训练记录:
const functions = require("firebase-functions"); const admin = require("firebase-admin"); admin.initializeApp(); // 每日凌晨1点(北京时间)触发扫描 exports.checkUpcomingTrainings = functions.pubsub.schedule('0 1 * * *') .timeZone('Asia/Shanghai') .onRun(async (context) => { const now = new Date(); const tomorrowStart = new Date(now.getFullYear(), now.getMonth(), now.getDate() + 1); const tomorrowEnd = new Date(tomorrowStart.getTime() + 24 * 60 * 60 * 1000); // 查询次日训练且未发送通知的记录 const querySnapshot = await admin.firestore() .collection('trainings') .where('trainingDatetime', '>=', tomorrowStart) .where('trainingDatetime', '<', tomorrowEnd) .where('notificationSent', '==', false) .get(); const taskPromises = []; querySnapshot.forEach(doc => { const data = doc.data(); // 构造FCM通知内容 const message = { notification: { title: "健身训练提醒", body: `你明天有训练安排,时间:${new Date(data.trainingDatetime.toDate()).toLocaleString()},别忘记哦!` }, token: data.fcmToken }; // 发送通知并标记已发送 const sendTask = admin.messaging().send(message) .then(() => doc.ref.update({ notificationSent: true })) .catch(err => functions.logger.error("通知发送失败:", err)); taskPromises.push(sendTask); }); return Promise.all(taskPromises); });
方案二:创建训练时设置延迟触发
当用户创建/更新训练时间时,直接计算提前24小时的时间点,设置延迟执行的云函数,到点自动推送通知。
步骤1:编写触发型云函数
包含创建训练时的延迟任务、更新训练时的重新调度逻辑:
const functions = require("firebase-functions"); const admin = require("firebase-admin"); admin.initializeApp(); // 训练记录创建时,调度通知任务 exports.scheduleTrainingNotification = functions.firestore .document('trainings/{trainingId}') .onCreate(async (snap, context) => { const data = snap.data(); const trainingTime = data.trainingDatetime.toDate(); const now = new Date(); const delayMs = trainingTime.getTime() - now.getTime() - 24 * 60 * 60 * 1000; // 提前24小时的延迟时间 if (delayMs <= 0) { // 训练时间不足24小时,立即发送通知 return sendNotification(snap.ref, data); } // 设置延迟执行任务 return setTimeout(async () => { await sendNotification(snap.ref, data); }, delayMs); }); // 训练记录更新时,重新调度通知任务 exports.updateTrainingNotification = functions.firestore .document('trainings/{trainingId}') .onUpdate(async (change, context) => { const newData = change.after.data(); const oldData = change.before.data(); // 仅当训练时间或FCM令牌变化时重新调度 if (newData.trainingDatetime.toDate().getTime() !== oldData.trainingDatetime.toDate().getTime() || newData.fcmToken !== oldData.fcmToken) { const trainingTime = newData.trainingDatetime.toDate(); const now = new Date(); const delayMs = trainingTime.getTime() - now.getTime() - 24 * 60 * 60 * 1000; if (delayMs <= 0) { return sendNotification(change.after.ref, newData); } return setTimeout(async () => { await sendNotification(change.after.ref, newData); }, delayMs); } return null; }); // 通用通知发送函数 async function sendNotification(docRef, data) { const message = { notification: { title: "健身训练提醒", body: `你明天有训练安排,时间:${new Date(data.trainingDatetime.toDate()).toLocaleString()},别忘记哦!` }, token: data.fcmToken }; try { await admin.messaging().send(message); await docRef.update({ notificationSent: true }); functions.logger.log("通知发送成功"); } catch (err) { functions.logger.error("通知发送失败:", err); throw err; } }
关键注意事项
- 时区处理:确保训练时间和云函数触发时间的时区一致,避免时差导致的推送错误。
- FCM令牌管理:定期清理失效的FCM令牌(可通过FCM返回的错误码判断),避免无效推送。
- 重复推送防护:通过
notificationSent字段标记已发送的通知,防止重复触发。 - 错误日志:利用Cloud Functions的日志功能记录推送失败信息,方便排查问题。
内容的提问来源于stack exchange,提问作者Alexandr
相关产品推荐
相关产品推荐

