使用 Pub/Sub 建立設有防火牆的 Google Chat 應用程式

本頁面說明如何使用 Pub/Sub 建立即時通訊應用程式。如果您的機構設有防火牆,導致 Chat 無法傳送訊息至 Chat 應用程式,或者 Chat 應用程式使用 Google WorkspaceEvents API,就很適合採用這種即時通訊應用程式架構。然而,由於這些 Chat 應用程式只能傳送及接收非同步訊息,因此本架構具有下列限制:

  • 無法在訊息中使用對話方塊。請改用資訊卡訊息
  • 無法更新含有同步回應的個別資訊卡。請改為呼叫 patch 方法,更新整個訊息。

下圖顯示使用 Pub/Sub 建構的 Chat 應用程式架構:

使用 Pub/Sub 實作的 Chat 應用程式架構。

在上圖中,與 Pub/Sub Chat 應用程式互動的使用者俱有下列資訊流程:

  1. 使用者在 Chat 中將訊息傳送至 Chat 應用程式 (透過即時訊息或 Chat 聊天室),或是在 Chat 應用程式具備有效訂閱項目的 Chat 聊天室中發生事件。

  2. Chat 會將訊息傳送至 Pub/Sub 主題。

  3. 應用程式伺服器為包含即時通訊應用程式邏輯的雲端或內部部署系統,會訂閱 Pub/Sub 主題,以便透過防火牆接收訊息。

  4. 或者,Chat 應用程式可以呼叫 Chat API,以非同步方式發布訊息或執行其他作業。

必要條件

Java

設定環境

使用 Google API 前,請先在 Google Cloud 專案中啟用這些 API。您可以在單一 Google Cloud 專案中啟用一或多個 API。
  • 在 Google Cloud 控制台中,啟用 Google Chat API 和 Pub/Sub API。

    啟用 API

設定 Pub/Sub

  1. 建立 Pub/Sub 主題,讓 Chat API 可傳送訊息。建議您為每個 Chat 應用程式使用一個主題。

  2. 為下列服務帳戶指派 Pub/Sub 發布者角色,授予 Chat 發布權限至主題:

    chat-api-push@system.gserviceaccount.com
    
  3. 為 Chat 應用程式建立服務帳戶,以便使用 Pub/Sub 和 Chat 進行授權,並將私密金鑰檔案儲存至工作目錄。

  4. 為主題建立提取訂閱項目

  5. 為您先前建立的服務帳戶指派訂閱項目的 Pub/Sub 訂閱者角色

編寫指令碼

Java

  1. 在 CLI 中提供服務帳戶憑證

    export GOOGLE_APPLICATION_CREDENTIALS=SERVICE_ACCOUNT_FILE_PATH
    
  2. 在工作目錄中,建立名為 pom.xml 的檔案。

  3. 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>
    
  4. 在工作目錄中,建立目錄結構 src/main/java

  5. src/main/java 目錄中,建立名為 Main.java 的檔案。

  6. 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

  1. 在 Google Cloud 控制台中,依序點選「選單」圖示 >「API 和服務」>「已啟用的 API 和服務」>「已啟用的 API 和服務」>「Google Chat API」>「設定」

    前往「設定」

  2. 設定 Pub/Sub 的 Chat 應用程式:

    1. 在「應用程式名稱」中輸入 Quickstart App
    2. 在「Avatar URL」(顯示圖片網址) 中輸入 https://developers.google.com/chat/images/quickstart-app-avatar.png
    3. 在「說明」中輸入 Quickstart app
    4. 在「功能」下方,選取「接收 1:1 訊息」和「加入聊天室和群組對話」
    5. 在「Connection settings」(連線設定) 下方,選取「Cloud Pub/Sub」,然後貼上您先前建立的 Pub/Sub 主題名稱。
    6. 在「瀏覽權限」下方,選取「將這個 Google Chat 應用程式提供給網域中的特定使用者和群組」,然後輸入您的電子郵件地址。
    7. 在「記錄檔」下方,選取「將錯誤記錄到 Logging」
  3. 點按「儲存」

應用程式現在可以在 Chat 中接收及回覆訊息了。

執行指令碼

在 CLI 中,切換至工作目錄並執行指令碼:

Java

mvn compile exec:java -Dexec.mainClass=Main

當您執行程式碼時,應用程式會開始監聽發布至 Pub/Sub 主題的訊息。

測試 Chat 應用程式

如要測試 Chat 應用程式,請傳送即時訊息給應用程式:

  1. 開啟 Google Chat
  2. 如要傳送即時訊息給應用程式,請按一下「Start a chat」(發起即時通訊) ,然後在顯示的視窗中按一下「Find apps」
  3. 在「尋找應用程式」對話方塊中,搜尋「快速入門導覽課程應用程式」。
  4. 如要透過應用程式開啟即時訊息,請找到快速入門導覽課程應用程式,然後依序按一下「Add」>「Chat」
  5. 在即時訊息中輸入 Hello,然後按下 enter。Chat 應用程式會回應訊息。

如要新增信任的測試人員並進一步瞭解如何測試互動功能,請參閱「測試 Google Chat 應用程式的互動式功能」。

疑難排解

如果 Google Chat 應用程式或卡片傳回錯誤,Chat 介面會顯示「發生錯誤」或「無法處理您的要求」的訊息。有時 Chat UI 不會顯示任何錯誤訊息,但即時通訊應用程式或資訊卡產生非預期的結果,例如資訊卡訊息可能不會顯示。

雖然 Chat UI 可能不會顯示錯誤訊息,但我們提供描述性的錯誤訊息和記錄資料,協助您修正啟用 Chat 應用程式錯誤記錄功能時發生的錯誤。如要瞭解如何查看、偵錯及修正錯誤,請參閱「疑難排解及修正 Google Chat 錯誤」。

清除所用資源

如要避免系統向您的 Google Cloud 帳戶收取本教學課程中所用資源的費用,建議您刪除 Cloud 專案。

  1. 在 Google Cloud 控制台中,前往「Manage resources」(管理資源) 頁面。依序點選「Menu」圖示 >「IAM & Admin」>「管理資源」

    前往 Resource Manager

  2. 在專案清單中選取要刪除的專案,然後按一下「Delete」(刪除)
  3. 在對話方塊中輸入專案 ID,然後按一下「Shut down」(關閉) 來刪除專案。