使用 Cloud Pub/Sub 作为应用的端点

为您的组织创建应用时,组织使用的可能是防火墙,这可能会导致 Google Chat 无法将消息发送到您的应用。为此,Google Chat 与 Cloud Pub/Sub 集成。此集成可让您的应用与 Cloud Pub/Sub 建立连接并接收消息。

Cloud Pub/Sub 与 Chat 集成的架构。

设置 Cloud Pub/Sub

本部分介绍如何设置 Cloud Pub/Sub 主题并为您的应用订阅该主题。

创建启用了 Pub/Sub 的 Google Cloud 项目

要将 Cloud Pub/Sub 用于您的应用,您需要创建一个已启用 Pub/Sub API 的 Google Cloud Platform 项目。

  1. 在 Google Cloud 控制台中创建新项目
  2. 在控制台的左侧窗格中,选择 Pub/Sub,然后选择启用 API

创建 Pub/Sub 主题

接下来,创建一个主题,Chat API 应向该主题发送消息。如需了解如何创建主题,请参阅 Pub/Sub 控制台快速入门

启用 Google Chat API

请务必启用 Google Chat API,并将其设为上一步中创建的主题。

  1. 在控制台的左侧窗格中,选择 API 和服务,然后启用 Google Chat API。
  2. 配置 API 时,请确保将“连接设置”设置为 Cloud Pub/Sub,并提供上一步中指定的主题。
  3. 填写示例应用指南中详述的其他字段。

针对您的主题授予发布权限

为了让 Google Chat 向您的主题发布消息,Google Chat 必须具有该主题的发布权限。如需授予 Google Chat 这些权限,请将 Pub/Sub Publisher 角色分配给以下服务帐号:

chat-api-push@system.gserviceaccount.com

这将授权 Google Chat 围绕您的主题发布内容。

创建服务账号

为了使应用代码使用 Cloud Pub/Sub 和 Google Chat 进行授权,需要使用服务帐号。如需设置服务帐号,请参阅使用服务帐号通过 Chat API 授权和身份验证

创建订阅

按照 Cloud Pub/Sub 订阅者指南,对您创建的主题设置订阅。将订阅类型配置为“webhook 拉取”。为您在上一步中创建的服务帐号添加订阅权限,并为其指定“Pub/Sub 订阅者”角色。

发布 Cloud Pub/Sub 应用

如需详细了解如何发布应用,请参阅发布应用指南。对于 Cloud Pub/Sub 应用,请选择适用于 Cloud Pub/Sub 的选项,并输入所创建主题的完全限定名称。主题名称必须采用以下格式:

projects/PROJECT_ID/topics/TOPIC_ID

例如 projects/pubsub-demo-2/topics/test-topic

以这种方式发布应用后,用户即可使用该应用,并且该应用将在您配置的 Pub/Sub 主题上接收消息。

下一部分中的示例将介绍如何创建和运行实现这些对象的简单应用。

应用实现示例

以下示例代码展示了一个使用 Cloud Pub/Sub 接收传入消息的简单应用。若要试用此应用,您需要:

  • 在 Main 类中修改项目 ID 和订阅 ID 值
  • 按照身份验证使用入门指南中的说明提供服务帐号凭据。

    导出 GOOGLE_APPLICATIONCREDENTIALS=<path_to_service_account_file>

如需运行此代码,您的类路径中必须包含以下依赖项:

package com.google.chat;

import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.node.JsonNodeFactory;
import com.fasterxml.jackson.databind.node.ObjectNode;
import com.google.api.client.googleapis.auth.oauth2.GoogleCredential;
import com.google.api.client.googleapis.javanet.GoogleNetHttpTransport;
import com.google.api.client.http.ByteArrayContent;
import com.google.api.client.http.GenericUrl;
import com.google.api.client.http.HttpContent;
import com.google.api.client.http.HttpRequest;
import com.google.api.client.http.HttpRequestFactory;
import com.google.api.client.http.HttpTransport;
import com.google.cloud.pubsub.v1.AckReplyConsumer;
import com.google.cloud.pubsub.v1.MessageReceiver;
import com.google.cloud.pubsub.v1.Subscriber;
import com.google.pubsub.v1.PubsubMessage;
import com.google.pubsub.v1.ProjectSubscriptionName;
import java.io.FileInputStream;
import java.util.Collections;

public class Main {

  public static final String CREDENTIALS_PATH_ENV_PROPERTY = "GOOGLE_APPLICATION_CREDENTIALS";

  // Google Cloud Project ID
  public static final String PROJECT_ID = "my-project-id";

  // Cloud Pub/Sub Subscription ID
  public static final String SUBSCRIPTION_ID = "my-subscription-id";

  public static void main(String[] args) throws Exception {
    ProjectSubscriptionName subscriptionName =
        ProjectSubscriptionName.of(PROJECT_ID, SUBSCRIPTION_ID);

    // Instantiate app, which implements an asynchronous message receiver.
    EchoApp echoApp = new EchoApp();

    // Create a subscriber for "my-subscription-id" bound to the message receiver
    final Subscriber subscriber =
        Subscriber.newBuilder(subscriptionName, echoApp).build();
    System.out.println("Starting subscriber...");
    subscriber.startAsync();

    // Wait for termination
    subscriber.awaitTerminated();
  }
}

