星期六, 7月 11, 2026

[GAS] API 執行檔

根據官方文章-使用 Google Apps Script API 執行函式 的實作筆記,部屬 API 執行檔後在 C# 控制台上使用 OAuth2.0 來存取該 API,該 API 會 insert 資料進入 Google Sheet 內

說明
Apps Script API 提供 scripts.run 方法,可遠端執行指定的 Google Apps Script 函式。您可以在呼叫應用程式中使用這個方法,從遠端執行其中一個指令碼專案中的函式,並接收回應。
需求條件

必須先完成下列事項,才能使用 scripts.run 方法:
  • 將 GAS 部署為 API 執行檔
  • 設定 OAuth Scopes 且必須涵蓋 GAS 使用相關服務,不能只是呼叫函式使用的服務,EX:https://www.googleapis.com/auth/spreadsheets
  • 確認 GAS 和呼叫應用程式的 OAuth2 用戶端共用使用同一個 GCP 專案。GAS 專案必須是標準 GCP 專案,GAS 專案預設 GCP 專案無法使用
  • 在 GCP 專案中啟用 Google Apps Script API。
GAS Code

該 code.gs 單純是 insert 資料進入 Google Sheet 內,可以透過 testAddCheckIn() 進行測試
const SPREADSHEET_ID = '填入 Google Sheet ID';
const SHEET_NAME = 'CheckIn';

/**
 * 本機測試用:可先在 Apps Script 編輯器內執行,確認可正常寫入資料
 */
function testAddCheckIn() {
  return addCheckIn({
    identifier: 'U001',
    meetingCode: 'MEET-001',
    deviceInfo: 'Apps Script Editor Test'
  });
}

/**
 * 給外部 API Executable 呼叫的主函式
 */
function addCheckIn(payload) {
  payload = payload || {};

  const identifier = String(payload.identifier || '').trim();
  const meetingCode = String(payload.meetingCode || '').trim();
  const deviceInfo = String(payload.deviceInfo || '').trim();

  if (!identifier || !meetingCode) {
    return {
      success: false,
      status: 'NO_REQUIRED_DATA',
      message: 'identifier 和 meetingCode 為必填'
    };
  }

  const now = new Date();
  const checkedAt = Utilities.formatDate(
    now,
    'Asia/Taipei',
    'yyyy-MM-dd HH:mm:ss'
  );

  const sheet = getCheckInSheet_();
  sheet.appendRow([
    checkedAt,
    identifier,
    meetingCode,
    deviceInfo,
    'API_EXECUTABLE'
  ]);

  return {
    success: true,
    status: 'CHECK_IN_SUCCESS',
    checkedAt: checkedAt,
    identifier: identifier,
    meetingCode: meetingCode
  };
}

function getCheckInSheet_() {
  const ss = SpreadsheetApp.openById(SPREADSHEET_ID);
  let sheet = ss.getSheetByName(SHEET_NAME);

  if (!sheet) {
    sheet = ss.insertSheet(SHEET_NAME);
    sheet.appendRow(['時間', '識別碼', '會議代碼', '裝置資訊', '來源']);
  }

  return sheet;
}
設定 appsscript.json

點選專案設定並勾選「在編輯器中顯示 appsscript.json 資訊清單檔案」
開啟 appsscript.json 並把 oauthScopes 範圍加進去
{
  "timeZone": "Asia/Taipei",
  "exceptionLogging": "STACKDRIVER",
  "runtimeVersion": "V8",
  "oauthScopes": [
    "https://www.googleapis.com/auth/spreadsheets"
  ]
}

GCP 標準專案

建立標準專案 gas-api-executable-demo 並啟用 Google Apps Script API
利用搜尋功能尋找 Google Apps Script API 服務
在 Google Enterprise API 和 Google Workspace 分類內都可以找到
啟用 Apps Script API 服務
設定 OAuth 同意畫面
GAS 綁定 GCP 標準專案

在標準專案上取得專案編號 (如下圖)
在 GAS 專案上綁定專案標號
綁定完成

該筆記 [GAS] Goolge Cloud Logging 內有紀錄不同順序的設定流程,也可以參考看看

設定用戶端
  • 應用程式類別:選擇電腦版應用程式
  • 名稱:C# Console
按下 [下載 json] 按鈕後,可以下載 credentials.json 檔案,檔案名稱為 client_secret_用戶端ID.apps.googleusercontent.com
部署 API 執行檔

部屬 => 新增部屬 => API 執行檔,誰可以存取」選項有 「只有我自己」、「所以已登入 Google 帳號使用者」
部屬完成
部屬時假如是預設專案的話,會出現下圖並引導至設定內,要求設定為標準專案
C# Console App 呼叫

首先要先確認部屬 ID,下圖為部屬時畫面上的部屬 ID,事後要取得的話,請從管理部屬進入取的
credentials.json 檔案放在 EXE 執行檔案資料夾內
安裝 NuGet
Install-Package Google.Apis.Script.v1
Install-Package Google.Apis.Auth
第一次執行 C# 程式時,瀏覽器會跳出 Google 授權畫面,看要選擇哪一個帳號來執行,該帳號必須是在 GCP 目標對象內
執行結果
token 會儲存在本機 token.json 資料夾,後續執行就不須再次授權
C# Code,必須填入 deploymentId 和 credentialsFileName
using Google.Apis.Auth.OAuth2;
using Google.Apis.Script.v1;
using Google.Apis.Script.v1.Data;
using Google.Apis.Services;
using Google.Apis.Util.Store;

internal class Program
{
    private static async Task Main()
    {
        string deploymentId = "請填入 API Executable 的 Deployment ID";
        string credentialsFileName = "請填入 credentials.json 檔案名稱或是完整檔案路徑";

        string[] scopes =
        {
            "https://www.googleapis.com/auth/spreadsheets"
        };

        UserCredential credential;

        using (var stream = new FileStream(credentialsFileName, FileMode.Open, FileAccess.Read))
        {
            credential = await GoogleWebAuthorizationBroker.AuthorizeAsync(
                GoogleClientSecrets.FromStream(stream).Secrets,
                scopes,
                "user",
                CancellationToken.None,
                new FileDataStore("token.json", true)
            );
        }

        var service = new ScriptService(new BaseClientService.Initializer
        {
            HttpClientInitializer = credential,
            ApplicationName = "GAS API Executable Demo"
        });

        var payload = new Dictionary<string, object>
        {
            { "identifier", "U001" },
            { "meetingCode", "MEET-001" },
            { "deviceInfo", "C# Console App" }
        };

        var request = new ExecutionRequest
        {
            Function = "addCheckIn",
            Parameters = new List<object> { payload }
        };

        Operation operation = await service.Scripts.Run(request, deploymentId).ExecuteAsync();

        if (operation.Error != null)
        {
            Console.WriteLine("GAS 執行失敗:");
            Console.WriteLine(operation.Error.Message);
            return;
        }

        if (operation.Response != null && operation.Response.ContainsKey("result"))
        {
            Console.WriteLine("GAS 回傳結果:");
            Console.WriteLine(operation.Response["result"]);
        }
        else
        {
            Console.WriteLine("GAS 執行完成,但沒有回傳 result。");
        }
    }
}
Google Sheet 內資料

沒有留言:

張貼留言