构建启动器

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

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

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

前提条件

启用 API

在您的 Google Cloud 项目中,启用 Google Workspace Studio API:

启用 API

切换到标准 Google Cloud 云项目(仅限 Google Apps 脚本)

默认情况下,Apps 脚本项目使用默认的云项目,该项目不支持启用 API 或配置 OAuth 权限请求页面。如果您使用 Apps 脚本构建启动器,则必须切换到标准 Google Cloud 项目:

  1. 在 Apps 脚本编辑器中,点击项目设置 。
  2. 在 Google Cloud Platform (GCP) 项目 下,点击 更改项目 。
  3. 输入您的标准 Cloud 项目编号,然后点击设置项目 。
  4. 切换项目后,通过在编辑器中运行任何函数并授予所请求的权限,重新授权您的脚本。

如需了解详情,请参阅 切换到标准 Google Cloud 项目。

配置 OAuth 权限请求页面

如需与 Workspace Studio API 端点通信,您的应用或服务必须使用 OAuth 2.0 进行身份验证。配置项目的 OAuth 权限请求页面,并添加以下专用 OAuth 范围:

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

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

如需了解详情,请参阅配置 OAuth 权限请求页面并选择 范围。

离线访问权限和刷新令牌

由于启动器会在外部服务中发生事件时异步通知 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 一起存储),并在每次发生事件时使用该令牌检索新的访问令牌,然后再向启动器的 notifyUri 或 triggers.fire API 端点发送请求。

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

在清单文件中定义启动器

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

在 workflowTrigger 代码块内,指定:

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

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

JSON

{
  "timeZone": "America/Los_Angeles",
  "exceptionLogging": "STACKDRIVER",
  "runtimeVersion": "V8",
  "oauthScopes": [
    "https://www.googleapis.com/auth/script.external_request",
    "https://www.googleapis.com/auth/workspace.studio.trigger"
  ],
  "urlFetchWhitelist": [
    "https://workspacestudio.googleapis.com/"
  ],
  "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)。 如果事件载荷中省略了 notifyUri,请使用 triggerId 直接构建端点 网址:https://workspacestudio.googleapis.com/v1/triggers/{triggerId}:fire。

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

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

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

备用运行时 (HTTP API) 订阅生命周期

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

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

在 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;
    // Fall back to constructing the endpoint URL if notifyUri is omitted.
    const notifyUri = triggerCreation.notifyUri ||
        ("https://workspacestudio.googleapis.com/v1/triggers/" +
         triggerId + ":fire");
    const inputs = triggerCreation.inputs;

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

    // Tip: In Apps Script, use PropertiesService to persist triggerId and notifyUri:
    // const props = PropertiesService.getUserProperties();
    // props.setProperty("notifyUri", notifyUri);
    // props.setProperty("triggerId", triggerId);
    //
    // 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;

    // In Apps Script, clean up stored properties:
    // PropertiesService.getUserProperties().deleteProperty("notifyUri");
    // PropertiesService.getUserProperties().deleteProperty("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.
 *     In Apps Script, defaults to ScriptApp.getOAuthToken().
 */
function simulateEventFire(notifyUri, triggerId, userAccessToken) {
  // In Apps Script, obtain the OAuth token using ScriptApp.getOAuthToken().
  const token = userAccessToken || ScriptApp.getOAuthToken();
  // 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 " + token
    },
    "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 (映射,可选):表示事件数据的启动器输出变量的映射。每个值都是一个 VariableData 对象,支持类型化列表(例如 stringValues、booleanValues、integerValues)。
  • log (对象,可选):Workspace Studio 执行活动日志中显示的 TextFormat 标记表示形式。
  • requestId (字符串,可选):最多 36 个 ASCII 字符的唯一标识符(建议使用 UUID v4),以确保重试时 API 的幂等性。

响应

成功后,响应会返回一个空 JSON 对象 {}。

Workspace Studio API 配额

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

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

配额类型 Quota
每个项目每分钟 1,000 个启动器请求
每位用户每分钟 100 个启动器请求

配额类型包括:

  • 每个项目每分钟:将单个开发者的 Google Cloud 项目中触发的启动器事件的累计数量限制为每分钟 1,000 个请求,适用于运行其启动器的所有用户。
  • 每位用户每分钟:将任何单个最终用户在给定云项目中的启动器调用累计数量限制为每分钟 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 不支持启动器的测试 运行。
  • 幂等性和重放预防:虽然不是严格要求,但您 应添加唯一的 requestId(例如 UUID)到您的 HTTP 或 Apps 脚本载荷中。提供 requestId 可确保幂等性,方法是允许 API 检测并忽略重复通知,防止流程针对单个事件多次运行。
  • 已停用和重新启用的流程:当包含启动器的 流程在 Workspace Studio 中被 停用时,Google 会向 triggerDeletion 生命周期事件发送到您的 onManageFunction 回调。此外,对关联的 FireTrigger 方法的任何调用都会返回 404 Not Found 错误返回代码 (Requested entity was not found.)。您的 服务应通过停止针对该启动器实例 ID 的未来事件通知 传送来响应 404 错误。

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

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

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

  • 触发器触发时出现“权限被拒”(403):如果对 triggers.fire 端点的调用返回 403 PERMISSION_DENIED 并显示 SERVICE_DISABLED 错误状态,请验证:

    • Google Workspace Studio API 已在您的 Google Cloud 项目中启用。
    • 如果使用 Google Apps 脚本,您的项目会关联到标准云项目,而不是默认的 Apps 脚本云项目。
    • OAuth 访问令牌包含 https://www.googleapis.com/auth/workspace.studio.trigger 范围。