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


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

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

目标

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

  1. 在 Google 云端硬盘中创建一个新文件夹。此文件夹用于存储您的电子表格,并允许工作流写入电子表格。
  2. 创建 Google 表格电子表格以捕获审批并启动对工作流的回调。
  3. 使用 Google Apps 脚本(一种基于云端的 JavaScript 平台,可让您以编程方式创建、读取和修改 Google Workspace 产品),在通过更新电子表格批准请求时,触发暂停的工作流恢复。
  4. 创建并部署一个工作流,该工作流会调用 Google Sheets 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、Sheets 和 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、Sheets 和 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 脚本,您可以以编程方式创建、读取和修改 Google 表格。大多数为 Google 表格设计的脚本都会通过操纵数组来与电子表格中的单元格、行和列进行交互。如需了解如何将 Apps 脚本与 Google 表格搭配使用,请参阅自定义函数快速入门

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

    1. 打开您的 Google 表格电子表格。
    2. 依次选择扩展程序 > Apps 脚本
    3. 在脚本编辑器中,点击无标题项目
    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. 为 YOUR_PROJECT_NAME 添加触发器对话框中,配置触发器:
      1. 选择要运行的函数列表中,选择 handleEdit
      2. 选择要运行的部署列表中,选择 Head
      3. 选择活动来源列表中,选择来自电子表格
      4. 选择事件类型列表中,选择 On edit
      5. 失败通知设置列表中,选择每天通知我
    5. 点击保存
    6. 如果您收到选择 Google 账号的提示,请选择相应的账号,然后点击允许

      这可让您的 Apps 脚本项目查看、修改、创建和删除您的 Google 表格电子表格,并连接到外部服务。

  4. Apps 脚本项目的清单文件是一个 JSON 文件,用于指定 Apps 脚本成功运行脚本所需的基本项目信息。请注意,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 Sheets 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-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. 删除工作流

后续步骤