使用回调和 Google 表格暂停和继续工作流


Google 表格是一款基于云的电子表格解决方案,支持实时协作,并提供用于可视化、处理和传达数据的工具。

本教程演示了如何创建和部署一个工作流,该工作流会创建回调端点(或 webhook)、将回调网址保存到 Google 表格、暂停执行,然后等待通过 Google 表格电子表格获得人工批准以重启工作流。详细了解如何使用回调

目标

在此教程中,您将学习以下操作:

  1. 在 Google 云端硬盘中创建一个新文件夹。此文件夹用于存储电子表格,并允许工作流写入电子表格。
  2. 创建一个 Google 表格电子表格,以捕获批准并发起对工作流的回调。
  3. 使用 Google Apps 脚本(这是一个基于云的 JavaScript 平台,可让您以编程方式创建、读取和修改 Google Workspace 产品),以便在通过更新电子表格批准请求时触发暂停的工作流程的恢复。
  4. 创建并部署一个工作流,用于调用 Google 表格 API 连接器将数据附加到电子表格。工作流会执行、暂停,然后在通过电子表格批准回调后恢复。详细了解 Workflows 连接器
  5. 测试整个流程,并确认工作流是否按预期进行。

费用

在本文档中,您将使用 Google Cloud 的以下收费组件:

您可使用价格计算器根据您的预计使用情况来估算费用。 Google Cloud 新用户可能有资格申请免费试用

本教程还使用 Google Workspace。Google 向个人用户免费提供的应用所不具备的企业级服务需付费。

准备工作

您可以在 Google Cloud 控制台中运行以下部分命令,也可以在终端或 Cloud Shell 中使用 Google Cloud CLI 运行这些命令。

您的组织定义的安全限制条件可能会导致您无法完成以下步骤。如需了解相关问题排查信息,请参阅在受限的 Google Cloud 环境中开发应用

控制台

  1. 在 Google Cloud 控制台的“项目选择器”页面上,选择或创建 Google Cloud 项目

    转到“项目选择器”

  2. 确保您的 Google Cloud 项目已启用结算功能。了解如何检查项目是否已启用结算功能

  3. 启用 Compute Engine、Google 表格和 Workflows API。

    启用 API

  4. 记下 Compute Engine 默认服务账号,因为您将把它与本教程中的工作流相关联以进行测试。已启用 Compute Engine API 的新项目会使用 IAM 基本 Editor 角色创建此服务账号,并且电子邮件地址格式如下:

    PROJECT_NUMBER-compute@developer.gserviceaccount.com

    您可以在 Google Cloud 控制台的欢迎页面上找到项目编号。

    对于生产环境,我们强烈建议创建新的服务账号,并为其授予一个或多个 IAM 角色,这些角色包含所需的最小权限并遵循最小权限原则。

gcloud

  1. In the Google Cloud console, activate Cloud Shell.

    Activate Cloud Shell

    At the bottom of the Google Cloud console, a Cloud Shell session starts and displays a command-line prompt. Cloud Shell is a shell environment with the Google Cloud CLI already installed and with values already set for your current project. It can take a few seconds for the session to initialize.

  2. 确保您的 Google Cloud 项目已启用结算功能。 了解如何检查项目是否已启用结算功能

  3. 启用 Compute Engine、Google 表格和 Workflows API。

    gcloud services enable \
        compute.googleapis.com \
        sheets.googleapis.com \
        workflows.googleapis.com
  4. 记下 Compute Engine 默认服务账号,因为您将把它与本教程中的工作流相关联以进行测试。已启用 Compute Engine API 的新项目会使用 IAM 基本 Editor 角色创建此服务账号,并且电子邮件地址格式如下:

    PROJECT_NUMBER-compute@developer.gserviceaccount.com

    您可以通过以下方式检索项目编号:

    gcloud projects describe PROJECT_ID

    对于生产环境,我们强烈建议创建新的服务账号,并为其授予一个或多个 IAM 角色,这些角色包含所需的最小权限并遵循最小权限原则。

在 Google 云端硬盘中创建新文件夹

