Activepieces 0.92.3 功能验证手册

Activepieces 0.92.3 功能验证手册:注册与令牌、连接器同步、工作流导入发布、Webhook/定时触发、Code/HTTP/循环/路由分支、全局变量、连接器凭据与异常路径的 REST API 实测验收

ActivepiecesActivepieces自动化2026/10/18

Activepieces 0.92.3 功能验证手册

目标:对已部署的 Activepieces 0.92.3 社区版做一次端到端功能验收:账号注册与令牌、连接器元数据同步、工作流创建/导入/发布、Webhook 与定时两类触发、Code/HTTP/循环/路由分支等动作组件、全局变量、连接器凭据、运行历史与错误呈现,全部通过 REST API 实测并与预期逐项比对。本文全部命令与回显均在 Ubuntu 22.04 实测通过(实测日期:2026-10-18)。

一、环境信息

项目值
服务地址http://192.168.146.128:8100(Ubuntu 22.04 服务器)
平台版本Activepieces 0.92.3 社区版(API + Worker 分离形态)
管理账号admin@example.com / q1w2e3r4
管理项目 IDptCNUnkh0eHoNtIFJWA2C
验证方式REST API(服务端内置,前缀 /api/v1,认证头 Authorization: Bearer <token>)
外部依赖https://httpbin.org(HTTP 步骤回显目标,出口可达)

REST API 的完整端点定义可在服务自带的 OpenAPI 规范中查询(官方文档站 API Reference,共 67 个端点),本手册按验证路径选用其中认证、flows、webhooks、flow-runs、pieces、variables、app-connections 七组。

二、账号注册与 API 令牌

2.1 注册首个管理员

全新部署后没有任何用户,第一个注册的账号自动成为平台管理员(platformRole=ADMIN):

curl -s -m 15 -X POST http://192.168.146.128:8100/api/v1/authentication/sign-up \
  -H 'Content-Type: application/json' \
  -d '{"email":"admin@example.com","password":"q1w2e3r4","firstName":"Admin","lastName":"User","trackEvents":false,"newsLetter":false}' \
  -w '\nHTTP_STATUS:%{http_code}\n' | python3 -c "
import sys,json
raw=sys.stdin.read()
body,_,tail=raw.rpartition('HTTP_STATUS:')
d=json.loads(body)
print('注册邮箱:', d.get('email'))
print('平台角色:', d.get('platformRole'))
print('自动创建项目 id:', d.get('projectId'))
print('HTTP_STATUS:'+tail)
"

实测回显:

注册邮箱: admin@example.com
平台角色: ADMIN
自动创建项目 id: ptCNUnkh0eHoNtIFJWA2C
HTTP_STATUS:200

预期比对:注册接口 200、返回平台角色 ADMIN、自动创建首个项目,符合"首个用户即管理员"的设计。注册接口只对空库开放(已有用户时返回 409 或权限错误),这是需要重点确认的安全特性。

2.2 登录获取会话令牌

curl -s -m 15 -X POST http://192.168.146.128:8100/api/v1/authentication/sign-in \
  -H 'Content-Type: application/json' \
  -d '{"email":"admin@example.com","password":"q1w2e3r4"}' | python3 -c "
import sys,json
d=json.load(sys.stdin)
open('/tmp/ap-token.txt','w').write(d['token'])
print('token 前缀:', d['token'][:30]+'...')
print('用户:', d.get('email'), '| 角色:', d.get('platformRole'))
"

实测回显:

token 前缀: eyJhbGciOiJIUzI1NiIsInR5c...
用户: admin@example.com | 角色: ADMIN

预期比对:返回 JWT 会话令牌,后续所有 API 请求以 Authorization: Bearer <token> 认证(下文示例统一用 $TOKEN 指代)。社区版(CE)平台层鉴权只有 JWT 会话一种方式;管理页面的 sk- 平台 API Key 属于商业版能力,CE 环境直接用 sk- 密钥调 API 会得到 401(实测验证),按需购买商业许可即可。

三、连接器元数据同步

Activepieces 的连接器(piece)元数据由服务端从官方 registry 同步后缓存在本地数据库,画布上能用哪些连接器取决于同步结果。全新部署后第一次查询可能为空:

