构建启动器

本文档介绍了如何构建一个启动器,让您的应用或服务能够在发生事件时通知 Google Workspace Studio 并启动流程执行。在 API 中,启动器称为 workflowTriggers

启动器用于启动流程,而步骤是构成流程的任务序列中的单个任务。通过构建启动器,您可以让用户设置自动流程,以响应来自您的应用或服务的实时事件。

构建启动器涉及在插件清单文件中声明启动器,并在 Google Apps 脚本中实现生命周期回调,或者通过向 Google Workspace Studio API 端点发布载荷来触发启动器。

前提条件和 OAuth 授权

如需与 Workspace Studio API 端点通信,您的应用或服务必须使用 OAuth 2.0 进行身份验证。应用必须在授权期间向用户请求以下专用 OAuth 范围:

https://www.googleapis.com/auth/workspace.studio.trigger

此范围授权应用调用 Workspace Studio API 并触发用户为此启动方式配置的流程。

离线访问和刷新令牌

由于启动器会在外部服务中发生事件时异步通知 Workspace Studio(这可能发生在用户配置流程后的数小时、数天或数月),因此您的服务在调用 API 端点时必须提供有效的 OAuth 2.0 访问令牌。

Google 在插件事件对象(例如在启动器配置或生命周期回调请求期间)中提供的访问令牌有效期较短,仅为 1 小时。这不足以在未来异步触发启动器事件。为了能够随时调用 Workspace Studio API,您的服务需要使用离线刷新令牌来按需生成新的访问令牌。

您处理授权和获取刷新令牌的方式取决于插件运行时:

  • HTTP 加购项(替代运行时):对于 HTTP 加购项,您的后端服务必须实现一个独立于内置加购项授权的单独 OAuth 2.0 授权流程,以请求离线访问权限 (access_type=offline) 并接收刷新令牌。

    当用户在 Workspace Studio 中配置启动方式时,您可以通过显示登录或授权卡片来提示用户授权此连接。如需详细了解如何返回授权卡片和处理 OAuth 流程,请参阅将 Google Workspace 插件与第三方服务相关联(将 Google Workspace 视为您要关联的第三方服务)。

    您的后端服务必须安全地存储刷新令牌(例如,在服务的数据库中与 triggerId 一起存储),并在每次发生事件时使用该令牌检索新的访问令牌,然后再向启动器的 notifyUritriggers.fire API 端点发送请求。

  • Google Apps 脚本插件:基于 Google Apps 脚本且使用预定(时间驱动型)触发器轮询事件的插件可以跳过实现独立的 OAuth 流程。由于定时触发器直接在 Google Apps 脚本运行时环境中运行,因此 Google Apps 脚本会使用清单中声明的范围自动管理和刷新 OAuth 令牌。

在清单文件中定义启动器

如需定义启动器,请将其添加到插件清单文件 (appsscript.json) 的 addOns.studio.flows.workflowElements 块中。Apps 脚本和 HTTP 运行时(备选运行时)都需要此配置。将元素配置为 workflowTrigger,而不是 workflowAction(用于定义步骤)。如需了解详情,请参阅 Google Workspace 加载项的清单结构

workflowTrigger 代码块内,指定:

  • inputs:用户在配置卡片上配置的变量(例如项目名称、资源过滤条件等)。
  • outputs:启动器可返回给流程中下游步骤的变量。
  • onConfigFunction:用于显示用户配置界面的回调函数的名称。
  • onManageFunction:由 Google 调用以处理初始订阅创建和删除的回调函数的名称。

以下代码示例展示了事件启动器的示例清单定义:

JSON