在 Google 云端硬盘中创建一个新文件夹。此文件夹用于存储您的电子表格。为共享文件夹设置权限后,您的工作流就可以写入电子表格了。

  1. 转到 drive.google.com
  2. 依次点击新建 > 新建文件夹
  3. 输入文件夹名称。
  4. 点击创建
  5. 右键点击新建的文件夹,然后选择共享
  6. 添加 Compute Engine 默认服务账号的电子邮件地址。

    这会向服务账号授予对该文件夹的访问权限。将服务账号与工作流关联后,工作流将拥有对文件夹中所有文件的修改权限。详细了解如何共享文件、文件夹和云端硬盘

  7. 选择编辑者角色。

  8. 取消选中通知对方复选框。

  9. 点击分享

使用 Google 表格创建电子表格

您通过 Google 表格创建电子表格时,系统会将其保存在 Google 云端硬盘中。默认情况下,电子表格会保存到云端硬盘的根文件夹中。无法使用 Google 表格 API 直接在指定文件夹中创建电子表格。不过,您可以使用其他方法,包括在创建电子表格后将其移至特定文件夹(如本例所示)。如需了解详情,请参阅使用 Google 云端硬盘文件夹

  1. 前往 sheets.google.com

  2. 点击新建图标 Plus

    此操作会创建并打开您的新电子表格。每个电子表格都有一个唯一的 spreadsheetId 值,其中包含字母、数字、连字符或下划线。您可以在 Google 表格网址中找到电子表格 ID:

    https://docs.google.com/spreadsheets/d/spreadsheetId/edit#gid=0

  3. 请记下此 ID,因为您在创建工作流时需要用到它。

  4. 添加列标题,使其与以下示例相符:

    用于记录审批情况的电子表格示例

    请注意,G 列中的值“已批准?”用于在工作流中发起回调。

  5. 将电子表格移至您之前创建的 Google 云端硬盘文件夹:

    1. 在电子表格中,依次选择文件 > 移动
    2. 前往您创建的文件夹。
    3. 点击移动

您还可以使用 Google Sheets API 连接器创建电子表格。请注意,使用该连接器时,可以从 resp 结果中检索 spreadsheetId。例如:

- create_spreadsheet:
    call: googleapis.sheets.v4.spreadsheets.create
    args:
      body:
      connector_params:
        scopes: ${driveScope}
    result: resp
- assign_sheet_id:
    assign:
      - sheetId: ${resp.spreadsheetId}

使用 Apps 脚本扩展 Google 表格