curl -s -m 30 "http://192.168.146.128:8100/api/v1/pieces" \
  -H "Authorization: Bearer $TOKEN" | python3 -c "import sys,json;print('pieces:',len(json.load(sys.stdin)))"

实测回显(首次):

pieces: 0

手动触发一次全量同步:

curl -s -m 300 -X POST "http://192.168.146.128:8100/api/v1/pieces/sync" \
  -H "Authorization: Bearer $TOKEN" -H 'Content-Type: application/json' -d '{}' \
  -w '\nHTTP:%{http_code}\n'

实测回显:

HTTP:200

同步在后台分批进行,轮询确认直至目标连接器就位:

curl -s -m 30 "http://192.168.146.128:8100/api/v1/pieces" \
  -H "Authorization: Bearer $TOKEN" | python3 -c "
import sys,json
d=json.load(sys.stdin)
print('pieces 总数:', len(d))
names={p['name']:p['version'] for p in d}
for n in ('@activepieces/piece-webhook','@activepieces/piece-http','@activepieces/piece-schedule'):
    print(n, '->', names.get(n))
"

实测回显:

pieces 总数: 769
@activepieces/piece-webhook -> 0.1.42
@activepieces/piece-http -> 0.13.0
@activepieces/piece-schedule -> 0.1.22

预期比对:同步完成后 pieces 接口可查 769 条连接器元数据,本文后续步骤依赖的 webhook/http/schedule 三个核心连接器全部就位且版本明确。若长时间停在 0,通常是服务器访问 npm registry 不稳,见部署篇常见问题的代理方案。

四、工作流创建与发布

4.1 创建空工作流

curl -s -m 15 -X POST http://192.168.146.128:8100/api/v1/flows \
  -H "Authorization: Bearer $TOKEN" -H 'Content-Type: application/json' \
  -d '{"displayName":"webhook-demo-flow","projectId":"ptCNUnkh0eHoNtIFJWA2C"}' | python3 -c "
import sys,json
d=json.load(sys.stdin)
print('flow id:', d['id'])
print('版本状态:', d['version']['state'], '| 有效:', d['version']['valid'])
"

实测回显:

flow id: iHQTB0tlaNLEGDy5dSSFV
版本状态: DRAFT | 有效: False

预期比对:新工作流处于 DRAFT 草稿态,valid: False 表示还没有可运行的触发器定义。

4.2 导入工作流定义

通过 IMPORT_FLOW 操作一次性写入触发器与动作链。本工作流的结构:Webhook 触发器(catch_webhook,免认证)→ Code 步骤(计算含 6% 税费的价格)→ HTTP 步骤(POST 到 httpbin.org):

cat > flow1-def.json <<'EOF'
{"type":"IMPORT_FLOW","request":{"displayName":"webhook-demo-flow","trigger":{"name":"trigger","valid":true,"displayName":"Catch Webhook","lastUpdatedDate":"2026-10-18T08:50:00.000Z","type":"PIECE_TRIGGER","settings":{"propertySettings":{},"pieceName":"@activepieces/piece-webhook","pieceVersion":"~0.1.42","triggerName":"catch_webhook","input":{"authType":"none"}},"nextAction":{"name":"process_code","valid":true,"displayName":"Process Order","lastUpdatedDate":"2026-10-18T08:50:00.000Z","type":"CODE","settings":{"sourceCode":{"code":"export const code = async (inputs) => {\n    const amount = Number(inputs.amount);\n    return {\n        order_id: inputs.order_id,\n        customer: inputs.customer,\n        amount: amount,\n        final_price: Math.round(amount * 1.06 * 100) / 100\n    };\n};"},"input":{"order_id":"{{trigger.body.order_id}}","customer":"{{trigger.body.customer}}","amount":"{{trigger.body.amount}}"}},"nextAction":{"name":"notify_http","valid":true,"displayName":"Send HTTP request","lastUpdatedDate":"2026-10-18T08:50:00.000Z","type":"PIECE","settings":{"propertySettings":{},"pieceName":"@activepieces/piece-http","pieceVersion":"~0.13.0","actionName":"send_request","input":{"method":"POST","url":"https://httpbin.org/post","headers":{},"queryParams":{},"authType":"NONE","body_type":"json","body":{"data":{"order_id":"{{process_code.order_id}}","final_price":"{{process_code.final_price}}"}}}},"nextAction":null}}}}}
EOF

