使用回调和 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 API、Sheets API 和 Workflows API。

    启用 API

  4. 记下 Compute Engine 默认服务帐号,因为您要将其与本教程中的工作流相关联以进行测试。对于启用了 Compute Engine API 的新项目,系统将使用 IAM 基本 Editor 角色创建此服务帐号,并使用以下电子邮件格式:

    PROJECT_NUMBER-compute@developer.gserviceaccount.com

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

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

gcloud

  1. 在 Google Cloud 控制台中,激活 Cloud Shell。

    激活 Cloud Shell

    Cloud Shell 会话随即会在 Google Cloud 控制台的底部启动,并显示命令行提示符。Cloud Shell 是一个已安装 Google Cloud CLI 且已为当前项目设置值的 Shell 环境。该会话可能需要几秒钟时间来完成初始化。

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

  3. 启用 Compute Engine API、Sheets API 和 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 Sheets API 直接在指定文件夹中创建电子表格。不过,也有替代方案,例如在创建电子表格后将其移至特定文件夹,如本例所示。如需了解详情,请参阅使用 Google 云端硬盘文件夹

  1. 访问 sheets.google.com

  2. 点击 New Plus

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

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

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

  4. 添加列标题以匹配以下示例:

    用于记录批准的电子表格示例

    请注意,G 列中的值 Approved? 用于在工作流程中启动回调。

  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 表格设计的大多数脚本都会操作数组,以便与电子表格中的单元格、行和列进行交互。如需了解如何在 Google 表格中使用 Apps 脚本,请参阅自定义函数快速入门

  1. 如需通过 Google 表格创建 Apps 脚本项目,请执行以下操作:

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

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

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

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

    1. 打开您的 Apps 脚本项目。
    2. 点击 Editor
    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. 选择事件类型列表中,选择修改时
      5. 失败通知设置列表中,选择每天通知我
    5. 点击保存
    6. 如果您收到选择 Google 帐号的提示,请选择适当的帐号,然后点击允许

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

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

    1. 打开您的 Apps 脚本项目。
    2. 点击 Project Settings 图标
    3. 选中在编辑器中显示“appsscript.json”清单文件复选框。
    4. 点击 Editor
    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

  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

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

  3. 工作流详情页面上,点击 执行

  4. 再次点击执行

    工作流开始运行,并且执行状态应为 Running。这些日志还指示工作流已暂停且正在等待:

    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

    一两分钟后,执行应该会恢复,然后完成,并且执行状态为成功

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. 在 Google Cloud 控制台中,进入管理资源页面。

    转到“管理资源”

  2. 在项目列表中,选择要删除的项目,然后点击删除
  3. 在对话框中输入项目 ID,然后点击关闭以删除项目。

删除在本教程中创建的资源

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

后续步骤