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

Google Pub/Sub本地正常,生产环境无法推送谷歌商家评论至Facebook

Google商家评论推送Facebook功能生产环境失效排查方案

我开发了一套接收Google商家评论通知的系统,已完成Google商家登录及权限配置,基于Node.js实现了Pub/Sub订阅消息拉取功能。本地运行时,星级≥4的评论能正常生成图片并推送至Facebook,但部署到生产环境后该功能失效。相关核心代码如下:

async function pubsubReview() {
  const starNum = (str) => {
    str = str.toUpperCase();
    if (str == "ONE" || str == "STAR_RATING_UNSPECIFIED") {
      return 1;
    } else if (str == "TWO") return 2;
    else if (str == "THREE") return 3;
    else if (str == "FOUR") return 4;
    return 5;
  };
  try {
    const [subs] = await pubSubClient.getSubscriptions();
    console.log("Subscriptions:");
    subs.forEach((s) => console.log(s.name));

    const subscription = pubSubClient.subscription(subscriptionNameOrId);

    // Receive callbacks for new messages on the subscription
    subscription.on("message", async (message) => {
      await message.ackWithResponse();

      console.log("Received message:", message.data.toString());
      try {
        console.log("\n\n", JSON.parse(message.data.toString()), "\n\n");
        const noti = JSON.parse(message.data.toString());
        const accountName = noti.location.split("/locations")[0];
        const reviewName = noti.review;

        const review_name_parts = reviewName.split("/");

        const location_name = review_name_parts[2] + "/" + review_name_parts[3];

        const query = `

        SELECT c.company_id, st.page_token, st.selected_template_url, st.enable_bg, c.location_ids FROM
        (SELECT location_ids, company_id FROM google_my_business WHERE account_name = '${accountName}' ) c
        JOIN 
        (SELECT * FROM socials_token ) st
        ON
        c.company_id = st.company_id


         
      `;

        const queryRes = await promiseQuery(query);
        const pageToken = queryRes[0].page_token;
        const companyId = queryRes[0].company_id;
        let enable_bg = queryRes[0].enable_bg;

        const locationsAddedCheckQuery = `SELECT locations_added FROM google_my_business WHERE company_id = ${companyId}`;
        const locationsAddedCheckQueryRes = await promiseQuery(
          locationsAddedCheckQuery
        );
        let locationsAddedCheck =
          locationsAddedCheckQueryRes[0].locations_added;
        if (!locationsAddedCheck) {
          return res
            .status(200)
            .send({ data: "[]", success: true, toggle: false });
        }

        let toggleCheck = `SELECT location_ids FROM google_my_business WHERE company_id = ${companyId}`;
        let toggleCheckRes = await promiseQuery(toggleCheck);
        toggleCheck = JSON.parse(toggleCheckRes[0].location_ids);
        toggleCheck = toggleCheck.filter((item) => {
          return item.toggle && item.name == location_name;
        });

        console.log("\n\ntoggle Check is\n", toggleCheck);

        if (toggleCheck.length > 0) {
          console.log("\n\ntrying to make post to fb\n");
          try {
            const reviewData = await fetchReview(reviewName, companyId);
            const revieweeName = reviewData.reviewer.displayName;
            const reviewText = reviewData.comment;
            const stars = starNum(reviewData.starRating);
            if (stars >= 4) {
              let templateUrl = queryRes[0].selected_template_url;

              console.log("\n", templateUrl, "\n", pageToken, "\n");
              const response = await axios.get(templateUrl, {
                responseType: "arraybuffer",
              });
              const bufferTemp = Buffer.from(response.data, "binary");
              let image = await Jimp.read(bufferTemp);
              let buffer = null;
              if (enable_bg) {
                buffer = await generateReviewImageWithBG(
                  image,
                  stars,
                  revieweeName,
                  reviewText
                );
              } else {
                buffer = await generateReviewImage(
                  image,
                  stars,
                  revieweeName,
                  reviewText
                );
              }
              const publicUrl = await uploadToGCP(buffer, reviewName);
              console.log(
                "\n\nreview created successfully\n",
                publicUrl,
                "\n\n"
              );
              const imgPost = await postImageToFB(
                pageToken,
                publicUrl,
                "demo-post"
              );
              await deleteFromGCP(reviewName);
              console.log("\n\nposted review to  FB and deleted from GCP");
            }
          } catch (err) {
            console.log("\n\nfailed to make post with error\n", err, "\n\n");
            throw err; // Rethrow the error to be caught in the listener's catch block
          }
        } else {
          console.log("\n\nwill not post to fb\n\n");
        }
      } catch (err) {
        console.log("\nGot error While running PubSub Listener\n", err, "\n\n");
      }
    });

    // Receive callbacks for errors on the subscription
    subscription.on("error", (error) => {
      console.error("Received error:", error);
    });
  } catch (err) {
    console.log("\n\n", err, "\n\n");
  }
}

