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

如何编写延迟5分钟发送消息的Firebase Cloud Functions?

问题描述

我正在使用PubSub和Scheduler编写Firebase Cloud Functions,希望在timelimitScheduler函数启动后5分钟再发布消息。但目前timelimitScheduler会立即发送消息,导致cancelReservation函数在10秒内就执行,预约被提前取消。

当前代码

timelimitScheduler 函数

exports.timelimitScheduler = functions.firestore// eslint-disable-line
  .document('Reservation/{documentId}')// eslint-disable-line
  .onCreate((snapshot, context) => {// eslint-disable-line
    const newReservation = snapshot.data();// eslint-disable-line
    const userId = newReservation.userId;// eslint-disable-line
    const shopId = newReservation.shopId;// eslint-disable-line

    const topicName = 'reservation-cancellation';// eslint-disable-line
    const reservationId = context.params.documentId;// eslint-disable-line
    const data = JSON.stringify({ reservationId });// eslint-disable-line
    const pubSubClient = new PubSub();// eslint-disable-line

    const delaySeconds = 300;// eslint-disable-line
    const publishTime = new Date(Date.now() + delaySeconds * 1000);// eslint-disable-line
    console.log('Scheduled publish time:', publishTime);// eslint-disable-line    
      const message = {// eslint-disable-line
        data: Buffer.from(data),// eslint-disable-line
    attributes: {// eslint-disable-line
                scheduledTime: publishTime.getTime().toString(),// eslint-disable-line
            },// eslint-disable-line
      };// eslint-disable-line

      return pubSubClient// eslint-disable-line
  .topic(topicName)// eslint-disable-line
  .publishMessage(message)// eslint-disable-line
  .then((messageId) => {// eslint-disable-line
    console.log(`Message ${messageId} published at ${publishTime}`);// eslint-disable-line
    return null;// eslint-disable-line
  })// eslint-disable-line
  .catch((error) => {// eslint-disable-line
    console.error(`Error publishing message: ${error}`);// eslint-disable-line
    throw error;// eslint-disable-line
  });// eslint-disable-line
  });// eslint-disable-line

cancelReservation 函数

exports.cancelReservation = functions.pubsub.topic('reservation-cancellation')// eslint-disable-line
    .onPublish((message) => {// eslint-disable-line
        const reservationId = message.json.reservationId;// eslint-disable-line
        const scheduledTime = parseInt(message.attributes.scheduledTime, 10);// eslint-disable-line
        const delaySeconds = Math.floor((Date.now() - scheduledTime) / 1000);// eslint-disable-line
        console.log(`Canceling reservation ${reservationId} (delayed by ${delaySeconds} seconds)`);// eslint-disable-line
        return admin.firestore().collection('Reservation').doc(reservationId).get()// eslint-disable-line
            .then((reservationDoc) => {// eslint-disable-line
                const reservation = reservationDoc.data();// eslint-disable-line
                if (reservation.state !== '예약접수') {// eslint-disable-line
                    return;// eslint-disable-line
                }// eslint-disable-line
                return admin.firestore().collection('Reservation').doc(reservationId).update({// eslint-disable-line
                    state: '예약취소',// eslint-disable-line
                });// eslint-disable-line
            })// eslint-disable-line
            .catch((error) => {// eslint-disable-line
                console.error(`Error canceling reservation: ${error}`);// eslint-disable-line
                throw error;// eslint-disable-line
            });// eslint-disable-line
    });// eslint-disable-line

问题原因

你的代码仅将计划发布时间存入消息属性,但直接调用了publishMessage方法——PubSub本身不支持延迟发布消息,因此消息会被立即发送,cancelReservation自然会马上触发。

解决方案

方案1:使用Cloud Scheduler创建定时PubSub任务

通过Cloud Scheduler API,在Firestore文档创建时生成一个5分钟后触发的定时任务,该任务会向指定PubSub主题发送消息,实现真正的延迟执行。

首先安装依赖:

npm install @google-cloud/scheduler

修改timelimitScheduler函数:

const { SchedulerClient } = require('@google-cloud/scheduler');
const schedulerClient = new SchedulerClient();

exports.timelimitScheduler = functions.firestore
  .document('Reservation/{documentId}')
  .onCreate(async (snapshot, context) => {
    const reservationId = context.params.documentId;
    const projectId = process.env.GCLOUD_PROJECT;
    const location = 'asia-northeast1'; // 替换为你的函数部署区域
    const topicName = 'reservation-cancellation';
    const jobName = schedulerClient.jobPath(projectId, location, `cancel-reservation-${reservationId}`);

    // 计算5分钟后的触发时间
    const scheduleTime = new Date(Date.now() + 300 * 1000);
    // 转换为Cloud Scheduler支持的Cron格式
    const cronSchedule = `${scheduleTime.getSeconds()} ${scheduleTime.getMinutes()} ${scheduleTime.getHours()} ${scheduleTime.getDate()} ${scheduleTime.getMonth() + 1} ? ${scheduleTime.getFullYear()}`;

    const job = {
      name: jobName,
      pubsubTarget: {
        topicName: `projects/${projectId}/topics/${topicName}`,
        data: Buffer.from(JSON.stringify({ reservationId })).toString('base64'),
      },
      schedule: cronSchedule,
      timeZone: 'Asia/Seoul', // 替换为你的时区
    };

    try {
      await schedulerClient.createJob({ parent: schedulerClient.locationPath(projectId, location), job });
      console.log(`定时任务已创建,将在${scheduleTime}触发取消预约`);
      return null;
    } catch (error) {
      console.error('创建定时任务失败:', error);
      throw error;
    }
  });

方案2:直接使用延迟执行逻辑(无需PubSub)

如果不需要PubSub中转,可以直接在timelimitScheduler中设置延迟执行取消逻辑,注意这种方式依赖函数实例存活时间,若实例提前被回收则任务失效:

exports.timelimitScheduler = functions.firestore
  .document('Reservation/{documentId}')
  .onCreate((snapshot, context) => {
    const reservationId = context.params.documentId;
    const delaySeconds = 300;

    setTimeout(async () => {
      try {
        const reservationDoc = await admin.firestore().collection('Reservation').doc(reservationId).get();
        const reservation = reservationDoc.data();
        if (reservation?.state === '예약접수') {
          await admin.firestore().collection('Reservation').doc(reservationId).update({ state: '예약취소' });
          console.log(`预约${reservationId}已取消`);
        }
      } catch (error) {
        console.error('取消预约失败:', error);
      }
    }, delaySeconds * 1000);

    console.log(`已预约5分钟后取消预约${reservationId}`);
    return null;
  });

临时优化方案(不推荐)

如果要保留原有PubSub逻辑,可以在消费端过滤未到时间的消息,但这不是真正的延迟,只是避免提前执行:

exports.cancelReservation = functions.pubsub.topic('reservation-cancellation')
  .onPublish(async (message) => {
    const reservationId = message.json.reservationId;
    const scheduledTime = parseInt(message.attributes.scheduledTime, 10);
    const now = Date.now();

    if (now < scheduledTime) {
      console.log(`未到计划时间,延迟至${new Date(scheduledTime)}执行`);
      return null;
    }

    // 原有的取消逻辑
    const reservationDoc = await admin.firestore().collection('Reservation').doc(reservationId).get();
    const reservation = reservationDoc.data();
    if (reservation?.state === '예약접수') {
      await admin.firestore().collection('Reservation').doc(reservationId).update({ state: '예약취소' });
      console.log(`预约${reservationId}已取消`);
    }
    return null;
  });

内容的提问来源于stack exchange,提问作者안은노

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.31 20:25:17