本页面介绍如何使用 Pub/Sub 创建聊天应用。如果贵组织设有可阻止 Chat 向 Chat 应用发送消息的防火墙,或者 Chat 应用使用 Google Workspace Events API,则此类聊天应用架构非常有用。但是,由于这些 Chat 应用只能发送和接收异步消息,因此该架构存在以下限制:
下图显示了使用 Pub/Sub 构建的 Chat 应用的架构:
在上图中,与 Pub/Sub Chat 应用交互的用户具有以下信息流:
用户在 Chat 中通过私信或 Chat 聊天室向 Chat 应用发送消息,或者事件发生在 Chat 聊天室具有有效订阅的 Chat 聊天室中。
Chat 将消息发送到 Pub/Sub 主题。
应用服务器是包含 Chat 应用逻辑的云端或本地系统,它会订阅 Pub/Sub 主题,以便通过防火墙接收消息。
(可选)Chat 应用可以调用 Chat API 来异步发布消息或执行其他操作。
前提条件
Java
- 有权访问 Google Chat 的 Google Workspace 帐号。
- Google Cloud 项目。
- 确保为 Cloud 项目启用结算功能。了解如何验证项目的结算状态。
- Java 11 或更高版本。
- Apache Maven
设置环境
在使用 Google API 之前,您需要在 Google Cloud 项目中启用它们。您可以在单个 Google Cloud 项目中启用一个或多个 API。在 Google Cloud 控制台中,启用 Google Chat API 和 Pub/Sub API。
设置 Pub/Sub
创建 Pub/Sub 主题,Chat API 可向其发送消息。我们建议您为每个 Chat 应用使用一个主题。
通过将 Pub/Sub Publisher 角色分配给以下服务帐号,向 Chat 授予发布权限:
chat-api-push@system.gserviceaccount.com
为 Chat 应用创建服务帐号,以使用 Pub/Sub 和 Chat 进行授权,并将私钥文件保存到您的工作目录中。
为该主题创建拉取订阅。
为您之前创建的服务帐号分配订阅的 Pub/Sub Subscriber 角色。
编写脚本
Java
在 CLI 中,提供服务账号凭据:
export GOOGLE_APPLICATION_CREDENTIALS=SERVICE_ACCOUNT_FILE_PATH
在您的工作目录中,创建一个名为
pom.xml
的文件。在
pom.xml
文件中,粘贴以下代码:<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>com.google.chat.pubsub</groupId> <artifactId>java-pubsub-app</artifactId> <version>0.1.0</version> <name>java-pubsub-app</name> <properties> <maven.compiler.target>11</maven.compiler.target> <maven.compiler.source>11</maven.compiler.source> </properties> <dependencyManagement> <dependencies> <dependency> <groupId>com.google.cloud</groupId> <artifactId>libraries-bom</artifactId> <version>26.26.0</version> <type>pom</type> <scope>import</scope> </dependency> </dependencies> </dependencyManagement> <dependencies> <dependency> <groupId>com.google.code.gson</groupId> <artifactId>gson</artifactId> <version>2.9.1</version> </dependency> <dependency> <groupId>com.google.api-client</groupId> <artifactId>google-api-client</artifactId> <version>1.32.1</version> </dependency> <dependency> <groupId>com.google.cloud</groupId> <artifactId>google-cloud-pubsub</artifactId> </dependency> <dependency> <groupId>com.fasterxml.jackson.core</groupId> <artifactId>jackson-databind</artifactId> <version>2.14.2</version> </dependency> </dependencies> <build> <pluginManagement> <plugins> <plugin> <artifactId>maven-compiler-plugin</artifactId> <version>3.8.0</version> </plugin> </plugins> </pluginManagement> </build> </project>
在工作目录中,创建目录结构
src/main/java
。在
src/main/java
目录中,创建一个名为Main.java
的文件。在
Main.java
中,粘贴以下代码: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 = PROJECT_ID; // Cloud Pub/Sub Subscription ID public static final String SUBSCRIPTION_ID = 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 <var>SUBSCRIPTION_ID</var> 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(); } }
请替换以下内容:
PROJECT_ID
:Google Cloud 项目 ID。SUBSCRIPTION_ID
:您之前创建的 Pub/Sub 订阅的订阅 ID。
将应用发布到 Chat
在 Google Cloud 控制台中,依次点击“菜单”图标 > API 和服务 > 已启用的 API 和服务 > Google Chat API > 配置。
为 Pub/Sub 配置 Chat 应用:
- 在应用名称中,输入
Quickstart App
。 - 在头像网址中输入
https://developers.google.com/chat/images/quickstart-app-avatar.png
。 - 在说明中,输入
Quickstart app
。 - 在功能下方,选择接收 1 对 1 消息和加入聊天室和群组对话。
- 在连接设置下,选择 Cloud Pub/Sub 并粘贴您之前创建的 Pub/Sub 主题的名称。
- 在公开范围下方,选择将此 Google Chat 应用提供给您网域中的特定人员和群组,然后输入您的电子邮件地址。
- 在日志下,选择将错误记录到 Logging。
- 在应用名称中,输入
点击保存。
应用已准备好在 Chat 中接收和回复消息。
运行脚本
在 CLI 中,切换到您的工作目录并运行以下脚本:
Java
mvn compile exec:java -Dexec.mainClass=Main
运行该代码时,应用会开始监听发布到 Pub/Sub 主题的消息。
测试 Chat 应用
如需测试您的 Chat 应用,请使用 Chat 应用打开私信聊天室并发送消息:
使用您将自己添加为可信测试员时提供的 Google Workspace 账号打开 Google Chat。
- 点击 发起新聊天。
- 在添加 1 位或更多人字段中,输入您的 Chat 应用的名称。
从结果中选择您的 Chat 应用。系统会打开一条私信。
- 在与应用的新私信对话中,输入
Hello
,然后按enter
。
如需添加可信测试员并详细了解如何测试交互功能,请参阅测试 Google Chat 应用的交互功能。
排查问题
当 Google Chat 应用或卡片返回错误时,Chat 界面会显示“出了点问题”的消息。或“无法处理您的请求”。有时,Chat 界面不会显示任何错误消息,但 Chat 应用或卡片会产生意外结果;例如,卡片消息可能不会显示。
虽然 Chat 界面中可能不会显示错误消息,但当为 Chat 应用启用错误日志记录功能时,描述性错误消息和日志数据可帮助您修复错误。如需查看、调试和修复错误方面的帮助,请参阅排查和修正 Google Chat 错误。
清理
为避免因本教程中使用的资源导致您的 Google Cloud 帐号产生费用,我们建议您删除该 Cloud 项目。
- 在 Google Cloud 控制台中,前往管理资源页面。依次点击菜单图标 > IAM 和管理 > 管理资源。
- 在项目列表中,选择要删除的项目,然后点击删除 。
- 在对话框中输入项目 ID,然后点击关停以删除项目。
相关主题
如需向 Chat 应用添加更多功能,请参阅以下内容: