יצירת אפליקציית Google Chat מאחורי חומת אש בעזרת Pub/Sub

בדף הזה מוסבר איך יוצרים אפליקציה ל-Chat באמצעות Pub/Sub. הארכיטקטורה של אפליקציות ל-Chat יכולה להיות שימושית אם יש בארגון חומת אש, שמונעת מ-Chat לשלוח הודעות לאפליקציית Chat, או אם אפליקציית Chat משתמשת ב-Google Workspace Event API. עם זאת, לארכיטקטורה הזו יש את המגבלות הבאות כי אפליקציות הצ'אט האלה יכולות לשלוח ולקבל רק הודעות אסינכרוניות:

  • לא ניתן להשתמש בתיבות דו-שיח בהודעות. במקום זאת, כדאי להשתמש בהודעה בכרטיס.
  • לא ניתן לעדכן כרטיסים בודדים בתגובה סינכרונית. במקום זאת, צריך לעדכן את ההודעה כולה על ידי קריאה לשיטה patch.

בתרשים הבא מוצגת הארכיטקטורה של אפליקציית Chat המבוססת על Pub/Sub:

הארכיטקטורה של אפליקציית Chat שהוטמעה באמצעות Pub/Sub.

בתרשים הקודם, משתמש שמקיים אינטראקציה עם אפליקציית Pub/Sub Chat מקבל את המידע הבא:

  1. משתמש שולח הודעה ב-Chat לאפליקציית Chat, בצ'אט אישי או במרחב משותף ב-Chat, או אירוע שמתרחש במרחב משותף ב-Chat שבו יש לאפליקציית Chat מינוי פעיל.

  2. ההודעה נשלחת ל-Pub/Sub ב-Chat.

  3. שרת אפליקציות, שהוא מערכת בענן או מערכת מקומית שכוללת את הלוגיקה של אפליקציית Chat, נרשם לנושא Pub/Sub כדי לקבל את ההודעה דרך חומת האש.

  4. אפשר גם להפעיל את אפליקציית Chat ל-Chat API כדי לפרסם הודעות באופן אסינכרוני או לבצע פעולות אחרות.

דרישות מוקדמות

Java

הגדרת הסביבה

כדי להשתמש ב-Google APIs, צריך להפעיל אותם בפרויקט ב-Google Cloud. אפשר להפעיל ממשק API אחד או יותר בפרויקט אחד ב-Google Cloud.

הגדרת Pub/Sub

  1. יוצרים נושא Pub/Sub שאליו ה-Chat API יכול לשלוח הודעות. מומלץ להשתמש בנושא אחד לכל אפליקציית Chat.

  2. מעניקים ל-Chat הרשאת פרסום לנושא על ידי הקצאה של התפקיד פרסום ב-Pub/Sub לחשבון השירות הבא:

    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.
    • SUBSCRIPTION_ID: מזהה המינוי למינוי Pub/Sub שיצרתם בעבר.

פרסום האפליקציה ב-Chat

  1. במסוף Google Cloud, נכנסים לתפריט > APIs ושירותים > Enabled APIs & Services > Google Chat API > הגדרה.

    לפתיחת ההגדרה

  2. מגדירים את אפליקציית Chat ל-Pub/Sub:

    1. בקטע שם האפליקציה, מזינים Quickstart App.
    2. בקטע כתובת URL של הדמות, יש להזין https://developers.google.com/chat/images/quickstart-app-avatar.png.
    3. בתיאור, מזינים Quickstart app.
    4. בקטע פונקציונליות, בוחרים באפשרות לקבל הודעות אישיות ובאפשרות הצטרפות למרחבים משותפים ולשיחות קבוצתיות.
    5. בקטע Connection settings (הגדרות חיבור), בוחרים באפשרות Cloud Pub/Sub ומדביקים את השם של נושא Pub/Sub שיצרתם קודם לכן.
    6. בקטע חשיפה, בוחרים באפשרות הגדרת האפליקציה הזו של Google Chat לזמינה לקבוצות ולאנשים ספציפיים בדומיין ומזינים את כתובת האימייל שלכם.
    7. בקטע Logs (יומנים), בוחרים באפשרות Log errors to Logging.
  3. לוחצים על שמירה.

האפליקציה מוכנה לקבל הודעות ולהשיב להן ב-Chat.

מריצים את הסקריפט

ב-CLI, עוברים לספריית העבודה ומריצים את הסקריפט:

Java

mvn compile exec:java -Dexec.mainClass=Main

כשמריצים את הקוד, האפליקציה מתחילה להאזין להודעות שפורסמו בנושא Pub/Sub.

בדיקה של אפליקציית Chat

כדי לבדוק את אפליקציית Chat, אפשר לשלוח לה הודעה בצ'אט אישי:

  1. פותחים את Google Chat.
  2. כדי לשלוח הודעה בצ'אט אישי לאפליקציה, לוחצים על הסמל 'התחלת צ'אט' ובחלון שמופיע לוחצים על חיפוש אפליקציות.
  3. בתיבת הדו-שיח חיפוש אפליקציות, מחפשים את 'אפליקציית מדריך למתחילים'.
  4. כדי לפתוח צ'אט אישי עם האפליקציה, מאתרים את אפליקציית המדריך למתחילים ולוחצים על הוספה > צ'אט.
  5. כותבים Hello ולוחצים על enter בצ'אט האישי. אפליקציית Chat תחזיר לכם את ההודעה.

כדי להוסיף בודקים נאמנים ולקבל מידע נוסף על בדיקת תכונות אינטראקטיביות, קראו את המאמר בדיקת תכונות אינטראקטיביות לאפליקציות של Google Chat.

פתרון בעיות

כשמוצגת הודעת שגיאה באפליקציה או בכרטיס של Google Chat, מוצגת בממשק של Chat ההודעה "משהו השתבש" או "לא ניתן לעבד את הבקשה שלך". לפעמים לא מוצגת הודעת שגיאה בממשק המשתמש של Chat, אבל קיבלתי תוצאה לא צפויה באפליקציה או בכרטיס של Chat. למשל, יכול להיות שלא תופיע הודעה על כרטיס.

יכול להיות שהודעת השגיאה לא תוצג בממשק המשתמש של Chat, אבל יש הודעות שגיאה תיאוריות ונתוני יומן שיכולים לעזור לכם לתקן שגיאות כשמפעילים רישום שגיאות באפליקציות של Chat. לקבלת עזרה בהצגה, בניפוי באגים ובתיקון שגיאות, אפשר להיעזר במאמר פתרון בעיות ותיקון שגיאות ב-Google Chat.

הסרת המשאבים

על מנת לא לצבור חיובים לחשבון Google Cloud עבור המשאבים שבהם השתמשתם במדריך הזה, מומלץ למחוק את הפרויקט ב-Cloud.

  1. במסוף Google Cloud, עוברים לדף Manage resources. לוחצים על תפריט > IAM ואדמין > ניהול משאבים.

    כניסה למנהל המשאבים

  2. ברשימת הפרויקטים, בוחרים את הפרויקט שרוצים למחוק ולוחצים על Delete .
  3. כדי למחוק את הפרויקט, כותבים את מזהה הפרויקט בתיבת הדו-שיח ולוחצים על Shut down.