借助 Apps Script,您可以以编程方式创建、读取和修改 Google 表格。大多数专为 Google 表格设计的脚本都会操控数组,以便与电子表格中的单元格、行和列进行交互。如需简要了解如何将 Apps 脚本与 Google 表格搭配使用,请参阅自定义函数快速入门

  1. 通过 Google 表格创建 Apps 脚本项目:

    1. 打开您的 Google 表格电子表格。
    2. 依次选择扩展程序 > Apps 脚本
    3. 在脚本编辑器中,点击 Untitled project(无标题项目)。
    4. 为项目命名,然后点击重命名

    您的脚本现在已绑定到电子表格,这使脚本具有特殊能力,可以在打开电子表格时更改界面或做出响应。

    脚本项目表示一组 Apps 脚本文件和资源。脚本项目中的代码文件扩展名为 .gs

  2. 您可以使用 Apps 脚本编写自定义函数,并在 Google 表格中使用这些函数,就像使用内置函数一样。自定义函数是使用标准 JavaScript 创建的。创建函数:

    1. 打开您的 Apps 脚本项目。
    2. 点击编辑器
    3. 脚本文件显示为名为 Code.gs 的项目文件。如需修改文件,请选择相应文件。
    4. 将脚本编辑器中的所有代码替换为以下代码,该代码会读取电子表格中的数据,并将其作为输入传递给工作流执行:

      function handleEdit(e) {
        var range = e.range.getA1Notation();
        var sheet = e.source;
      
        if (range.length > 1 && range[0] === 'G') {
          if (e.value == "TRUE") {
            Logger.log("Approved: TRUE");
      
            var row = range.slice(1);
            var url = sheet.getRange('E' + row).getCell(1, 1).getValue();
            var approver = sheet.getRange('F' + row).getCell(1, 1).getValue();
      
            callback(url, approver);
          }
          else {
            Logger.log("Approved: FALSE");
          }
        }
      }
      
      function callback(url, approver) {
        const headers = {
          "Authorization": "Bearer " + ScriptApp.getOAuthToken()
        };
      
        var payload = {
          'approver': approver
        };
      
        const params = {
          "method": 'POST',
          "contentType": 'application/json',
          "headers": headers,
          "payload": JSON.stringify(payload)
        };
      
      
        Logger.log("Workflow callback request to " + url);
        var response = UrlFetchApp.fetch(url, params);
        Logger.log(response);
      }
    5. 点击“保存”图标

  3. 借助 Apps 脚本可安装的触发器,脚本项目可以在满足特定条件(例如打开或修改电子表格)时执行指定函数。创建触发器:

    1. 打开您的 Apps 脚本项目。
    2. 点击触发器
    3. 点击添加触发器
    4. Add Trigger for YOUR_PROJECT_NAME 对话框中,配置触发器:
      1. 选择要运行的函数列表中,选择 handleEdit
      2. 选择要执行的部署列表中,选择主分支
      3. 选择事件来源列表中,选择从电子表格
      4. 选择事件类型列表中,选择修改时
      5. 失败通知设置列表中,选择每天通知我
    5. 点击保存
    6. 如果系统提示您选择 Google 账号,请选择相应的账号,然后点击允许

      这样,您的 Apps Script 项目便可以查看、修改、创建和删除您的 Google 表格电子表格,以及连接到外部服务。

  4. Apps Script 项目清单文件是一个 JSON 文件,用于指定 Apps Script 成功运行脚本所需的基本项目信息。请注意,Apps 脚本编辑器默认会隐藏清单文件,以保护您的 Apps 脚本项目设置。修改清单文件:

    1. 打开您的 Apps 脚本项目。
    2. 点击项目设置
    3. 选中在编辑器中显示“appsscript.json”清单文件复选框。
    4. 点击编辑器
    5. 清单文件显示为名为 appsscript.json 的项目文件。如需修改文件,请选择该文件。
    6. oauthScopes 字段指定一个字符串数组。如需设置您的项目使用的授权范围,请添加一个包含要支持的范围的数组。例如:

      {
        "timeZone": "America/Toronto",
        "dependencies": {
        },
        "exceptionLogging": "STACKDRIVER",
        "runtimeVersion": "V8",
        "oauthScopes": [
          "https://www.googleapis.com/auth/script.external_request",
          "https://www.googleapis.com/auth/cloud-platform",
          "https://www.googleapis.com/auth/spreadsheets"
        ]
      }

      这会将显式镜重范围设置为:

      • 连接到外部服务
      • 查看、修改、配置和删除您的 Google Cloud 数据,并查看您 Google 账号的电子邮件地址
      • 查看、修改和删除您使用 Google 表格创建的所有电子表格以及创建这种电子表格
    7. 点击“保存”图标

部署一个会写入电子表格并使用回调的工作流

部署一个工作流,以便在通过电子表格批准回调时执行、暂停,然后恢复。该工作流使用 Google 表格 API 连接器将数据写入 Google 表格电子表格。