curl -s -m 30 -X POST http://192.168.146.128:8100/api/v1/flows/iHQTB0tlaNLEGDy5dSSFV \
  -H "Authorization: Bearer $TOKEN" -H 'Content-Type: application/json' \
  -d @flow1-def.json -o flow1-resp.json -w 'HTTP_STATUS:%{http_code}\n'

python3 -c "
import json
d=json.load(open('flow1-resp.json'))
v=d['version']
print('版本状态:', v['state'], '| 有效:', v['valid'])
steps=[]
a=v['trigger']
while a:
    steps.append(a['displayName'])
    a=a.get('nextAction')
print('步骤链:', ' -> '.join(steps))
"

实测回显:

HTTP_STATUS:200
版本状态: DRAFT | 有效: True
步骤链: Catch Webhook -> Process Order -> Send HTTP request

预期比对:导入后工作流结构完整(触发器 + 2 个动作),valid: True 表示所有步骤参数通过了连接器 schema 校验。注意 pieceVersion 用 ~ 锁定大版本(如 ~0.1.42),与连接器元数据对齐后才能通过校验——若引用了未同步或版本不存在的连接器,发布时会报 PINC 类错误。

4.3 发布(锁定运行版本)

curl -s -m 60 -X POST http://192.168.146.128:8100/api/v1/flows/iHQTB0tlaNLEGDy5dSSFV \
  -H "Authorization: Bearer $TOKEN" -H 'Content-Type: application/json' \
  -d '{"type":"LOCK_AND_PUBLISH","request":{"status":"ENABLED"}}' | python3 -c "
import sys,json
d=json.load(sys.stdin)
print('工作流状态:', d.get('status'))
print('已发布版本 id:', d.get('publishedVersionId'))
v=d.get('version',{})
print('版本状态:', v.get('state'))
"

实测回显:

工作流状态: ENABLED
已发布版本 id: bhltBIf1fxOracYond6tU
版本状态: LOCKED

预期比对:发布后 status: ENABLED、版本进入 LOCKED 态并生成 publishedVersionId。此后画布上的草稿改动不再影响线上运行,直到下一次发布——这是 Activepieces 的草稿/发布双版本模型。

五、Webhook 触发验证

5.1 触发并确认同步响应

发布后即可向平台的 Webhook 入口发请求(路径为 /api/v1/webhooks/<flowId>):

curl -s -m 30 -X POST http://192.168.146.128:8100/api/v1/webhooks/iHQTB0tlaNLEGDy5dSSFV \
  -H 'Content-Type: application/json' \
  -d '{"order_id":"A-2048","amount":299.9,"customer":"Han Meimei"}'

实测回显:

{}

预期比对:Webhook 入口立即返回 {}(默认异步执行,不等待流跑完);请求体被完整接受并投递给执行器。若把触发器配置为同步响应模式,这里会改为返回流内"响应数据"步骤的输出。

5.2 查询运行历史

等待约 10 秒后查询该项目最近 3 条运行记录:

curl -s -m 30 "http://192.168.146.128:8100/api/v1/flow-runs?projectId=ptCNUnkh0eHoNtIFJWA2C&limit=3" \
  -H "Authorization: Bearer $TOKEN" | python3 -c "
import sys,json
d=json.load(sys.stdin)
for r in d['data']:
    print('run:', r['id'], '| status:', r['status'], '| flowId:', r['flowId'])
"

实测回显:

run: 9Of1jRTgeY94i3VAdpnjD | status: SUCCEEDED | flowId: iHQTB0tlaNLEGDy5dSSFV
run: XH7YpHFOejDCTFxCNrMlK | status: FAILED_RUN | flowId: iHQTB0tlaNLEGDy5dSSFV
run: wgbi81EwspSMdLHBFfHzq | status: FAILED_RUN | flowId: iHQTB0tlaNLEGDy5dSSFV

