Google云函数自定义通知异常及Firestore每日数据处理方案咨询
核心问题根因
你遇到的通知偶尔触发的问题和配额无关,完全是代码的异步处理逻辑错误导致的:
- 你在
collections.forEach、snapshot.forEach里传入了async回调,但普通的forEach不会等待异步回调执行完成,外层的await collections.forEach是无效操作(forEach返回undefined,await对它没有任何作用) - 云函数的执行上下文在主线程同步代码执行完成后就会被回收,你代码里大量未被等待的异步操作(Firestore读写、FCM通知发送)还没执行完就被中断了,所以才会出现偶尔能触发通知、大部分时候没反应的情况
- 你当前只有87份文档,单日全量扫描的读操作不到100次,远低于Firestore每日5万次免费读配额,云函数定时调用每日1次也远低于免费额度,完全可以在免费配额内实现需求。
代码修复方案
把所有forEach迭代替换为Promise.all+数组map的形式,确保所有异步操作都被正确等待,同时优化查询逻辑减少不必要的读操作:
import * as admin from "firebase-admin"; import * as functions from "firebase-functions"; admin.initializeApp(); const db = admin.firestore(); const fcm = admin.messaging(); export const expiryDateChecker = functions.pubsub.schedule("0 06 * * *") .timeZone("Europe/London").onRun(async () => { const collections = await db.listCollections(); const today = new Date(); today.setHours(0, 0, 0, 0); const tomorrow = new Date(today); tomorrow.setDate(tomorrow.getDate() + 1); const afterTomorrow = new Date(tomorrow); afterTomorrow.setDate(afterTomorrow.getDate() + 1); const options = { priority: "high", timeToLive: 60 * 60 * 24, }; // 用Promise.all等待所有集合处理完成 await Promise.all(collections.map(async (collection) => { // 可选优化:直接加查询过滤,只查3天内到期/已过期的文档,减少读次数 // const snapshot = await collection.where('ExpiryDate', '<', afterTomorrow).get(); const snapshot = await collection.get(); let amountToday = 0; let amountTomorrow = 0; let amountExpired = 0; // 等待所有文档处理完成 await Promise.all(snapshot.docs.map(async (doc) => { const expDate = new Date(doc.data().ExpiryDate); const diffInTime = expDate.getTime() - today.getTime(); const diffInDays = Math.floor(diffInTime / (1000 * 3600 * 24)); if (diffInDays === 0) { amountToday++; } else if (diffInDays === 1) { amountTomorrow++; } else if (diffInDays < 0) { amountExpired++; try { const counter = parseInt(doc.data().Quantity.toString()); const expiryDocRef = db.collection("expiryGroups").doc("Users") .collection(collection.id).doc(doc.id); const doc2 = await expiryDocRef.get(); if (doc2.exists) { // 所有写操作加await确保执行完成 await expiryDocRef.update({Expired: true}); const check = parseInt(doc2.data()?.expiryCount?.toString() || "0"); if (check === 0) { await expiryDocRef.update({ expiryCount: admin.firestore.FieldValue.increment(counter) }); } } } catch (e) { console.log(e); } } })); // 所有FCM发送加await,补全通知内容配置 if (amountToday > 0) { const message = { notification: { title: "食品到期提醒", body: `${amountToday}件食品今日到期` } }; await fcm.sendToTopic(collection.id, message, options); } if (amountTomorrow > 0) { const message = { notification: { title: "食品即将到期提醒", body: `${amountTomorrow}件食品明日到期` } }; await fcm.sendToTopic(collection.id, message, options); } if (amountExpired > 0) { const message = { notification: { title: "食品已过期提醒", body: `${amountExpired}件食品已过期` } }; await fcm.sendToTopic(collection.id, message, options); } })); return null; });
可选优化(进一步降低配额消耗)
如果后续数据量上涨,可以做以下优化,进一步降低资源消耗:
- 给所有食品集合的
ExpiryDate字段加索引,直接通过Firestore查询过滤出3天内到期/已过期的文档,不需要全表扫描所有文档,读操作次数会大幅降低 - 避免调用
listCollections,可以把所有用户的食品数据都放到同一个根集合下,用用户ID当文档的字段,单次查询就能拿到所有需要处理的数据,进一步减少调用次数
内容的提问来源于stack exchange,提问作者TheDude Alex Nuzum
相关产品推荐
相关产品推荐

