如何在React+Firebase中于指定时间向用户列表发送推送通知?
事件触发式推送通知实现方案
核心逻辑
要完成事件发生时给报名用户发送推送,需要结合Firebase的Firestore、Cloud Functions、Firebase Cloud Messaging(FCM)三个核心服务,同时要确保用户UID与FCM推送令牌关联存储。
具体实现步骤
1. 关联用户UID与FCM令牌
前端需要在用户登录后,获取设备的FCM令牌并存储到Firestore的users集合中(文档ID用用户UID):
// React前端代码:获取并存储FCM令牌 import { getMessaging, getToken } from "firebase/messaging"; import { doc, setDoc } from "firebase/firestore"; import { auth } from "./firebaseConfig"; const saveFCMToken = async () => { const messaging = getMessaging(); try { const token = await getToken(messaging, { vapidKey: "你的VAPID密钥" }); if (token && auth.currentUser) { await setDoc( doc(db, "users", auth.currentUser.uid), { fcmToken: token }, { merge: true } ); } } catch (error) { console.error("FCM令牌获取失败:", error); } }; // 用户登录成功后调用该函数
2. 用Cloud Functions定时扫描并发送通知
通过Cloud Functions的定时触发器,定期扫描即将发生的事件,获取报名用户的FCM令牌并批量发送推送:
// Cloud Functions代码 const functions = require("firebase-functions"); const admin = require("firebase-admin"); admin.initializeApp(); // 每5分钟扫描一次即将发生的事件(可根据需求调整频率) exports.sendEventNotifications = functions.pubsub.schedule("every 5 minutes").onRun(async (context) => { const now = new Date(); // 筛选接下来5分钟内要发生的事件 const eventsSnapshot = await admin.firestore() .collection("events") .where("datetime", ">=", now) .where("datetime", "<=", new Date(now.getTime() + 5 * 60 * 1000)) .where("notificationSent", "!=", true) // 跳过已发送通知的事件 .get(); if (eventsSnapshot.empty) { functions.logger.info("无即将发生的事件"); return null; } // 遍历事件处理推送 for (const doc of eventsSnapshot.docs) { const { registeredUsers, title, datetime } = doc.data(); const userTokens = []; // 收集报名用户的有效FCM令牌 for (const uid of registeredUsers) { const userDoc = await admin.firestore().collection("users").doc(uid).get(); if (userDoc.exists && userDoc.data().fcmToken) { userTokens.push(userDoc.data().fcmToken); } } if (userTokens.length === 0) { functions.logger.info(`事件${doc.id}无有效推送令牌`); continue; } // 构建推送消息 const message = { notification: { title: `事件提醒:${title}`, body: `你报名的「${title}」即将开始,时间:${datetime.toLocaleString()}` }, tokens: userTokens }; // 发送批量推送 try { const response = await admin.messaging().sendMulticast(message); functions.logger.info(`推送完成:成功${response.successCount}条,失败${response.failureCount}条`); // 标记事件已发送通知,避免重复推送 await doc.ref.update({ notificationSent: true }); } catch (error) { functions.logger.error(`推送失败:${error}`); } } return null; });
3. 前端配置消息接收
在public目录下创建firebase-messaging-sw.js,处理后台消息接收:
// firebase-messaging-sw.js importScripts("https://www.gstatic.com/firebasejs/9.23.0/firebase-app-compat.js"); importScripts("https://www.gstatic.com/firebasejs/9.23.0/firebase-messaging-compat.js"); firebase.initializeApp({ apiKey: "你的API密钥", authDomain: "你的Auth域名", projectId: "你的项目ID", storageBucket: "你的存储桶", messagingSenderId: "你的消息发送ID", appId: "你的AppID" }); const messaging = firebase.messaging(); messaging.onBackgroundMessage((payload) => { const notificationTitle = payload.notification.title; const notificationOptions = { body: payload.notification.body, icon: "/你的应用图标路径" }; self.registration.showNotification(notificationTitle, notificationOptions); });
关键注意事项
- 权限申请:前端需先调用
Notification.requestPermission()获取用户的通知权限,才能正常获取FCM令牌。 - 令牌更新:监听FCM令牌变化事件,及时更新Firestore中的令牌存储,避免推送失败。
- 无效令牌清理:推送失败时,可将无效令牌从
users集合中移除,减少后续无效请求。
内容的提问来源于stack exchange,提问作者Hudson Etkin
相关产品推荐
相关产品推荐