{
  "timeZone": "America/Los_Angeles",
  "exceptionLogging": "STACKDRIVER",
  "runtimeVersion": "V8",
  "addOns": {
    "common": {
      "name": "Trigger App",
      "logoUrl": "https://fonts.gstatic.com/s/i/short-term/release/googlesymbols/start/default/24px.svg",
      "useLocaleFromApp": true
    },
    "studio": {
      "flows": {
        "workflowElements": [
          {
            "id": "triggerDemo",
            "state": "ACTIVE",
            "name": "Event Trigger",
            "description": "Fires when a event occurs in the app.",
            "workflowTrigger": {
              "inputs": [
                {
                  "id": "projectId",
                  "description": "The project identifier to watch.",
                  "cardinality": "SINGLE",
                  "dataType": {
                    "basicType": "STRING"
                  }
                }
              ],
              "outputs": [
                {
                  "id": "eventName",
                  "description": "The name of the triggered event.",
                  "cardinality": "SINGLE",
                  "dataType": {
                    "basicType": "STRING"
                  }
                },
                {
                  "id": "eventMessage",
                  "description": "Detailed event message description.",
                  "cardinality": "SINGLE",
                  "dataType": {
                    "basicType": "STRING"
                  }
                }
              ],
              "onConfigFunction": "onConfigTrigger",
              "onManageFunction": "onManageTrigger"
            }
          }
        ]
      }
    }
  }
}

处理初始订阅生命周期

当用户配置并启用包含您的初始流程的流程时,或者当流程被停用或删除时,Google 会使用清单中声明的 onManageFunction 回调函数来调用您的插件。

生命周期事件对象