/**
 * A demo app which implements {@link MessageReceiver} to receive messages. It simply echoes the
 * incoming messages.
 */
class EchoApp implements MessageReceiver {

  // Path to the private key JSON file of the service account to be used for posting response
  // messages to Google Chat.
  // In this demo, we are using the same service account for authorizing with Cloud Pub/Sub to
  // receive messages and authorizing with Google Chat to post messages. If you are using
  // different service accounts, please set the path to the private key JSON file of the service
  // account used to post messages to Google Chat here.
  private static final String SERVICE_ACCOUNT_KEY_PATH =
      System.getenv(Main.CREDENTIALS_PATH_ENV_PROPERTY);

  // Developer code for Google Chat api scope.
  private static final String GOOGLE_CHAT_API_SCOPE = "https://www.googleapis.com/auth/chat.bot";

  // Response URL Template with placeholders for space id.
  private static final String RESPONSE_URL_TEMPLATE =
      "https://chat.googleapis.com/v1/__SPACE_ID__/messages";

  // Response echo message template.
  private static final String RESPONSE_TEMPLATE = "You said: `__MESSAGE__`";

  private static final String ADDED_RESPONSE = "Thank you for adding me!";

  GoogleCredential credential;
  HttpTransport httpTransport;
  HttpRequestFactory requestFactory;

  EchoApp() throws Exception {
    credential =
        GoogleCredential.fromStream(new FileInputStream(SERVICE_ACCOUNT_KEY_PATH))
            .createScoped(Collections.singleton(GOOGLE_CHAT_API_SCOPE));
    httpTransport = GoogleNetHttpTransport.newTrustedTransport();
    requestFactory = httpTransport.createRequestFactory(credential);
  }

  // Called when a message is received by the subscriber.
  @Override
  public void receiveMessage(PubsubMessage pubsubMessage, AckReplyConsumer consumer) {
    System.out.println("Id : " + pubsubMessage.getMessageId());
    // handle incoming message, then ack/nack the received message
    try {
      ObjectMapper mapper = new ObjectMapper();
      JsonNode dataJson = mapper.readTree(pubsubMessage.getData().toStringUtf8());
      System.out.println("Data : " + dataJson.toString());
      handle(dataJson);
      consumer.ack();
    } catch (Exception e) {
      System.out.println(e);
      consumer.nack();
    }
  }

  public void handle(JsonNode eventJson) throws Exception {
    JsonNodeFactory jsonNodeFactory = new JsonNodeFactory(false);
    ObjectNode responseNode = jsonNodeFactory.objectNode();

    // Construct the response depending on the event received.

    String eventType = eventJson.get("type").asText();
    switch (eventType) {
      case "ADDED_TO_SPACE":
        responseNode.put("text", ADDED_RESPONSE);
        // An app can also be added to a space by @mentioning it in a message. In that case, we fall
        // through to the MESSAGE case and let the app respond. If the app was added using the
        // invite flow, we just post a thank you message in the space.
        if(!eventJson.has("message")) {
          break;
        }
      case "MESSAGE":
        responseNode.put("text",
            RESPONSE_TEMPLATE.replaceFirst(
                "__MESSAGE__", eventJson.get("message").get("text").asText()));
        // In case of message, post the response in the same thread.
        ObjectNode threadNode = jsonNodeFactory.objectNode();
        threadNode.put("name", eventJson.get("message").get("thread").get("name").asText());
        responseNode.put("thread", threadNode);
        break;
      case "REMOVED_FROM_SPACE":
      default:
        // Do nothing
        return;
    }

    // Post the response to Google Chat.

    String URI =
        RESPONSE_URL_TEMPLATE.replaceFirst(
            "__SPACE_ID__", eventJson.get("space").get("name").asText());
    GenericUrl url = new GenericUrl(URI);

    HttpContent content =
        new ByteArrayContent("application/json", responseNode.toString().getBytes("UTF-8"));
    HttpRequest request = requestFactory.buildPostRequest(url, content);
    com.google.api.client.http.HttpResponse response = request.execute();
  }
}

限制和注意事项

请注意以下 Pub/Sub 限制和注意事项:

  • 打开、提交或取消对话框需要来自具有 DialogEventType 的 Chat 应用的同步响应。因此,使用异步架构(如 Cloud Pub/Sub)构建的聊天应用也不支持对话框。作为权宜之计,请考虑使用卡片消息而非对话框。
  • 使用 Cloud Pub/Sub 构建的聊天应用无法使用同步响应更新个别卡片。如需更新卡片,请调用 Patch Message API 方法来更新整条消息。

排查问题

当 Google Chat 应用或卡片返回错误时,Chat 界面会显示“出了点问题”或“无法处理您的请求”。有时,Chat 界面中不显示任何错误消息,但 Chat 应用或卡片产生了意外结果;例如,卡片消息可能不会显示。

为 Chat 应用启用错误日志记录功能后,虽然 Chat 界面中可能不会显示错误消息,但借助描述性错误消息和日志数据,您可以修复相关错误。如需获取查看、调试和修正错误方面的帮助,请参阅排查和修正 Google Chat 错误