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

Firebase Cloud Functions实现定时单次FCM推送通知问题求助

解决方案:实现土耳其时区提醒的精准推送与去重

核心思路

要解决你的问题,关键要搞定时区统一计算、时间达标判断和推送状态标记三个环节,以下是具体实现步骤和代码示例:


1. 统一时区处理

云函数默认使用UTC时区,而你的用户是UTC+3(土耳其)时区,所以需要将当前时间转换为土耳其时区后,再和用户设置的startat时间对比:

  • 计算UTC到土耳其时区的毫秒偏移量:3 * 60 * 60 * 1000
  • 将当前UTC时间加上偏移量,得到土耳其本地时间

2. 时间达标判断

因为调度函数每分钟运行一次,我们只处理当前土耳其时间与提醒时间差在0~60秒内的记录,避免因调度延迟导致重复触发。

3. 标记已推送避免重复

在实时数据库的提醒记录中新增notified字段,初始为null或不设置;推送成功后将其设为true,后续调度函数只处理未标记的记录。


完整代码示例

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

// 每分钟运行的调度函数
exports.checkReminders = functions.pubsub.schedule("every 1 minutes").onRun(async (context) => {
  const db = admin.database();
  // 读取所有未推送的提醒记录
  const remindersSnapshot = await db.ref("reminders")
    .orderByChild("notified")
    .equalTo(null)
    .once("value");

  if (!remindersSnapshot.exists()) {
    functions.logger.info("无待处理提醒");
    return null;
  }

  // 计算当前土耳其时区时间
  const nowUTC = new Date();
  const turkeyOffsetMs = 3 * 60 * 60 * 1000; // UTC+3 毫秒偏移量
  const nowTurkey = new Date(nowUTC.getTime() + turkeyOffsetMs);

  const updateBatch = {};

  // 遍历待处理提醒
  await Promise.all(remindersSnapshot.forEach(async (childSnap) => {
    const reminder = childSnap.val();
    const reminderId = childSnap.key;
    const remindTimeTurkey = new Date(reminder.startat); // 假设startat是土耳其时间的ISO字符串/时间戳

    // 判断是否到达提醒时间(误差在1分钟内)
    const timeDiffMs = nowTurkey.getTime() - remindTimeTurkey.getTime();
    if (timeDiffMs >= 0 && timeDiffMs < 60 * 1000) {
      // 构造FCM推送消息
      const fcmMessage = {
        notification: {
          title: "你的提醒到了",
          body: reminder.content || "预设的提醒内容"
        },
        token: reminder.fcmToken // 需确保每条记录存储用户的FCM Token
      };

      try {
        // 推送FCM通知
        await admin.messaging().send(fcmMessage);
        functions.logger.info(`提醒推送成功:${reminderId}`);
        // 标记为已推送
        updateBatch[`/reminders/${reminderId}/notified`] = true;
      } catch (err) {
        functions.logger.error(`推送失败 ${reminderId}:${err.message}`);
        // 可选:添加重试次数限制,避免无限重试
        // updateBatch[`/reminders/${reminderId}/retryCount`] = (reminder.retryCount || 0) + 1;
      }
    }
  }));

  // 批量更新数据库状态,减少IO操作
  if (Object.keys(updateBatch).length > 0) {
    await db.ref().update(updateBatch);
  }

  return null;
});

注意事项

  • 确保用户存储的startat格式正确(推荐ISO字符串如"2024-05-20T15:30:00"或时间戳),避免Date解析错误。
  • 如果可以,建议用户存储UTC时间戳而非本地时间,这样无需时区转换,直接用nowUTC.getTime()对比即可,更稳妥。
  • 若需处理推送失败的情况,可添加retryCount字段,当重试次数超过阈值(如3次)时,标记为推送失败不再处理。

内容的提问来源于stack exchange,提问作者Berkay Isıkoglu

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.22 19:42:17