控制台

  1. 在 Google Cloud 控制台中,前往 Workflows 页面:

    进入 Workflows

  2. 点击 创建

  3. 输入新工作流的名称:workflows-awaits-callback-sheets

  4. 区域列表中,选择 us-central1(爱荷华)

  5. 对于服务账号,请选择 Compute Engine 默认服务账号 (PROJECT_NUMBER-compute@developer.gserviceaccount.com)。

  6. 点击下一步

  7. 在工作流编辑器中,输入工作流的定义:

    main:
      steps:
        - init:
            assign:
            # Replace with your sheetId and make sure the service account
            # for the workflow has write permissions to the sheet
            - sheetId: "10hieAH6b-oMeIVT_AerSLNxQck14IGhgi8ign-x2x8g"
        - before_sheets_callback:
            call: sys.log
            args:
              severity: INFO
              data: ${"Execute steps here before waiting for callback from sheets"}
        - wait_for_sheets_callback:
            call: await_callback_sheets
            args:
              sheetId: ${sheetId}
            result: await_callback_result
        - after_sheets_callback:
            call: sys.log
            args:
              severity: INFO
              data: ${"Execute steps here after receiving callback from sheets"}
        - returnResult:
            return: ${await_callback_result}
    
    await_callback_sheets:
        params: [sheetId]
        steps:
            - init:
                assign:
                  - project_id: ${sys.get_env("GOOGLE_CLOUD_PROJECT_ID")}
                  - location: ${sys.get_env("GOOGLE_CLOUD_LOCATION")}
                  - workflow_id: ${sys.get_env("GOOGLE_CLOUD_WORKFLOW_ID")}
                  - execution_id: ${sys.get_env("GOOGLE_CLOUD_WORKFLOW_EXECUTION_ID")}
            - create_callback:
                call: events.create_callback_endpoint
                args:
                  http_callback_method: POST
                result: callback_details
            - save_callback_to_sheets:
                call: googleapis.sheets.v4.spreadsheets.values.append
                args:
                    range: ${"Sheet1!A1:G1"}
                    spreadsheetId: ${sheetId}
                    valueInputOption: RAW
                    body:
                        majorDimension: "ROWS"
                        values:
                          - ["${project_id}", "${location}", "${workflow_id}", "${execution_id}", "${callback_details.url}", "", "FALSE"]
            - log_and_await_callback:
                try:
                  steps:
                    - log_await_start:
                        call: sys.log
                        args:
                          severity: INFO
                          data: ${"Started waiting for callback from sheet " + sheetId}
                    - await_callback:
                        call: events.await_callback
                        args:
                          callback: ${callback_details}
                          timeout: 3600
                        result: callback_request
                    - log_await_stop:
                        call: sys.log
                        args:
                          severity: INFO
                          data: ${"Stopped waiting for callback from sheet " + sheetId}
                except:
                    as: e
                    steps:
                        - log_error:
                            call: sys.log
                            args:
                                severity: "ERROR"
                                text: ${"Received error " + e.message}
            - check_null_await_result:
                switch:
                  - condition: ${callback_request == null}
                    return: null
            - log_await_result:
                call: sys.log
                args:
                  severity: INFO
                  data: ${"Approved by " + callback_request.http_request.body.approver}
            - return_await_result:
                return: ${callback_request.http_request.body}
  8. 请务必将占位符 sheetId 值替换为您的 spreadsheetId

  9. 点击部署

gcloud

  1. 为您的工作流创建源代码文件:

    touch workflows-awaits-callback-sheets.yaml
  2. 在文本编辑器中,将以下工作流复制到源代码文件中:

    main:
      steps:
        - init:
            assign:
            # Replace with your sheetId and make sure the service account
            # for the workflow has write permissions to the sheet
            - sheetId: "10hieAH6b-oMeIVT_AerSLNxQck14IGhgi8ign-x2x8g"
        - before_sheets_callback:
            call: sys.log
            args:
              severity: INFO
              data: ${"Execute steps here before waiting for callback from sheets"}
        - wait_for_sheets_callback:
            call: await_callback_sheets
            args:
              sheetId: ${sheetId}
            result: await_callback_result
        - after_sheets_callback:
            call: sys.log
            args:
              severity: INFO
              data: ${"Execute steps here after receiving callback from sheets"}
        - returnResult:
            return: ${await_callback_result}
    
    await_callback_sheets:
        params: [sheetId]
        steps:
            - init:
                assign:
                  - project_id: ${sys.get_env("GOOGLE_CLOUD_PROJECT_ID")}
                  - location: ${sys.get_env("GOOGLE_CLOUD_LOCATION")}
                  - workflow_id: ${sys.get_env("GOOGLE_CLOUD_WORKFLOW_ID")}
                  - execution_id: ${sys.get_env("GOOGLE_CLOUD_WORKFLOW_EXECUTION_ID")}
            - create_callback:
                call: events.create_callback_endpoint
                args:
                  http_callback_method: POST
                result: callback_details
            - save_callback_to_sheets:
                call: googleapis.sheets.v4.spreadsheets.values.append
                args:
                    range: ${"Sheet1!A1:G1"}
                    spreadsheetId: ${sheetId}
                    valueInputOption: RAW
                    body:
                        majorDimension: "ROWS"
                        values:
                          - ["${project_id}", "${location}", "${workflow_id}", "${execution_id}", "${callback_details.url}", "", "FALSE"]
            - log_and_await_callback:
                try:
                  steps:
                    - log_await_start:
                        call: sys.log
                        args:
                          severity: INFO
                          data: ${"Started waiting for callback from sheet " + sheetId}
                    - await_callback:
                        call: events.await_callback
                        args:
                          callback: ${callback_details}
                          timeout: 3600
                        result: callback_request
                    - log_await_stop:
                        call: sys.log
                        args:
                          severity: INFO
                          data: ${"Stopped waiting for callback from sheet " + sheetId}
                except:
                    as: e
                    steps:
                        - log_error:
                            call: sys.log
                            args:
                                severity: "ERROR"
                                text: ${"Received error " + e.message}
            - check_null_await_result:
                switch:
                  - condition: ${callback_request == null}
                    return: null
            - log_await_result:
                call: sys.log
                args:
                  severity: INFO
                  data: ${"Approved by " + callback_request.http_request.body.approver}
            - return_await_result:
                return: ${callback_request.http_request.body}
  3. 请务必将占位符 sheetId 值替换为您的 spreadsheetId

  4. 输入以下命令以部署工作流:

    gcloud workflows deploy workflows-awaits-callback-sheets \
        --source=workflows-awaits-callback-sheets.yaml \
        --location=us-central1 \
        --service-account=PROJECT_NUMBER-compute@developer.gserviceaccount.com

    PROJECT_NUMBER 替换为您的 Google Cloud 项目编号。您可以通过以下方式检索项目编号:

    gcloud projects describe PROJECT_ID

