Firebase云函数实现FCM向符合条件用户推送通知代码异常排查
故障原因
- 异步处理逻辑错误:查询用户子集合
notificationTokens时未添加await关键字,且forEach遍历不会等待内部异步请求执行完成,最终得到的tokens变量为未完成的Promise或空值 - 子集合查询结果处理错误:调用子集合的
get()方法返回的是集合查询快照(可能包含多个token文档),并非单个文档对象,无法直接调用.data()方法获取字段 - 返回值无法被正确收集:
forEach方法的返回值不会被汇总为数组,无法得到所有token的集合
修正后代码
exports.sendToDevice5 = functions.firestore .document('Articles/{paper}') .onCreate(async (snapshot, context) => { const item = snapshot.data(); // 只处理放射科相关子专业的文章 if (item.subspecialty !== "RadMSK" && item.subspecialty !== "RadMS") { return null; } // 1. 查询所有专业为放射科的用户 const radiologyUsersSnapshot = await admin.firestore().collection('users').where("specialty", "==", "RADIOLOGY").get(); if (radiologyUsersSnapshot.empty) { return null; } // 2. 批量查询所有用户的通知token const tokenPromises = []; radiologyUsersSnapshot.forEach(userDoc => { tokenPromises.push( admin.firestore().collection('users').doc(userDoc.id).collection('notificationTokens').get() ); }); const tokenSnapshots = await Promise.all(tokenPromises); // 3. 收集所有有效token const tokens = []; tokenSnapshots.forEach(tokenSnapshot => { tokenSnapshot.forEach(tokenDoc => { if (tokenDoc.data().token) { tokens.push(tokenDoc.data().token); } }); }); if (tokens.length === 0) { return null; } // 4. 构造推送 payload const payload = { notification: { title: `${item.title}!`, body: `New Journal`, sound: "default", }, data: {click_action: 'FLUTTER_NOTIFICATION_CLICK'}, }; // 5. 推送通知,可选添加结果处理逻辑,删除无效token const response = await admin.messaging().sendToDevice(tokens, payload); // 以下为可选的无效token清理逻辑 const invalidTokens = []; response.results.forEach((result, index) => { const error = result.error; if (error) { if (error.code === 'messaging/invalid-registration-token' || error.code === 'messaging/registration-token-not-registered') { invalidTokens.push(tokens[index]); } } }); // 这里可以添加代码删除数据库中存储的无效token return response; });
补充说明
- 代码新增了空结果判断,避免无匹配用户时出现空指针错误
- 可选添加的无效token清理逻辑可以减少后续无效推送的请求,降低FCM接口报错概率
- 如果推送用户量超过1000,建议做分批推送,避免触发FCM接口限制
内容的提问来源于stack exchange,提问作者Kaveshan Naidoo
相关产品推荐
相关产品推荐

