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

如何用Cloud Function监听指定节点新增子节点并推送通知?

Firebase实时数据库Cloud Function通知推送问题排查与实现

问题描述

  • 需求确认:能否通过Cloud Function监听Firebase实时数据库Emergencies节点的子节点新增事件,并向Registered Admins下的所有用户推送通知?
  • 数据库结构:
    • 根节点包含Emergencies(存储紧急求助记录)和Registered Admins(存储管理员用户信息)
    • Registered Admins下每个用户节点包含Notification(存储通知消息)和Token(存储设备推送令牌)
  • 当前问题:已部署Cloud Function,但无法发送任何通知,请求排查。

用户提供的代码:

const functions = require("firebase-functions");
const admin = require("firebase-admin");
admin.initializeApp();

exports.listen = functions.database.ref("/Emergencies")
.onWrite(async (change, context) => {
  change.after.val();
  context.params.pushId;

  // Get the list of device notification tokens.
  const getDeviceTokensPromise = admin.database()
      .ref("/Registered Admins/{uid}/Token").once("value");

  // The snapshot to the user's tokens.
  let tokensSnapshot;

  // The array containing all the user's tokens.
  let tokens;

  const results = await Promise.all([getDeviceTokensPromise]);
  tokensSnapshot = results[0];
  
  // Check if there are any device tokens.
  if (!tokensSnapshot.hasChildren()) {
    return functions.logger.log(
      'There are no notification tokens to send to.'
    );
  }
  functions.logger.log(
    'There are',
    tokensSnapshot.numChildren(),
    'tokens to send notifications to.'
  );

  // Notification details.
  const payload = {
  notification: {
      title: "New Emergency Request!",
      body: "Someone needs help check Emergenie App now!",
    }
  }; 

  // Listing all tokens as an array.
  tokens = Object.keys(tokensSnapshot.val());
  // Send notifications to all tokens.
  const response = await admin.messaging().sendToDevice(tokens, payload);
  // For each message check if there was an error.
  const tokensToRemove = [];
  response.results.forEach((result, index) => {
    const error = result.error;
    if (error) {
      functions.logger.error(
        'Failure sending notification to',
        tokens[index],
        error
      );
      // Cleanup the tokens who are not registered anymore.
      if (error.code === 'messaging/invalid-registration-token' ||
          error.code === 'messaging/registration-token-not-registered') {
        tokensToRemove.push(tokensSnapshot.ref.child(tokens[index]).remove());
      }
    }
  });
  return Promise.all(tokensToRemove);
});

问题排查与修复

你的代码存在3个核心问题,导致通知无法正常发送:

1. 监听事件选择错误

你使用onWrite监听/Emergencies根节点,这会在节点**任何写入操作(新增、修改、删除)**时触发,而你只需要监听子节点新增事件。应改用onCreate监听/Emergencies/{pushId},仅在新增紧急求助记录时触发函数,避免无效执行。

2. 令牌读取路径错误

代码中/Registered Admins/{uid}/Token的{uid}是占位符写法,实际会尝试读取名为{uid}的固定节点,而非遍历所有管理员的令牌。正确做法是读取整个Registered Admins节点,再逐个提取每个用户的Token值。

3. 令牌提取逻辑错误

原代码用Object.keys(tokensSnapshot.val())获取的是管理员ID列表,而非令牌值。需要遍历每个管理员节点,提取其Token字段的实际内容。

修正后的完整代码

const functions = require("firebase-functions");
const admin = require("firebase-admin");
admin.initializeApp();

exports.sendEmergencyNotification = functions.database.ref("/Emergencies/{pushId}")
.onCreate(async (snapshot, context) => {
  // 读取所有管理员用户数据
  const adminsSnapshot = await admin.database().ref("/Registered Admins").once("value");
  
  if (!adminsSnapshot.exists()) {
    functions.logger.log("未找到任何管理员用户");
    return null;
  }

  // 提取所有有效令牌
  const tokens = [];
  adminsSnapshot.forEach(adminNode => {
    const token = adminNode.child("Token").val();
    if (token) {
      tokens.push(token);
    }
  });

  if (tokens.length === 0) {
    functions.logger.log("没有可用的设备推送令牌");
    return null;
  }

  functions.logger.log("准备向", tokens.length, "个设备发送通知");

  // 通知内容配置
  const payload = {
    notification: {
      title: "新的紧急求助请求!",
      body: "有人需要帮助,请立即查看Emergenie应用!"
    }
  };

  // 批量发送通知
  const response = await admin.messaging().sendToDevice(tokens, payload);

  // 清理无效令牌
  const cleanupTasks = [];
  response.results.forEach((result, index) => {
    const error = result.error;
    if (error) {
      functions.logger.error("通知发送失败,令牌:", tokens[index], "错误信息:", error);
      // 移除已失效的令牌
      if (error.code === "messaging/invalid-registration-token" ||
          error.code === "messaging/registration-token-not-registered") {
        adminsSnapshot.forEach(adminNode => {
          if (adminNode.child("Token").val() === tokens[index]) {
            cleanupTasks.push(adminNode.child("Token").remove());
          }
        });
      }
    }
  });

  return Promise.all(cleanupTasks);
});

额外注意事项

  • 确保Cloud Function拥有firebase.messaging.send权限,以及实时数据库的读写权限(默认部署的函数已具备基础权限)。
  • 测试时可通过Firebase控制台手动向Emergencies添加子节点,然后查看函数日志(Firebase控制台->函数->日志)排查错误。
  • 确认设备令牌有效,且对应设备已正确订阅Firebase消息推送服务。

内容的提问来源于stack exchange,提问作者Timow

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.10 15:50:20