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

Pub/sub emulator与functions-framework本地联调报资源不存在错误

问题根因
  • 核心原因是Node.js版Pub/Sub客户端默认连接谷歌云公网Pub/Sub服务,没有连上你本地启动的模拟器。你用curl调用接口能成功,是因为手动指定了localhost:8043的模拟器地址,但代码初始化Pub/Sub客户端时没有配置模拟器连接参数,也没有读取对应环境变量,所以请求实际发到了公网的GCP服务,公网项目下不存在你本地创建的reports主题,直接返回5 NOT_FOUND错误。
  • 你写的发布函数代码本身存在3个逻辑bug,就算连通模拟器也无法正常运行:
    • publishAllMessages方法中给map传入的回调没有加async关键字,直接在内部写await会触发语法错误;同时map返回的是Promise数组,没有用Promise.all等待所有异步发布任务执行完成,函数会直接返回空数组
    • sendReports方法判断发布结果时用的变量名是publishReports,但实际接收返回值的变量是publishMessages,变量名不匹配会直接抛出引用错误
    • publishMessage接收的参数是已经被序列化为JSON字符串的报告内容,代码里却读取data.title属性,字符串不存在title字段,会导致日志打印内容全为undefined
  • 之前创建的推送订阅配置错误,填写的pushEndpoint是Pub/Sub主题的REST路径,不是本地消费函数的监听地址,就算消息发布成功也无法推送到消费端。
本地联调配置步骤

1. 配置Pub/Sub客户端连接本地模拟器

启动Pub/Sub模拟器之后,在运行本地函数的终端窗口执行环境变量注入命令,客户端会自动识别该变量将请求转发到本地模拟器,不再连接公网服务:

$(gcloud beta emulators pubsub env-init --project=abc)

如果使用Windows PowerShell,执行上述命令后按照终端输出的提示手动设置环境变量即可。
注意:该环境变量仅对当前终端窗口生效,每次新开终端启动本地函数都需要重新执行,也可以将PUBSUB_EMULATOR_HOST=localhost:8043、GOOGLE_CLOUD_PROJECT=abc两个变量写到本地.env文件中,启动函数时自动加载。

2. 修正代码中的逻辑错误

补全缺失的常量定义,修正异步写法、变量名和参数传递的问题,修正后的参考代码如下:

const { PubSub } = require('@google-cloud/pubsub');
// 显式指定项目ID,避免客户端从本地gcloud配置中读取线上项目ID导致寻址错误
const pubSubClient = new PubSub({ projectId: 'abc' });
const TOPIC = 'reports'; // 补全缺失的主题名常量

exports.sendReports = async (req, res) => {
  const reports = await getReports(); // 从数据库获取报告数据
  const publishMessages = await publishAllMessages(reports); // 逐条发布报告到主题
  // 修正变量名,和上方接收返回值的变量保持一致
  if (publishMessages.length) {
    console.log('reports published');
    res.status(200).send();
  } else {
    console.error(new Error('reports was not published'));
    res.status(400).send();
  }
};

async function publishAllMessages(reports) {
  try {
    // 给map回调加async关键字,用Promise.all等待所有异步发布任务执行完成
    const results = await Promise.all(reports.map(async (report) => {
      const publishResult = await publishMessage(report);
      console.log(`${report.title} published = ${publishResult.published}`);
      return publishResult.published ? report : null;
    }));
    // 过滤发布失败的空值,只返回发布成功的报告
    return results.filter(Boolean);
  } catch (err) {
    console.error(err);
    return [];
  }
}

async function publishMessage(report) {
  try {
    // 调整序列化逻辑,直接传入报告对象,在方法内部做JSON序列化,避免读取不到title属性
    const dataBuffer = Buffer.from(JSON.stringify(report));
    const messageId = await pubSubClient.topic(TOPIC).publish(dataBuffer);
    console.log(`Message ${messageId} published.`);
    return { name: report.title, published: true };
  } catch (err) {
    console.error(err);
    return { name: report.title, published: false };
  }
}

3. 修正推送订阅配置

先删除之前创建的错误订阅:

curl -s -X DELETE 'http://localhost:8043/v1/projects/abc/subscriptions/mysub'

重新创建订阅,将pushEndpoint改为本地Pub/Sub消费函数的实际监听地址。比如你用functions-framework将消费函数启动在8081端口,根路径提供服务,就按如下命令创建:

# 如果模拟器直接在本地宿主机运行,用localhost
curl -s -X PUT 'http://localhost:8043/v1/projects/abc/subscriptions/mysub' \
    -H 'Content-Type: application/json' \
    --data '{"topic":"projects/abc/topics/reports","pushConfig":{"pushEndpoint":"http://localhost:8081/"}}'

注意:如果Pub/Sub模拟器运行在Docker容器中,不能填localhost,需要将地址改为http://host.docker.internal:8081/,保证容器能访问到宿主机上运行的函数服务。

4. 全链路联调验证

分别启动两个本地函数:

  • HTTP触发的发布函数按你本地的调试配置启动,比如监听8080端口
  • Pub/Sub触发的消费函数启动在你配置推送的对应端口(比如前面示例的8081)
    调用本地发布函数的HTTP接口,即可跑通「发布函数拉取数据->发送消息到本地Pub/Sub模拟器->模拟器推送消息到消费函数」的完整链路。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.30 01:09:33