---
title: "伺服器端 Action、排程與 Webhook"
category: "app-development"
canonical: "https://www.ai-go.app/zh-TW/docs/guide/app-actions"
locale: "zh-TW"
last_updated: "2026-08-10"
source: "AI GO 開發者文件（https://www.ai-go.app）"
---

# 伺服器端 Action、排程與 Webhook

Action 是 Custom App 的伺服器端邏輯。這一章說明怎麼寫、怎麼被觸發、以及執行時的實際限制。

---

## 為什麼需要 Action

前端程式碼跑在使用者的瀏覽器裡——這代表任何寫在前端的東西，使用者都看得到、也改得動。

因此以下三類邏輯必須放進 Action：

1. **需要金鑰的**：呼叫第三方 API、簽章、加解密
2. **需要高權限的**：觸發 ERP 業務動作、寫入敏感資料
3. **不該被看到的**：計價規則、風控邏輯、內部演算法

Action 在隔離的伺服器環境執行，使用者拿不到原始碼，也無法繞過裡面的檢查。

---

## Action 的基本結構

每個 Action 是 `actions/` 底下的一支 Python 檔，必須提供 `execute(ctx)` 函式：

```python
# actions/confirm_orders.py
def execute(ctx):
    ids = ctx.params.get('ids', [])

    if 'sale.write' not in ctx.user_permissions:
        return ctx.response.json({'error': '沒有權限'})

    confirmed = []
    for oid in ids:
        ctx.erp.confirm_sale_order(oid)
        confirmed.append(oid)

    ctx.response.json({'confirmed': confirmed})
```

### `actions/manifest.json`

清單檔宣告有哪些 Action 以及它們的設定：

```json
{
  "actions": [
    {
      "name": "confirm_orders",
      "description": "批次確認銷售訂單",
      "meta": { "webhook": false }
    },
    {
      "name": "receive_payment_callback",
      "description": "接收金流回呼",
      "meta": { "webhook": true }
    }
  ]
}
```

### 共用程式碼：`actions/_shared/`

Action 之間要共用邏輯時，放進 `actions/_shared/`：

```python
# actions/_shared/money.py —— 不是 Action，不需要 execute(ctx)
def round_to_cents(value):
    return round(value + 1e-9, 2)
```

```python
# actions/settle.py
from _shared.money import round_to_cents

def execute(ctx):
    total = round_to_cents(sum(x['amount'] for x in ctx.params['items']))
    ...
```

**注意**：Action 彼此之間不能互相 import——`from confirm_orders import ...` 不會work。要共用就把邏輯移到 `_shared/`，兩邊都從那裡匯入。

---

## 三種觸發方式

### 1. 前端呼叫

最常見的方式。使用者在介面上操作，前端透過 `src/action.ts` 呼叫：

```ts
import { callAction } from './action';
const res = await callAction('confirm_orders', { ids: [...] });
```

### 2. 排程（App Cron）

讓 Action 在指定時間自動執行，不需要有人在線上。

**設定入口有兩個，各自的授權面不同**：

| 入口 | 誰用 | 權限 |
|---|---|---|
| Builder 的「排程」分頁 | 應用開發者 | `builder.app_cron_manage` |
| 主控台「系統與維運管理 › App 排程」 | 組織管理者 | `system.admin` 或 `settings.write` |

前者讓開發者在開發自己的應用時就能配好排程；後者讓管理者總覽全組織的所有排程。

**典型場景**：

- 每天凌晨彙整前一日銷售並產生報表
- 每小時檢查庫存低於警戒值的品項並通知
- 每月為月結客戶批次合併開票

**執行身分**：排程觸發的 Action **沒有觸發者**。`ctx.user_id` 為空、`ctx.user_permissions` 為空清單。這代表：

- 依賴使用者身分的 SDK（如 `ctx.approval.list_pending`）在排程中不可用
- `ctx.knowledge.search` 會以「無角色成員」的權限檢索
- 簽核守衛仍會生效，簽核介面上的申請人會顯示為 API

**自動暫停**：連續失敗的排程會被自動暫停，避免一個壞掉的任務持續消耗配額。暫停後需要人工檢視並重新啟用。

### 3. Webhook（外部系統呼叫）

在 `manifest.json` 中把某個 Action 的 `meta` 加上 `"webhook": true`，即開啟對外端點：

```
POST https://{app 子網域}/webhook/{action_name}
```

可以對多個 Action 分別宣告，各自成為獨立端點。發布後生效。

**典型場景**：金流回呼、物流狀態推送、第三方系統的事件通知。

**安全提醒**：Webhook 端點是**公開的**——任何人知道網址都能打。請務必在 Action 內驗證來源，常見做法是用 `ctx.crypto.hmac_sign` 比對簽章：

```python
def execute(ctx):
    body = ctx.params.get('_raw_body', '')
    expected = ctx.crypto.hmac_sign('webhook_secret', body)
    if ctx.params.get('signature') != expected:
        return ctx.response.json({'error': 'invalid signature'})
    ...
```

---

## 執行環境與限制

### 隔離

每個 Action 在獨立的沙箱環境執行。應用程式之間、組織之間互不可見。

### 逾時

Action 有執行時間上限。超時的處理分三層：

1. **軟逾時**：達到設定的時限時，平台嘗試中斷執行並回報逾時
2. **硬逾時**：軟逾時無效時，執行環境會直接切斷請求
3. **殘留偵測**：若執行緒無法回收，該執行個體會標記為不健康並被替換

實務意義：**Action 不適合做長時間運算**。需要跑很久的工作，請拆成多次排程執行，或把重活交給外部服務再以 Webhook 接回結果。

### 併發

平台會依用量自動擴充執行個體，並受組織的運算配額約束。

### 執行紀錄

每次執行都留下紀錄，包含參數摘要、耗時、結果狀態與錯誤訊息。可在 Builder 中檢視，AI 開發助手也能讀取它來協助排錯。

若執行結果因基礎設施問題無法確認，紀錄會標記為「結果不明」——這種狀態**永不計費、不重跑**，並在真實結果遲到時自動覆蓋。

---

## 撰寫 Action 的實務建議

**檢查權限，不要假設**。前端藏起來的按鈕不構成安全邊界：

```python
if 'accounting.write' not in ctx.user_permissions:
    return ctx.response.json({'error': '沒有權限執行此操作'})
```

**檢查外部呼叫的回傳狀態**。`ctx.http.call` 不拋例外：

```python
resp = ctx.http.call('svc', '/path')
if resp['status'] != 200:
    return ctx.response.json({'error': resp['data'].get('fix', '外部服務錯誤')})
```

**避免阻塞式寫法**。長時間的同步等待會佔住執行個體。平台的靜態檢查會警告這類寫法。

**先試跑再發布**。Builder 的試跑功能會在開發環境真的執行一次，用真實的權限與資料驗證——比讀程式碼可靠得多。

---

## 簽核會攔截寫入

如果管理者為某張表設定了簽核流程，Action 的寫入會被自動攔截：

- **新增**：資料保持為待審核狀態，直到所有關卡通過
- **更新／刪除**：**不執行**，並拋出待簽核例外（帶申請識別碼）。更新的內容會暫存，簽核通過後由平台自動執行

**Action 不需要也不應該重試**——重試只會產生重複的簽核申請。正確做法是捕捉例外並告訴使用者「已送出簽核」。

詳見第 13 章。