pubsubReview();

排查方向与解决方案

1. Pub/Sub消息确认时机错误

  • 当前代码在收到消息后立即调用await message.ackWithResponse();,但后续异步操作(数据库查询、图片生成、FB推送)尚未完成。生产环境中如果处理耗时较长,Pub/Sub会判定消息已处理完成,但实际流程失败后无法重新投递。
  • 修复:将消息确认逻辑移到所有异步操作成功完成之后,确保只有全流程执行成功才确认消息。若中间失败,调用message.nack()让Pub/Sub重新投递:
    subscription.on("message", async (message) => {
      try {
        console.log("Received message:", message.data.toString());
        // 原有所有业务逻辑...
        
        // 全流程成功后再确认消息
        await message.ackWithResponse();
        console.log("\n\n流程完成,消息已确认");
      } catch (err) {
        console.log("\n处理失败:\n", err);
        // 拒绝消息,触发重新投递
        message.nack();
      }
    });
    

2. 生产环境权限与网络限制

  • GCP权限:检查生产环境服务账号是否拥有Pub/Sub订阅权限、GCS存储读写权限、Google My Business API访问权限。
  • Facebook API访问:确认生产环境服务器能访问Facebook Graph API,Page Token是否有效(未过期、权限覆盖发布图片),且生产环境域名已在FB开发者平台配置。
  • 模板图片访问:验证templateUrl对应的地址在生产环境是否可访问,避免内网地址或权限限制导致图片拉取失败。

3. 数据库查询风险与数据一致性

  • 当前使用字符串拼接SQL('${accountName}')存在SQL注入风险,若accountName含特殊字符会直接导致查询失败。同时生产环境数据可能与本地不一致(如page_token为空、location_name不匹配),导致流程终止。
  • 修复:改用参数化查询避免注入;添加查询结果校验逻辑,比如判断queryRes[0]是否存在、pageToken是否有效:
    const queryRes = await promiseQuery(query);
    if (!queryRes || queryRes.length === 0) {
      console.log("未找到匹配的企业数据");
      message.nack();
      return;
    }
    const pageToken = queryRes[0].page_token;
    if (!pageToken) {
      console.log("Page Token为空");
      message.nack();
      return;
    }
    

4. 错误处理与日志缺失

  • 生产环境可能未配置完整的日志采集,导致无法定位具体失败环节(如fetchReview、postImageToFB的错误未被记录)。
  • 修复:配置生产环境日志系统(如GCP Cloud Logging),在每个异步步骤添加详细日志,记录关键数据(如reviewData内容、FB接口响应)。

5. 资源限制问题

  • 生产环境服务器内存不足可能导致Jimp图片生成时崩溃;异步操作过多也会阻塞事件循环。
  • 排查:监控服务器内存、CPU使用率;优化图片生成逻辑,避免内存泄漏。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.24 00:54:55