回调函数会接收包含操作上下文的 WorkflowEventObject。首先,这包括:

  • 触发器创建 (event.workflow.triggerCreation):在发布或启用流程时触发。

    • triggerId:用于标识相应启动器注册实例的唯一 UUID 字符串。

    • notifyUri:与此初始注册相关联的唯一 REST API 端点网址(例如 https://workspacestudio.googleapis.com/v1/triggers/YOUR_TRIGGER_ID:fire)。

    • inputs:用户通过卡片配置的变量输入。

  • 触发器删除 (event.workflow.triggerDeletion):当启动器从流程中移除,或者当整个流程被停用或删除时触发。

    • triggerId:要清理的订阅实例的唯一 UUID 字符串。

Alternate Runtimes (HTTP API) 订阅生命周期

对于使用替代运行时构建的插件,订阅生命周期通知会通过 HTTP POST 请求发送到插件的已配置 HTTP 端点网址,并使用 onManageFunction 回调函数指定的操作名称。载荷与 WorkflowEventObject 的 JSON 表示形式一致。

如需详细了解替代运行时,请参阅使用 HTTP 端点构建 Google Workspace 插件

在 Apps 脚本中实现生命周期回调

以下 Apps 脚本示例展示了如何配置用户界面卡片、使用 onManageTrigger 处理订阅生命周期事件,以及在发生事件时将启动器请求发回给 Google。

Apps 脚本

/**
 * Generates and returns the user configuration card to collect inputs.
 */
function onConfigTrigger() {
  const projectInput = CardService.newTextInput()
    .setFieldName("projectId")
    .setTitle("Project ID")
    .setHint("Enter the project identifier to watch");

  const section = CardService.newCardSection()
    .setHeader("Configure Event Trigger")
    .addWidget(projectInput);

  const card = CardService.newCardBuilder()
    .addSection(section)
    .build();

  return card;
}

/**
 * Handles subscription lifecycle events sent from Google Workspace Studio.
 *
 * @param {Object} event The Workspace Studio event object.
 */
function onManageTrigger(event) {
  const triggerCreation = event.workflow.triggerCreation;
  const triggerDeletion = event.workflow.triggerDeletion;

  if (triggerCreation) {
    const triggerId = triggerCreation.triggerId;
    const notifyUri = triggerCreation.notifyUri;
    const inputs = triggerCreation.inputs;

    // Extract input values configured by the user.
    const projectId = inputs["projectId"].stringValues[0];

    // TODO: Save triggerId, notifyUri, and projectId in your database/service.
    // Your backend service listens for events related to 'projectId'
    // and calls notifyUri when those events occur.
    console.log("Trigger subscription created: " + triggerId +
                ", Notify URI: " + notifyUri +
                ", Match Project: " + projectId);

  } else if (triggerDeletion) {
    const triggerId = triggerDeletion.triggerId;

    // TODO: Remove references to triggerId from your database and stop
    // sending future event notifications to the associated notifyUri.
    console.log("Trigger subscription deleted: " + triggerId);
  }
}

/**
 * Mock function showing how your backend service fires the trigger.
 * This logic runs on your service when a watched event occurs.
 *
 * @param {string} notifyUri The stored notifyUri associated with the trigger.
 * @param {string} triggerId The stored triggerId.
 * @param {string} userAccessToken The OAuth 2.0 access token for the user
 *     (obtained using your stored refresh token).
 */
function simulateEventFire(notifyUri, triggerId, userAccessToken) {
  // A unique UUID version 4 is recommended as the requestId for idempotency.
  const requestId = Utilities.getUuid();

  const payload = {
    "name": "triggers/" + triggerId,
    "outputs": {
      "eventName": { "stringValues": ["EventOccurred"] },
      "eventMessage": { "stringValues": ["Hello from the service!"] }
    },
    "requestId": requestId
  };

  const options = {
    "method": "POST",
    "contentType": "application/json",
    "headers": {
      "Authorization": "Bearer " + userAccessToken
    },
    "payload": JSON.stringify(payload),
    "muteHttpExceptions": true
  };

  const response = UrlFetchApp.fetch(notifyUri, options);
  const responseCode = response.getResponseCode();

  if (responseCode === 200) {
    console.log("Trigger successfully fired!");
  } else if (responseCode === 404) {
    // 404 means the trigger registration is invalid or deleted.
    console.log("Trigger not found. Stop sending events for this trigger.");
    // TODO: Clean up the trigger from your backend database.
  } else if (responseCode === 429 || responseCode >= 500) {
    console.log("Temporary error (" + responseCode + "). Retry using exponential backoff.");
  } else {
    console.log("Failed to fire trigger. HTTP Code: " + responseCode + " - " + response.getContentText());
  }
}

使用 Workspace Studio API

您可以使用 Workspace Studio API (workspacestudio.googleapis.com) 以程序化方式将启动方式事件通知给 Google。

端点位于基本路径 https://workspacestudio.googleapis.com/v1 下。

通知启动器事件

使用 triggers.fire 方法触发启动器,以启动流程的执行。

  • HTTP 方法POST
  • 路径/v1/triggers/{triggerId}:fire(其中 {triggerId} 是在创建触发器订阅期间检索到的唯一标识符)
  • OAuth 范围https://www.googleapis.com/auth/workspace.studio.trigger

以下代码示例展示了如何在请求中触发启动器。

请求

{
  "name": "triggers/TRIGGER_ID",
  "outputs": {
    "eventName": {
      "stringValues": [
        "EventOccurred"
      ]
    },
    "eventMessage": {
      "stringValues": [
        "Hello from the service!"
      ]
    }
  },
  "log": {
    "textFormatElements": [
      {
        "text": "An event occurred in the app."
      }
    ]
  },
  "requestId": "UNIQUE_REQUEST_ID"
}
  • name(字符串,必需):启动器的资源名称,格式为 triggers/{triggerId}
  • outputs(映射,可选):表示事件数据的初始输出变量的映射。每个值都是支持类型化列表(例如 stringValuesbooleanValuesintegerValues)的 VariableData 对象。
  • log(对象,可选):在 Workspace Studio 执行活动日志中显示的 TextFormat 标记表示法。
  • requestId(字符串,可选):一个唯一标识符(建议使用 UUID v4),最多包含 36 个 ASCII 字符,用于确保在重试时 API 具有幂等性。

答案

如果成功,响应将返回一个空的 JSON 对象 {}

Workspace Studio API 配额

发送到 workspacestudio.googleapis.com 服务的流量受到限制,以防止系统过载、鼓励合理使用资源并保护整体 Google Workspace 性能。

系统会强制执行以下配额:

配额类型 配额
每个项目每分钟 1,000 个入门版请求
每位用户每分钟 100 个入门版请求

配额类型包括:

  • 每个项目每分钟:将单个开发者的 Google Cloud 项目中触发的启动器事件的累计数量限制为每分钟 1,000 个请求,适用于运行其启动器的所有用户。
  • 每位用户每分钟:将任何单个最终用户在给定 Cloud 项目中的累积启动器调用次数限制为每分钟 100 个请求。

处理基于时间的配额错误

如果超出这些配额,API 会返回 HTTP 429 Too Many Requests(或 429 Resource Exhausted)错误代码,指明速率配额已超出。

如需解决这些错误,您的代码应捕获异常并使用截断的指数退避算法策略。指数退避算法会重试失败的请求,并在每次尝试之间逐步增加延迟时间,包括随机抖动(在每次迭代时重新计算随机延迟时间),以防止多个客户端同时同步并重试:

  1. 向 Workspace Studio API 发出请求。
  2. 如果请求失败并显示 429 错误,请等待 1 second + random_number_milliseconds,然后重试。
  3. 如果再次失败,请等待 2 seconds + random_number_milliseconds,然后重试。
  4. 如果再次失败,请等待 4 seconds + random_number_milliseconds,然后重试。
  5. 继续此循环,将延迟时间加倍,直至达到 maximum_backoff 阈值(通常为 32 或 64 秒)。
  6. 达到退避时长上限后,使用该恒定延迟进行重试,直至达到重试次数上限,然后停止并记录错误。

最佳做法

在设计和实现启动器时,请考虑以下最佳实践:

发出单个事件,而不是批量列表

设计启动器时,请确保它能针对每个不同的事件(例如更新单条记录、发布新消息或分配任务)分别发出一个事件,而不是发出包含一批或一个列表的单个事件:

  • 与内置启动器的一致性:在 Workspace Studio 中,内置的 Google Workspace 启动器(例如在 Gmail 中收到电子邮件或用户加入 Google Chat 中的聊天室)由单个事件触发。发出单项事件与此行为保持一致,并为所有初始配置的用户提供一致且可预测的体验。
  • 更简单的流程配置:流程中的下游步骤通常一次处理一个项目。通过发出单项事件,用户可以直接映射变量,而无需添加复杂的步骤来迭代数组或解析列表。
  • 单独处理轮询和批量更改:如果您的后端服务轮询外部 API,并在单个轮询间隔期间检测到多个已更改的项,请为每个项触发单独的启动器事件,而不是将它们捆绑到一个批量事件中。
  • 管理事件速率和配额:由于为多个更改的项目触发单个事件可能会导致请求突然激增,因此请确保您的服务保持在 Workspace Studio API 配额(例如每位用户每分钟 100 个请求的限制)范围内。如果轮询周期产生大量项(例如,超过 100 条更改记录),请在一段时间内调整或限制事件分派,以避免 429 Too Many Requests 错误。

核心行为和极端情形

集成启动器时,开发者必须处理特定的错误行为和运行时功能:

  • 不支持测试运行:Workspace Studio 不支持新手版测试运行。
  • 幂等性和重放预防:虽然不是严格要求,但您应在 HTTP 或 Apps 脚本载荷中包含唯一的 requestId(例如 UUID)。提供 requestId 可确保幂等性,因为这样一来,API 就能检测并忽略重复的通知,从而防止流程针对单个事件运行多次。
  • 已停用和重新启用的流程:当包含启动器的流程在 Workspace Studio 中停用时,Google 会向您的 onManageFunction 回调发送 triggerDeletion 生命周期事件。此外,对关联的 FireTrigger 方法的任何调用都会返回 404 Not Found 错误返回代码 (Requested entity was not found.)。您的服务应通过停止向相应启动器实例 ID 传送未来的事件通知来对 404 错误做出反应。

    如果用户稍后重新启用该流程,Google 会通过调用您的 onManageFunction 回调来启动新的订阅生命周期,并提供包含新 triggerIdnotifyUri 的新 triggerCreation 事件。之前的 triggerId 已永久停用,不会重新启用,因此您的服务不应轮询或检查旧触发器实例是否已重新启用。如需了解详情,请参阅处理初始订阅生命周期

  • 幂等订阅删除:您的 onManageFunction 回调函数必须以幂等方式处理来自 Google 的启动器删除请求。如果 Google 针对同一 triggerId 多次调用删除钩子(例如,在因临时连接丢失而重试期间),该函数应成功返回。

  • 流程配额:除了 Workspace Studio API 配额之外,用户流程还受其他内部配额控制。高频循环或过多的事件量可能会超出安全阈值,导致系统自动停用流程。