预期比对:新触发的运行 SUCCEEDED;两条 FAILED_RUN 是 4.2 之前发布失败阶段的遗留触发(当时连接器元数据未同步,见 4.2 的说明),运行历史完整保留了失败记录。

5.3 查看运行详情与步骤输出

curl -s -m 30 "http://192.168.146.128:8100/api/v1/flow-runs/9Of1jRTgeY94i3VAdpnjD?projectId=ptCNUnkh0eHoNtIFJWA2C" \
  -H "Authorization: Bearer $TOKEN" | python3 -c "
import sys,json
d=json.load(open('/dev/stdin')) if False else json.load(sys.stdin)
print('run status:', d['status'])
for name in ('trigger','process_code','notify_http'):
    s=d['steps'][name]
    print('---', name, '| 状态:', s['status'])
    print(json.dumps(s.get('output'), ensure_ascii=False)[:220])
"

实测回显:

run status: SUCCEEDED
--- trigger | 状态: SUCCEEDED
{"body":{"order_id":"A-2048","amount":299.9,"customer":"Han Meimei"},"headers":{"host":"192.168.146.128:8100","content-type":"application/json",...}}
--- process_code | 状态: SUCCEEDED
{"order_id":"A-2048","customer":"Han Meimei","amount":299.9,"final_price":317.89}
--- notify_http | 状态: SUCCEEDED
{"status":200,"headers":{"content-type":"application/json",...},"body":{"data":"{\"order_id\":\"A-2048\",\"final_price\":317.89}","...}}

预期比对(本验证的核心链路,三步全部对上):

  1. 触发步骤:请求体 JSON 原样进入 trigger.body,HTTP 头一并保留;
  2. Code 步骤:模板表达式 {{trigger.body.amount}} 正确取值,代码计算 299.9 × 1.06 = 317.89 输出 final_price;
  3. HTTP 步骤:模板 {{process_code.final_price}} 引用上游输出,httpbin 回显的请求体包含预期字段,远端返回 200。

输入输出 JSON 逐步骤留痕,说明运行审计数据完整可用。

六、定时触发验证

6.1 创建定时工作流

用 @activepieces/piece-schedule 的 every_x_minutes 触发器建一条每分钟执行的心跳流(Code 步骤输出时间戳):

FLOW2=$(curl -s -m 15 -X POST http://192.168.146.128:8100/api/v1/flows \
  -H "Authorization: Bearer $TOKEN" -H 'Content-Type: application/json' \
  -d '{"displayName":"schedule-demo-flow","projectId":"ptCNUnkh0eHoNtIFJWA2C"}' \
  | python3 -c "import sys,json;print(json.load(sys.stdin)['id'])")
echo "flow2 id: $FLOW2"

实测回显:

flow2 id: 0SHCjSwwEKRzCAwI01gbd

导入定时触发定义并发布(操作同 4.2/4.3,触发器 settings 为 {"pieceName":"@activepieces/piece-schedule","pieceVersion":"~0.1.22","triggerName":"every_x_minutes","input":{"minutes":1}}):

curl -s -m 60 -X POST "http://192.168.146.128:8100/api/v1/flows/$FLOW2" \
  -H "Authorization: Bearer $TOKEN" -H 'Content-Type: application/json' \
  -d '{"type":"LOCK_AND_PUBLISH","request":{"status":"ENABLED"}}' \
  | python3 -c "import sys,json;d=json.load(sys.stdin);print('状态:',d['status'],'| 发布版本:',d['publishedVersionId'])"

实测回显:

状态: ENABLED | 发布版本: dkh1bMN2sGUkLpl3h8aHz

6.2 等待三个调度周期后核对

约 3 分钟后查询该流的运行记录:

curl -s -m 30 "http://192.168.146.128:8100/api/v1/flow-runs?projectId=ptCNUnkh0eHoNtIFJWA2C&flowId=$FLOW2&limit=5" \
  -H "Authorization: Bearer $TOKEN" | python3 -c "
import sys,json
d=json.load(sys.stdin)
for r in d['data']:
    print('run:', r['id'], '| status:', r['status'], '| 开始:', r['startTime'])
print('共', len(d['data']), '条')
"

实测回显:

run: BAlQMbVZc9Id9UOD8Ykwu | status: SUCCEEDED | 开始: 2026-10-18T09:21:15.457Z
run: ACb29DDhTfzYQr4HQyqab | status: SUCCEEDED | 开始: 2026-10-18T09:20:15.421Z
run: tcoxThxgRAlIP1jPtCmE7 | status: SUCCEEDED | 开始: 2026-10-18T09:19:15.381Z
共 3 条

预期比对:三次运行时间戳恰好间隔 60 秒(15 秒相位一致),全部 SUCCEEDED,说明定时调度器按 minutes=1 精确触发、执行链路稳定。

七、动作组件逐项验证

7.1 Code 代码步骤

已在 5.3 中验证核心行为(入参绑定、Number() 转换、业务计算、输出 JSON 化)。补充验证:代码抛异常的行为见 9.3 节失败流。

7.2 HTTP 请求步骤

@activepieces/piece-http 的 send_request 动作已在 5.3 验证 POST + JSON body;再验证 GET 与查询参数拼接(目标 httpbin.org/get):

curl -s -m 30 "http://192.168.146.128:8100/api/v1/flow-runs/ZAQiV97IpLR7TVitVn0Il?projectId=ptCNUnkh0eHoNtIFJWA2C" \
  -H "Authorization: Bearer $TOKEN" | python3 -c "
import sys,json
d=json.load(sys.stdin)
o=d['steps']['use_var_http'].get('output') or {}
print('HTTP 状态:', o.get('status'))
print('对端 Host:', o.get('body',{}).get('headers',{}).get('Host'))
"

实测回显:

HTTP 状态: 200
对端 Host: httpbin.org

预期比对:GET 请求经模板拼出的 URL 正确到达目标端点,返回结构与 POST 一致(status/headers/body 三段)。

7.3 循环(Loop)步骤

LOOP_ON_ITEMS 对数组逐项执行子步骤。构造一条含循环的流(输入 items 数组,子步骤逐项输出 sku/qty),发布后用 Webhook 触发:

curl -s -m 30 -X POST "http://192.168.146.128:8100/api/v1/webhooks/GwXt8RSWuIN8K0xEKAzMf" \
  -H 'Content-Type: application/json' \
  -d '{"amount":299,"customer":"VIP Guy","items":[{"sku":"SKU-1","qty":2},{"sku":"SKU-2","qty":5}]}'

实测回显:

{}
curl -s -m 30 "http://192.168.146.128:8100/api/v1/flow-runs/20bz0plnuwpxQvzPwZ54e?projectId=ptCNUnkh0eHoNtIFJWA2C" \
  -H "Authorization: Bearer $TOKEN" | python3 -c "
import sys,json
d=json.load(sys.stdin)
print('run status:', d['status'])
loop=d['steps']['loop_items']
out=loop.get('output') or {}
print('循环轮数:', len(out.get('iterations',[])))
print('最后 item:', json.dumps({k:out.get(k) for k in ('item','index')}, ensure_ascii=False))
for it in out.get('iterations',[]):
    ic=it.get('item_code',{})
    print('子步骤输出:', json.dumps(ic.get('output'), ensure_ascii=False))
"

实测回显:

run status: SUCCEEDED
循环轮数: 2
最后 item: {"item": {"sku": "SKU-2", "qty": 5}, "index": 2}
子步骤输出: {"sku": "SKU-1", "qty": 2}
子步骤输出: {"sku": "SKU-2", "qty": 5}

预期比对:数组两个元素各执行一轮子步骤,iterations 逐轮留痕,循环上下文变量 {{loop_items.item}} 与 {{loop_items.index}} 正确注入(index 从 1 计数)。注意:循环内引用上下文要用循环步骤的 name(本例为 loop_items),表达式引擎不校验引用名,写错不会报错但拿不到数据(见 9.4 节实测返工记录)。

7.4 路由分支(Router)步骤

同一条流里在循环前配置 ROUTER(条件分支 amount > 200 走 VIP Route,否则走 Fallback),验证条件命中:

curl -s -m 30 "http://192.168.146.128:8100/api/v1/flow-runs/20bz0plnuwpxQvzPwZ54e?projectId=ptCNUnkh0eHoNtIFJWA2C" \
  -H "Authorization: Bearer $TOKEN" | python3 -c "
import sys,json
d=json.load(sys.stdin)
r=d['steps']['router_1']
out=r.get('output') or {}
print('router 输出:', json.dumps(out, ensure_ascii=False))
vip=d['steps'].get('vip_code',{})
print('VIP 分支子步骤状态:', vip.get('status'), '| 输出:', json.dumps(vip.get('output'), ensure_ascii=False))
"

实测回显:

router 输出: {"branches": [{"branchName": "VIP Route", "branchIndex": 1, "evaluation": true}, {"branchName": "Fallback", "branchIndex": 2, "evaluation": false}]}
VIP 分支子步骤状态: SUCCEEDED | 输出: {"level":"VIP","customer":"VIP Guy","note":"amount > 200"}

预期比对:amount=299 使 VIP Route 分支求值为 true、Fallback 分支求值为 false,只有 VIP 分支的子步骤被执行——路由语义符合 EXECUTE_FIRST_MATCH(执行第一个命中的分支,branches 数组同时给出各分支的求值结果便于审计)。

八、全局变量与连接器验证

8.1 全局变量(Variables)

创建项目级变量并列表确认:

curl -s -m 20 -X POST "http://192.168.146.128:8100/api/v1/variables" \
  -H "Authorization: Bearer $TOKEN" -H 'Content-Type: application/json' \
  -d '{"name":"api_base","projectId":"ptCNUnkh0eHoNtIFJWA2C","value":"https://httpbin.org"}' \
  -w '\nHTTP:%{http_code}\n' | python3 -c "
import sys,json
raw=sys.stdin.read()
body,_,tail=raw.rpartition('HTTP:')
d=json.loads(body)
print('变量 id:', d.get('id'), '| 名称:', d.get('name'))
print('HTTP:'+tail)
"

实测回显:

变量 id: V6HuKgIncRHRBu6eCK5oq | 名称: api_base
HTTP:200

变量是"一个名字一个值"的模型(再建一条 tax_rate=0.06,列表返回两条,实测回显略)。使用验证:把 HTTP 步骤的 URL 写成 {{variables.api_base}}/get,运行后对端 Host 为 httpbin.org(见 7.2 回显),说明变量在工作流运行时正确插值。

8.2 连接器凭据(Connection)创建

以 @activepieces/piece-todoist 为例创建 SECRET_TEXT 型凭据(创建请求需带顶层 type 与 value.type、业务键名为 externalId):

curl -s -m 20 -X POST "http://192.168.146.128:8100/api/v1/app-connections" \
  -H "Authorization: Bearer $TOKEN" -H 'Content-Type: application/json' \
  -d '{"pieceName":"@activepieces/piece-todoist","externalId":"todoist_demo_conn","displayName":"Todoist Demo","projectId":"ptCNUnkh0eHoNtIFJWA2C","type":"SECRET_TEXT","value":{"type":"SECRET_TEXT","secret_text":"0123456789abcdef0123456789abcdef01234567"}}' \
  -w '\nHTTP:%{http_code}\n' | python3 -c "
import sys,json
raw=sys.stdin.read()
body,_,tail=raw.rpartition('HTTP:')
d=json.loads(body)
print('externalId:', d.get('externalId'), '| 类型:', d.get('type'), '| 状态:', d.get('status'))
print('HTTP:'+tail)
"

实测回显:

externalId: todoist_demo_conn | 类型: SECRET_TEXT | 状态: ACTIVE
HTTP:201

列表确认并删除(清理演示凭据):

curl -s -m 20 "http://192.168.146.128:8100/api/v1/app-connections?projectId=ptCNUnkh0eHoNtIFJWA2C" \
  -H "Authorization: Bearer $TOKEN" | python3 -c "
import sys,json
d=json.load(sys.stdin)
for c in d['data']:
    print('连接:', c['externalId'], '| piece:', c['pieceName'], '| 状态:', c['status'])
"

实测回显:

连接: todoist_demo_conn | piece: @activepieces/piece-todoist | 状态: ACTIVE
curl -s -m 20 -X DELETE "http://192.168.146.128:8100/api/v1/app-connections/iNpKNeSSS1pq9qDgnVgXa?projectId=ptCNUnkh0eHoNtIFJWA2C" \
  -H "Authorization: Bearer $TOKEN" -w '\nHTTP:%{http_code}\n'

实测回显:

HTTP:204

预期比对:凭据创建即加密落库(返回不回显 secret_text 明文)、列表元数据正常、删除返回 204。注意凭据值按连接器 schema 校验:实测用伪造 key 创建 @activepieces/piece-openai 凭据时被在线校验拦截,返回 INVALID_APP_CONNECTION — Invalid API key(说明部分商业连接器在保存时会真实调用对端 API 验证凭据有效性,这是预期内的安全行为);@activepieces/piece-todoist 等 SECRET_TEXT 型连接器不做在线校验,可正常保存。

九、异常与边界验证

9.1 /test 测试触发(生成样例数据,不产生真实运行)

curl -s -m 30 -X POST "http://192.168.146.128:8100/api/v1/webhooks/iHQTB0tlaNLEGDy5dSSFV/test" \
  -H 'Content-Type: application/json' -d '{"order_id":"TEST-001","amount":10.5,"customer":"Test User"}' \
  -w '\nHTTP:%{http_code}\n'

实测回显:

{}
HTTP:200

预期比对:/test 端点返回 200 且响应为空对象——它的用途是给画布"测试触发器"按钮投递样例负载(供后续步骤做数据映射预览),不进入正式运行队列,运行历史不新增记录。编排自动化验收时不要把它当作真实触发。

9.2 无效 Webhook URL

curl -s -m 15 -X POST "http://192.168.146.128:8100/api/v1/webhooks/aaaaaaaaaaaaaaaaaaaaa" \
  -H 'Content-Type: application/json' -d '{"x":1}' -w '\nHTTP:%{http_code}\n'

实测回显:

{"statusCode":404,"error":"Not Found","message":"Route not found"}
HTTP:404

预期比对:不存在的 flowId 返回 404,且查询运行历史确认未产生任何运行记录——无效请求不会污染数据,也不会被异步吞掉。

9.3 流执行失败的错误呈现

构造一条故意失败的流(Code 步骤抛出业务异常),发布后触发:

curl -s -m 30 -X POST "http://192.168.146.128:8100/api/v1/webhooks/os1nrc2F7u5xJdab5UaTs" \
  -H 'Content-Type: application/json' -d '{"name":"boom-case"}' && echo

实测回显:

{}
curl -s -m 30 "http://192.168.146.128:8100/api/v1/flow-runs/UqZENs9aAFVRpMMANpGY4?projectId=ptCNUnkh0eHoNtIFJWA2C" \
  -H "Authorization: Bearer $TOKEN" | python3 -c "
import sys,json
d=json.load(sys.stdin)
print('run status:', d['status'])
s=d['steps']['boom_code']
print('步骤状态:', s['status'])
print('errorMessage:', (s.get('errorMessage') or '')[:220])
"

实测回显:

run status: FAILED
步骤状态: FAILED
errorMessage: "{\"__apErrorVersion\":1,\"message\":\"intentional failure: invalid config for boom-case\",\"raw\":\"Error: intentional failure: invalid config for boom-case\\n    at Object.code (/usr/src/app/cache/v15/codes/.../boom_code/index.js:26:9)..."

预期比对:失败被精确隔离在出错步骤(前序 trigger 正常 SUCCEEDED),运行整体标记 FAILED,Code 步骤抛出的自定义异常消息与调用栈完整呈现在 errorMessage(结构化 JSON:message 为业务消息、raw 为原始堆栈),定位粒度到步骤级。失败运行会完整进入运行历史,支持定点重试。

9.4 循环上下文引用名错误

循环上下文变量的引用名是循环步骤的 name(如 {{loop_items.item}}、{{loop_items.index}}),而不是框架预设名。首次导入定义时误写成 {{loop_item.item}},运行不报错但子步骤拿不到数组元素;把引用名改为循环步骤名 loop_items 后重新导入、发布、触发,输出恢复正常(7.3 的回显即修正后的结果)。经验教训:表达式引擎不校验引用名的有效性,编排循环后务必先用画布的测试运行确认数据映射再发布。

9.5 Webhook Basic 认证(异步模式下的静默拒绝)

把触发器 authType 配置为 basic(用户名 admin / 密码 q1w2e3r4)并发布后,先发一个无凭据请求:

curl -s -m 30 -X POST "http://192.168.146.128:8100/api/v1/webhooks/VXI5l7ieV0IV1alvW8UY6" \
  -H 'Content-Type: application/json' -d '{"name":"noauth"}' -w '\nHTTP:%{http_code}\n'

实测回显:

{}
HTTP:200

注意:HTTP 层返回的是 200——异步模式下 Webhook 入口只负责"收包入队",认证发生在执行器端。再发一个携带正确 Basic 凭据的请求:

curl -s -m 30 -u admin:q1w2e3r4 -X POST "http://192.168.146.128:8100/api/v1/webhooks/VXI5l7ieV0IV1alvW8UY6" \
  -H 'Content-Type: application/json' -d '{"name":"auth-ok"}' -w '\nHTTP:%{http_code}\n'

实测回显:

{}
HTTP:200

然后核对运行历史与执行器日志。运行历史只有一条(auth-ok 的 SUCCEEDED 运行),noauth 请求没有产生任何运行记录;执行器日志中可以找到它的处置记录:

{"event":"job.execute","job":{"type":"EXECUTE_WEBHOOK"},...,"requestLogs":[...,{"level":"warn","message":"Trigger run did not complete successfully",...}],"error":"Webhook authentication failed: request did not pass the configured basic check","status":"USER_FAILURE",...,"level":"warn","service":"activepieces-worker",...}

预期比对:认证失败的请求被判定为 USER_FAILURE,既不计入运行历史也不触发流执行,效果等同拒绝;正确凭据的请求正常执行并留下 SUCCEEDED 运行。这里有一个值得记录的行为细节:异步模式下认证失败对调用方完全静默(HTTP 200 + 空响应),排障时要靠运行历史缺失加执行器日志判断,如果调用方需要明确的拒绝信号,应把 Webhook URL 加上 /sync 后缀走同步模式(同步失败会以错误响应呈现)。

十、验收结论清单

#验证项预期实测结果结论
1首个用户注册200,自动 ADMIN + 首项目platformRole=ADMIN,projectId 自动创建通过
2登录签发 JWT返回会话令牌eyJhbGciOiJIUzI1NiIs...通过
3连接器元数据同步可查询、版本明确769 条元数据,webhook/http/schedule 版本齐全通过
4工作流创建/导入/发布DRAFT→LOCKED,ENABLED三态迁移正常,publishedVersionId 生成通过
5Webhook 触发异步执行,逐步骤留痕trigger→code→http 输出 JSON 链路全部正确通过
6定时触发(1 分钟)60 秒间隔稳定调度3 条运行间隔精确 60s,全部 SUCCEEDED通过
7Code 步骤入参绑定、计算、输出final_price=317.89 计算正确通过
8HTTP 步骤GET/POST + 模板拼接httpbin 回显字段完整,200通过
9Loop 循环逐项执行、iterations 留痕2 轮循环,item/index 上下文正确通过
10Router 路由分支首个命中分支执行VIP 分支 evaluation=true 执行、Fallback=false 跳过通过
11全局变量创建/引用/运行时插值{{variables.api_base}} 运行时正确解析通过
12连接器凭据加密存储、增删查创建 201/列表/删除 204,明文不回显通过
13/test 测试触发投递样例不进运行队列200 + 空对象,运行历史无新增通过
14无效 Webhook URL404 且不留运行404 Route not found通过
15失败流错误呈现步骤级定位 + 消息透出run/step 均标记 FAILED,errorMessage 含业务消息与堆栈通过
16Webhook Basic 认证非法凭据不执行、合法凭据放行无凭据静默拒绝(无运行记录,日志 USER_FAILURE),正确凭据 SUCCEEDED通过

总体结论:Activepieces 0.92.3 社区版在本次验收覆盖的 16 项功能全部通过,工作流执行链路(触发→插值→代码→HTTP→循环/分支)数据流正确,异常路径(无效请求、执行失败、认证拒绝)行为符合预期且审计信息完整,可进入生产部署评估阶段。

十一、参考链接