Init Flow Orchestrator — PRD¶
状态:Implemented(Phase 1-2 后端引擎 + Phase 5 清理已完成,Phase 3-4 前端进行中) 创建日期:2026-03-29 替代:postcol 模块 + Postman/ApiFox Collection 方案(已删除)
1. 问题陈述¶
Robot 部署成功后,需要向 Robot 发起一组 HTTP 请求完成初始化(如登录、配置渠道、注册 webhook 等)。当前方案是让运营在 ApiFox/Postman 中编写 Collection JSON,粘贴到 Admin Portal。
此方案的核心问题:
- 生态不兼容:ApiFox CLI 导出的脚本使用 async/await、require() 等 Node.js 特性,与我们的 Goja ES5 引擎完全不兼容
- 维护者认知负担:要理解 Postman Collection v2.1 schema、ApiFox CLI 格式差异、三层变量作用域映射关系
- 本质错配:我们不是在做 API 测试工具,而是在做 Robot 初始化编排——流程控制、变量注入、断言校验的需求都是明确且有限的,不需要 1:1 复现 Postman/ApiFox
目标:自建一套轻量的 HTTP 请求编排系统,包含 AdminPortal 可视化编辑器 + TFRSManager 执行引擎,完全替代 Postman/ApiFox Collection 方案。
2. 解决方案概览¶
┌─────────────────────────────────────────────────────────────┐
│ AdminPortal (React) │
│ │
│ ┌────────────────────────────────────────────────────────┐ │
│ │ Init Flow Editor (可视化编排器) │ │
│ │ │ │
│ │ ┌──────┐ ┌──────┐ ┌──────┐ ┌──────┐ ┌──────┐ │ │
│ │ │ HTTP │ │ IF │ │LOOP │ │FOREACH│ │DELAY │ 步骤 │ │
│ │ └──────┘ └──────┘ └──────┘ └──────┘ └──────┘ 工具箱 │ │
│ │ │ │
│ │ ┌─ 1. POST /api/login ─────────────────────────────┐ │ │
│ │ │ Body: {"user":"admin","pass":"{{Password}}"} │ │ │
│ │ │ 提取: token ← $.data.accessToken │ │ │
│ │ │ 断言: HTTP 200 + $.code == 0 │ │ │
│ │ └───────────────────────────────────────────────────┘ │ │
│ │ ┌─ IF {{Feature.enableFeishu}} == true ────────────┐ │ │
│ │ │ ┌─ 2. POST /api/feishu/config ──────────────┐ │ │ │
│ │ │ │ Body: {"token":"{{Feature.feishuToken}}"} │ │ │ │
│ │ │ │ 断言: HTTP 200 │ │ │ │
│ │ │ └────────────────────────────────────────────┘ │ │ │
│ │ └───────────────────────────────────────────────────┘ │ │
│ │ ┌─ LOOP 最多10次, 间隔3s, 直到 $.status=="ready" ──┐ │ │
│ │ │ ┌─ 3. GET /api/status ──────────────────────┐ │ │ │
│ │ │ └────────────────────────────────────────────┘ │ │ │
│ │ └───────────────────────────────────────────────────┘ │ │
│ └────────────────────────────────────────────────────────┘ │
│ │ │
│ 保存为 JSON │
│ ▼ │
└─────────────────────────────────────────────────────────────┘
│
Admin API: PUT /templates/:id/init-flow
│
▼
┌─────────────────────────────────────────────────────────────┐
│ TFRSManager (Go) │
│ │
│ ┌─────────────┐ ┌──────────────┐ ┌────────────────┐ │
│ │ FlowParser │───▶│ FlowRunner │───▶│ InitExecutor │ │
│ │ (校验+解析) │ │ (执行引擎) │ │ (任务集成) │ │
│ └─────────────┘ └──────────────┘ └────────────────┘ │
│ │ │
│ ┌──────┴──────┐ │
│ │ │ │
│ ┌──────▼───┐ ┌─────▼──────┐ │
│ │VariableScope│ │ScriptSandbox│ ← 复用现有代码 │
│ │(三层变量) │ │(Goja ES5) │ │
│ └────────────┘ └────────────┘ │
└─────────────────────────────────────────────────────────────┘
3. 数据模型¶
3.1 InitFlow JSON Schema¶
完全自定义的格式,不兼容 Postman/ApiFox,为我们的场景量身设计。
{
"version": "1.0",
"name": "飞书客服机器人初始化",
"description": "部署后配置飞书渠道和管理员账户",
"steps": [
{
"type": "http",
"name": "登录获取 Token",
"request": {
"method": "POST",
"path": "/api/admin/login",
"headers": {
"{{AdminKey}}": "{{AdminSecret}}",
"Content-Type": "application/json"
},
"body": {
"robotId": "{{RobotID}}",
"password": "{{Password}}"
}
},
"extract": [
{
"var": "token",
"from": "body",
"path": "$.data.accessToken"
}
],
"assert": [
{ "source": "status", "op": "eq", "value": 200 },
{ "source": "body", "path": "$.code", "op": "eq", "value": 0 }
]
},
{
"type": "if",
"condition": {
"left": "{{Feature.enableFeishu}}",
"op": "eq",
"right": "true"
},
"steps": [
{
"type": "http",
"name": "配置飞书渠道",
"request": {
"method": "POST",
"path": "/api/channels/feishu",
"headers": {
"Authorization": "Bearer {{token}}"
},
"body": {
"appId": "{{Feature.feishuAppId}}",
"appSecret": "{{Feature.feishuAppSecret}}"
}
},
"assert": [
{ "source": "status", "op": "eq", "value": 200 }
]
}
]
},
{
"type": "forEach",
"array": "{{Feature.channels}}",
"itemVar": "channel",
"indexVar": "i",
"steps": [
{
"type": "http",
"name": "注册渠道 {{channel.name}}",
"request": {
"method": "POST",
"path": "/api/channels",
"headers": { "Authorization": "Bearer {{token}}" },
"body": {
"name": "{{channel.name}}",
"type": "{{channel.type}}"
}
},
"assert": [
{ "source": "status", "op": "eq", "value": 200 }
]
}
]
},
{
"type": "loop",
"maxIterations": 10,
"delay": 3000,
"breakWhen": {
"source": "body",
"path": "$.data.status",
"op": "eq",
"value": "ready"
},
"steps": [
{
"type": "http",
"name": "轮询服务就绪状态",
"request": {
"method": "GET",
"path": "/api/health",
"headers": { "Authorization": "Bearer {{token}}" }
}
}
]
},
{
"type": "delay",
"ms": 2000
}
]
}
3.2 步骤类型定义¶
| type | 说明 | 必填字段 | 可选字段 |
|---|---|---|---|
http |
HTTP 请求 | name, request |
extract, assert, script |
if |
条件分支 | condition, steps |
else(步骤数组) |
forEach |
遍历数组 | array, steps |
itemVar(默认 _item), indexVar(默认 _index) |
loop |
固定次数/条件循环 | steps |
maxIterations(默认 1), delay(ms), breakWhen |
delay |
等待 | ms |
— |
3.3 HTTP 步骤详解¶
request 对象¶
interface Request {
method: "GET" | "POST" | "PUT" | "DELETE" | "PATCH"
path: string // 相对路径,基于 ServiceURL
headers?: Record<string, string> // 支持 {{变量}} 模板
body?: object | string // JSON 对象或 raw 字符串,支持模板
timeout?: number // 单个请求超时(ms),默认 30000
}
extract(变量提取)¶
从响应中提取值,设为后续步骤可用的变量:
interface ExtractRule {
var: string // 变量名
from: "body" | "header" | "status" // 提取来源
path?: string // JSONPath 表达式(from=body 时必填)
headerName?: string // Header 名称(from=header 时必填)
}
示例:
[
{ "var": "token", "from": "body", "path": "$.data.accessToken" },
{ "var": "requestId", "from": "header", "headerName": "X-Request-Id" },
{ "var": "httpCode", "from": "status" }
]
assert(声明式断言)¶
interface AssertRule {
source: "status" | "body" // 断言来源
path?: string // JSONPath(source=body 时必填)
op: "eq" | "neq" | "gt" | "gte" | "lt" | "lte" | "contains" | "exists" | "notExist"
value?: any // 期望值(exists/notExist 不需要)
}
script(高级脚本,escape hatch)¶
当声明式 extract/assert 无法满足时,使用 ES5 脚本:
{
"type": "http",
"name": "复杂处理",
"request": { ... },
"script": {
"post": "var items = pm.response.json().data.items;\nvar names = items.map(function(i) { return i.name; });\npm.variables.set('itemNames', JSON.stringify(names));"
}
}
script.pre:请求前执行(可修改变量、跳过请求)script.post:请求后执行(可提取变量、做复杂断言)- 脚本与 extract/assert 可共存,执行顺序:pre → 发送请求 → extract → assert → post
3.4 条件表达式¶
if 和 loop.breakWhen 共用同一个条件结构:
interface Condition {
// 简单条件(三元素)
left?: string // 支持 {{变量}} 模板
op: string // eq | neq | gt | gte | lt | lte | contains | exists | notExist
right?: string // 支持 {{变量}} 模板
// 响应条件(用于 loop.breakWhen)
source?: "body" | "status"
path?: string // JSONPath
value?: any // 期望值
}
4. 与 Postman/ApiFox 的关键差异¶
| 维度 | Postman/ApiFox | Init Flow Orchestrator |
|---|---|---|
| 定位 | 通用 API 测试工具 | Robot 初始化专用编排 |
| 断言 | JS 脚本为主 | 声明式规则为主,脚本为辅 |
| 变量 | 多层作用域 + 全局 | 平台注入 + 步骤提取,两层够用 |
| 流程控制 | setNextRequest / GUI 流程 | 结构化嵌套(if/forEach/loop/delay) |
| 请求 URL | 完整 URL | 相对路径(ServiceURL 自动拼接) |
| 数据格式 | Postman Collection v2.1 | 自定义 JSON,简洁直观 |
| 运行时 | Node.js sandbox | Go 原生(声明式)+ Goja ES5(脚本) |
| 前端 | 外部工具(ApiFox/Postman) | 内置 AdminPortal 编排器 |
5. 平台注入变量¶
执行引擎在运行 InitFlow 前自动注入以下变量,步骤中通过 {{变量名}} 引用:
| 变量 | 说明 | 示例 |
|---|---|---|
ServiceURL |
Robot 服务地址(自动拼接到 path 前) | http://friday.ns.svc:8080 |
AdminKey |
管理认证 Header 名 | admin_key |
AdminSecret |
管理认证密钥 | ac4a30ae... |
RobotID |
机器人标识 | friday |
Password |
用户创建时的密码 | user-pwd |
Feature |
完整 Feature JSON | {"enableFeishu":true,...} |
Feature.* |
Feature 子字段 | Feature.feishuToken → xxx |
6. 后端架构¶
6.1 模块划分¶
internal/shared/initflow/ ← 新建包(替代 postcol/)
├── types.go ← InitFlow / Step / Condition 等类型定义
├── parser.go ← JSON 解析 + 校验(schema validation)
├── runner.go ← 执行引擎(遍历 steps,处理流程控制)
├── evaluator.go ← 条件求值器 + JSONPath 求值
├── extractor.go ← 变量提取器(extract 规则执行)
├── asserter.go ← 声明式断言求值器
└── runner_test.go ← 单元测试
internal/shared/initflow/ 复用:
├── (import) digital_employee_service.VariableScope ← 变量作用域
├── (import) digital_employee_service.ScriptSandbox ← Goja ES5 沙箱
├── (import) digital_employee_service.BuildEnvironmentVars ← 变量构建
6.2 复用 vs 新建¶
| 现有模块 | 决策 | 原因 |
|---|---|---|
VariableScope |
复用 | 三层变量作用域机制完全适用 |
ScriptSandbox + pmAPI |
复用 | ES5 脚本 escape hatch 仍需要 |
BuildEnvironmentVars |
复用 | 平台变量注入逻辑不变 |
InitProgress + 断点续传 |
复用 | 进度追踪和崩溃恢复仍需要 |
init_executor_v2.go |
改造 | 改为调用 initflow.Runner 而非 CollectionRunner |
postcol/ |
删除 | Postman/ApiFox 解析器不再需要 |
collection_runner.go |
删除 | 被 initflow.Runner 替代 |
| EchoServer (go-httpbin) | 保留 | 本地测试仍需要 |
6.3 执行流程¶
InitExecutorV3.Execute(task)
│
├─ 1. 加载 Instance + Template.InitFlow (新字段,存 InitFlow JSON)
├─ 2. initflow.Parse(json) → 校验 + 解析为 Flow 结构体
├─ 3. 加载 Checkpoint(断点续传)
├─ 4. BuildEnvironmentVars() → 构建注入变量
├─ 5. NewVariableScope(envVars)
│
├─ 6. runner.Execute(flow, scope, checkpoint)
│ │
│ ├─ 遍历 steps:
│ │ ├─ http → 解析模板 → 发送请求 → extract → assert → script.post
│ │ ├─ if → 求值 condition → true 执行 steps / false 执行 else
│ │ ├─ forEach → 解析 array → 每个元素设 itemVar → 执行 steps
│ │ ├─ loop → 循环执行 steps → 检查 breakWhen → delay
│ │ └─ delay → time.Sleep
│ │
│ └─ 每步完成后:保存进度 + 变量快照
│
└─ 7. 返回结果 → FSM 事件(init_complete_success / init_complete_failure)
6.4 JSONPath 实现¶
轻量 JSONPath 子集,支持以下语法即可覆盖所有场景:
推荐使用 github.com/PaesslerAG/jsonpath 或自行实现(仅需点路径 + 数组索引)。
7. 前端架构¶
7.1 AdminPortal 编排组件¶
交互形态:列表式步骤编辑器(非 DAG 流程图)
理由: - Init 流程本质是顺序执行 + 嵌套控制,不是复杂 DAG - 列表式更直观,学习成本低 - 实现复杂度远低于流程图编辑器 - 通过缩进/嵌套表达控制流程
7.2 组件结构¶
InitFlowEditor/
├── FlowEditor.tsx ← 主编辑器容器
├── StepList.tsx ← 步骤列表(支持拖拽排序、嵌套)
├── StepCard.tsx ← 单步卡片(显示摘要 + 展开编辑)
├── HttpStepForm.tsx ← HTTP 步骤编辑表单
│ ├── RequestSection.tsx ← Method/Path/Headers/Body
│ ├── ExtractSection.tsx ← 变量提取规则
│ ├── AssertSection.tsx ← 声明式断言规则
│ └── ScriptSection.tsx ← 高级脚本(折叠,按需展开)
├── IfStepForm.tsx ← IF 条件配置
├── ForEachStepForm.tsx ← ForEach 配置
├── LoopStepForm.tsx ← Loop 配置
├── DelayStepForm.tsx ← Delay 配置
├── ConditionEditor.tsx ← 条件表达式编辑器(复用于 if/loop)
├── VariableChips.tsx ← 可用变量提示(平台变量 + 已提取变量)
├── FlowPreview.tsx ← 只读预览/执行进度展示
└── useInitFlow.ts ← 状态管理 hook
7.3 关键交互¶
- 步骤工具箱:顶部工具栏,点击添加不同类型的步骤
- 拖拽排序:步骤卡片可拖拽调整顺序
- 嵌套:if/forEach/loop 的子步骤缩进展示,折叠/展开
- 变量自动补全:输入
{{时弹出可用变量列表(平台变量 + 前序步骤 extract 的变量) - 断言配置器:下拉选择 source → 填 JSONPath → 选操作符 → 填期望值
- 实时校验:编辑时即时校验 JSON 合法性、变量引用合法性
- 预览模式:展示执行时的步骤流程(带变量标记)
7.4 推荐依赖¶
| 用途 | 推荐库 |
|---|---|
| 拖拽排序 | @dnd-kit/core + @dnd-kit/sortable |
| 代码编辑 | @monaco-editor/react(Body/Script 编辑) |
| 表单 | Ant Design Form(AdminPortal 已有) |
| JSON Schema 校验 | ajv(前端校验 InitFlow JSON) |
8. 数据库变更¶
8.1 模板表¶
digital_employee_templates 表新增字段:
ALTER TABLE digital_employee_templates
ADD COLUMN init_flow JSONB; -- InitFlow JSON(新格式)
-- init_steps 字段保留但标记废弃,迁移完成后删除
8.2 迁移策略¶
- 新创建的模板使用
init_flow字段 - 旧模板的
init_steps(Postman JSON)通过一次性迁移脚本转换为init_flow格式 - 过渡期 InitExecutor 同时支持两种格式(先查 init_flow,为空则 fallback 到 init_steps)
- 所有模板迁移完成后删除 init_steps 字段和 postcol 包
9. API 变更¶
9.1 Admin API¶
# 更新模板的初始化流程
PUT /api/admin/templates/:id/init-flow
Body: { "initFlow": { ... InitFlow JSON ... } }
# 获取模板的初始化流程
GET /api/admin/templates/:id/init-flow
Response: { "initFlow": { ... }, "preview": { "stepCount": 5, "hasConditions": true, ... } }
# 校验初始化流程(不保存)
POST /api/admin/templates/validate-init-flow
Body: { "initFlow": { ... } }
Response: { "valid": true, "warnings": [...] }
# 预览执行计划(干运行,展示变量解析后的步骤)
POST /api/admin/templates/:id/preview-init-flow
Body: { "featureValues": { "enableFeishu": true, ... } }
Response: { "steps": [ ... 解析后的步骤列表 ... ] }
10. 实现阶段¶
Phase 1:后端执行引擎(MVP)¶
目标:能跑通 InitFlow JSON,替代 CollectionRunner
- 定义
initflow包类型(types.go) - 实现解析器 + JSON Schema 校验(
parser.go) - 实现执行引擎 — 仅
http和delay步骤(runner.go) - 实现声明式断言和变量提取(
asserter.go,extractor.go) - 改造
InitExecutorV2支持 InitFlow 格式 - 编写 seed testdata(用 InitFlow JSON 替代 Postman Collection)
- 单元测试 + EchoServer 端到端验证
验证:make run-echo + 手动触发 Init 任务,InitFlow 正确执行
Phase 2:流程控制¶
目标:支持 if/forEach/loop 控制节点
- 实现条件求值器(
evaluator.go) - 实现 if/else 分支执行
- 实现 forEach 数组遍历(含 itemVar/indexVar 注入)
- 实现 loop 循环 + breakWhen + delay
- 嵌套深度限制(max 5 层)+ 循环安全阀门(maxIterations 上限 100)
- 单元测试覆盖所有控制流组合
Phase 3:AdminPortal 编排器(基础版)¶
目标:Admin 可视化编辑 InitFlow
- InitFlowEditor 主容器 + StepList 拖拽
- HttpStepForm(Method/Path/Headers/Body 编辑)
- ExtractSection + AssertSection(声明式规则配置)
- 变量自动补全(VariableChips)
- JSON 导入/导出(从编辑器导出 InitFlow JSON)
- Admin API(save/load/validate)
Phase 4:AdminPortal 编排器(完整版)¶
目标:完整的流程控制编辑 + 脚本支持
- IfStepForm + ForEachStepForm + LoopStepForm
- ConditionEditor(条件表达式可视化编辑)
- ScriptSection(Monaco 编辑器,ES5 脚本编写)
- FlowPreview(只读预览 + 执行进度展示)
- 预览执行计划 API(干运行)
Phase 5:迁移 + 清理¶
目标:完全切换到新系统
- 迁移脚本:将所有
init_steps(Postman JSON)转为init_flow - 删除
postcol/包 - 删除
collection_runner.go - 删除
init_steps数据库字段 - 更新文档
11. 验证方案¶
开发阶段¶
# 1. 启动 EchoServer
make run-echo # go-httpbin :9999
# 2. 运行单元测试
go test ./internal/shared/initflow/...
# 3. 端到端测试(Mock 模式)
make seed-local # 写入带 InitFlow 的 seed 模板
make local-run # 启动服务
# → 创建数字员工 → 触发部署 → Init 执行 → 验证进度