测试端到端流程

执行工作流以测试端到端流程。执行某个工作流会运行与该工作流关联的当前工作流定义。

控制台

  1. 在 Google Cloud 控制台中,前往 Workflows 页面:

    进入 Workflows

  2. Workflows 页面上,选择 workflows-awaits-callback-sheets 工作流以转到其详情页面。

  3. 工作流详情页面上,选择 执行

  4. 再次点击执行

    工作流会开始运行,其执行状态应为正在运行。日志还表明工作流已暂停并处于等待状态:

    Execute steps here before waiting for callback from sheets
    ...
    Started waiting for callback from sheet 1JlNFFnqs760M_KDqeeeDc_qtrABZDxoalyCmRE39dpM
  5. 验证工作流是否已将回调详细信息写入电子表格中的某个行。

    例如,您应该会在“执行 ID”列中看到工作流执行 ID,在“回调网址”列中看到回调端点,并在“已批准?”列中看到“FALSE”。

  6. 在电子表格中,将 FALSE 更改为 TRUE

    一两分钟后,执行应会恢复,然后完成,执行状态为 Succeeded

gcloud

  1. 打开终端。

  2. 执行工作流:

      gcloud workflows run workflows-awaits-callback-sheets

    工作流会启动,并且输出应指示工作流已暂停并正在等待:

      Waiting for execution [a8361789-90e0-467f-8bd7-ea1c81977820] to complete...working.

  3. 验证工作流是否已将回调详细信息写入到电子表格中的某个行。

    例如,您应该会在“执行 ID”列中看到工作流执行 ID,在“回调网址”列中看到回调端点,并在“已批准?”列中看到“FALSE”。

  4. 在电子表格中,将 FALSE 更改为 TRUE

    一两分钟后,执行应会恢复,然后完成,执行状态为 SUCCEEDED

清理

如果您为本教程创建了一个新项目,请删除项目。 如果您使用的是现有项目,希望保留此项目且不保留本教程中添加的任何更改,请删除为教程创建的资源

删除项目

为了避免产生费用,最简单的方法是删除您为本教程创建的项目。

要删除项目,请执行以下操作:

  1. In the Google Cloud console, go to the Manage resources page.

    Go to Manage resources

  2. In the project list, select the project that you want to delete, and then click Delete.
  3. In the dialog, type the project ID, and then click Shut down to delete the project.

删除本教程中创建的资源

  1. 删除 Google 云端硬盘中的文件
  2. 删除工作流

后续步骤