如何在Flutter中通过Firebase Functions实现一对一推送通知
修正Firebase云函数实现一对一用户推送通知
需求:实现一对一用户推送通知,但当前代码会推送给所有用户。
Firestore数据结构:connect(集合)→userID(文档)→userconnect(集合)→PetID(文档)→message(集合)→messageID(文档)
其中message文档的uid字段为接收者UID,email字段为发送者邮箱。
原代码:
const functions = require('firebase-functions'); const admin = require('firebase-admin'); admin.initializeApp(); exports.notifyUserOnMessage = functions.firestore.document('connect/{userId}/userconnect/{petId}/message/{messageId}') .onCreate(async (snapshot, context) => { const messageData = snapshot.data(); // Extract the userId and petId from the context const targetUserId = context.params.userId; const petId = context.params.petId; // Fetch the userconnect document to get the userid field const userConnectDoc = await admin.firestore().doc(`connect/${targetUserId}/userconnect/${petId}`).get(); const userConnectData = userConnectDoc.data(); let recipientUserId; if (messageData.uid === userConnectData.userid) { recipientUserId = targetUserId; } else { recipientUserId = userConnectData.userid; } const payload = { notification: { title: 'You Have New Message', body: 'New Message Arrive.', clickAction: 'FLUTTER_NOTIFICATION_CLICK', }, data: { click_action: 'FLUTTER_NOTIFICATION_CLICK', // for background } }; return admin.messaging().sendToTopic(recipientUserId, payload); });
关键问题分析
当前代码使用sendToTopic(recipientUserId)进行推送,这是主题推送逻辑——所有订阅了以recipientUserId命名主题的设备都会收到通知,而非定向到单个用户。要实现一对一推送,需要使用FCM设备令牌进行精准推送,前提是你的Firestore中已存储用户的FCM令牌(建议在用户文档下添加fcmTokens数组字段,存储该用户所有设备的令牌)。
修改后的代码
const functions = require('firebase-functions'); const admin = require('firebase-admin'); admin.initializeApp(); exports.notifyUserOnMessage = functions.firestore.document('connect/{userId}/userconnect/{petId}/message/{messageId}') .onCreate(async (snapshot, context) => { const messageData = snapshot.data(); const targetUserId = context.params.userId; const petId = context.params.petId; // 获取userconnect文档数据 const userConnectDoc = await admin.firestore().doc(`connect/${targetUserId}/userconnect/${petId}`).get(); const userConnectData = userConnectDoc.data(); // 确定接收者UID let recipientUserId; if (messageData.uid === userConnectData.userid) { recipientUserId = targetUserId; } else { recipientUserId = userConnectData.userid; } // 获取接收者的FCM令牌(假设用户令牌存储在connect集合下的userID文档的fcmTokens字段中) const recipientUserDoc = await admin.firestore().doc(`connect/${recipientUserId}`).get(); const recipientUserData = recipientUserDoc.data(); // 检查令牌是否存在 if (!recipientUserData || !recipientUserData.fcmTokens || recipientUserData.fcmTokens.length === 0) { console.log(`用户 ${recipientUserId} 未存储FCM令牌,无法推送通知`); return null; } const payload = { notification: { title: '你有新消息', body: '收到一条新消息', clickAction: 'FLUTTER_NOTIFICATION_CLICK', }, data: { click_action: 'FLUTTER_NOTIFICATION_CLICK', } }; // 使用sendToDevice推送给指定用户的所有设备 return admin.messaging().sendToDevice(recipientUserData.fcmTokens, payload) .then(response => { // 清理无效令牌 const invalidTokens = []; response.results.forEach((result, index) => { const error = result.error; if (error) { console.error(`推送令牌 ${recipientUserData.fcmTokens[index]} 失败:`, error); if (error.code === 'messaging/invalid-registration-token' || error.code === 'messaging/registration-token-not-registered') { invalidTokens.push(recipientUserData.fcmTokens[index]); } } }); // 如果有无效令牌,从Firestore中移除 if (invalidTokens.length > 0) { return admin.firestore().doc(`connect/${recipientUserId}`).update({ fcmTokens: admin.firestore.FieldValue.arrayRemove(...invalidTokens) }); } return null; }) .catch(error => { console.error('推送通知失败:', error); }); });
核心改动说明
- 替换
sendToTopic为sendToDevice:该方法接收设备令牌数组,仅向这些设备推送通知,实现一对一精准推送 - 新增FCM令牌获取逻辑:从用户文档中读取存储的令牌数组,确保能定位到目标用户的设备
- 添加无效令牌处理:自动清理无法接收通知的无效令牌,避免后续推送报错
- 优化通知内容:将英文提示改为中文(可根据需求调整)
内容的提问来源于stack exchange,提问作者wiwat chong
相关产品推荐
相关产品推荐

