You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Firebase V2定时函数发送推送通知时因Socket断开连接失败求助

Firebase V2定时函数发送推送通知时因Socket断开连接失败求助

我看了你的代码,发现几个很可能导致Socket断开连接、推送发送失败的核心问题,下面给你逐一拆解和修复方案:


1. 最关键的问题:forEach循环里用async/await的陷阱

你在遍历查询结果时用了querySnapshot.forEach(async(doc:any) => { ... }),但forEach并不会等待每个异步操作完成。这会导致你的定时函数可能在所有推送发送、数据库更新都完成前就提前结束,Firebase会强制断开Socket连接,进而出现推送发送失败、操作中断的情况。

修复方案:改用for...of循环(或Promise.all控制并发)

用for...of遍历querySnapshot.docs,这样能确保每个异步操作都被正确等待,直到所有任务完成后函数才会终止。


2. 其他优化点,避免潜在的连接不稳定问题

除了上面的核心问题,还有几个细节可以优化,减少Socket连接压力:

  • 批量更新无效Token:如果有大量无效的推送Token,单个doc.update会多次建立数据库连接,换成WriteBatch批量更新能大幅减少连接开销
  • 简化字段检查逻辑,避免冗余的判断
  • 控制并发请求数:如果用户量很大,同时发送大量推送可能触发限流或连接不稳定,用Promise.all结合分块处理会更稳妥

完整修复后的代码示例

const { getFirestore, FieldPath, WriteBatch } = require('firebase-admin/firestore');
const { initializeApp } = require('firebase-admin/app');
const { logger, onSchedule } = require("firebase-functions/v2/scheduler");
const { getMessaging } = require("firebase-admin/messaging");
const { getAuth } = require('firebase-admin/auth');

initializeApp();
const db = getFirestore();
const messaging = getMessaging();
const auth = getAuth();

// 假设你已经实现了这个生成消息的函数
function genMissingDataMessage() {
  return "请完善你的个人资料数据,解锁完整仪表盘功能!";
}

exports.missingDataProgress = onSchedule(
  "every 24 hours",
  { timeoutSeconds: 540, retryConfig: { retryCount: 3 } }, // 配置超时和重试策略
  async (event) => {
    logger.log("Start of missingDataProgress");
    const dashboardReadyRef = new FieldPath("profile", "dashboardReady");
    const appColRef = db.collection("users");

    try {
      const querySnapshot = await appColRef.where(dashboardReadyRef, '==', false).get();
      
      // 用WriteBatch批量更新无效的pushToken,减少数据库连接
      const batch = db.batch();
      const sendPromises: Promise<void>[] = [];

      // 改用for...of循环,确保异步操作被正确等待
      for (const doc of querySnapshot.docs) {
        const data = doc.data();
        // 简化字段检查,可选链语法更简洁
        const pushToken = data?.profile?.pushToken;
        
        // 跳过没有有效推送Token的用户
        if (!pushToken || pushToken.trim() === '') continue;

        const pushPayload = {
          title: "Missing Data!",
          body: genMissingDataMessage(),
          image: "",
        };
        logger.info(`准备给用户 ${doc.id} 发送推送,内容:`, pushPayload);

        const message = {
          token: pushToken,
          notification: pushPayload,
        };

        // 把推送请求存入Promise数组,并行处理(如果用户量极大可以分块)
        const sendPromise = messaging.send(message)
          .then(() => {
            logger.info(`推送成功发送给用户 ${doc.id}`);
          })
          .catch((err) => {
            logger.error(`给用户 ${doc.id} 发送推送失败`, err);
            // 处理无效Token,加入批量更新队列
            if (err.code === 'messaging/invalid-registration-token' || err.code === 'messaging/registration-token-not-registered') {
              logger.error(`用户 ${doc.id} 的推送Token已失效,将清空`);
              batch.update(doc.ref, { "profile.pushToken": "" });
            }
          });

        sendPromises.push(sendPromise);
      }

      // 等待所有推送请求完成
      await Promise.all(sendPromises);
      // 执行批量更新操作
      await batch.commit();
      logger.log("所有推送和数据库更新操作已完成");

    } catch (err) {
      logger.error("missingDataProgress 函数执行失败", err);
      throw err; // 抛出错误触发重试(如果配置了重试策略)
    }
  }
);

额外建议,进一步避免Socket断开问题

  • 配置超时和重试:我在代码里加了timeoutSeconds: 540(最大支持9分钟)和retryConfig,如果函数因为超时或Socket断开失败,Firebase会自动重试
  • 分块处理大数量用户:如果你的用户量特别大,把sendPromises分成每50个一组,每组用Promise.all执行,避免并发过高导致的连接问题
  • 监控日志:通过Firebase控制台查看函数的执行日志,确认是否有超时、限流等提示,再针对性优化

内容来源于stack exchange

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.04.07 10:07:59