版本:v1.0 适用范围:平台前端、运维工具对工作流元数据与图结构进行创建、查询、修改、删除、分类绑定等管理操作。 实现来源:
backend/src/main/java/com/agent/management/controller/WorkflowController.java、service/impl/WorkflowServiceImpl.java、parser/WorkflowNameNormalizer.java。 如需第三方系统提交运行并订阅执行流,请参考external-workflow-api-spec.md。
| 要点 | 说明 |
|---|---|
| 标识符 | 工作流对外暴露的唯一标识为 name(kebab-case),URL 路由全部按 name 寻址;旧数字 ID 作为字符串回退兼容(路径参数 {name} 若为纯数字且无 kebab-case 匹配,则按 ID 查询) |
| 名称规范 | name 必须为 kebab-case:^[a-z0-9]+(-[a-z0-9]+)*$,长度 ≤ 64;非法字符在创建/重命名时抛 BusinessException(HTTP 400) |
| 中文名 | displayName 承载可读性与本地化,可为任意字符;未提供时默认取 name |
| 图结构校验 | 保存 graphData 时强制校验:至少 1 个 userInput 节点 + 至少 1 个 output 节点,且二者之间存在长度 ≥ 2 的有向路径(中间至少经过 1 个非输出节点) |
| 部分更新 | 修改接口对 displayName/description/graphData 支持部分更新:字段为 null 时跳过,不覆盖原值 |
| 版本快照 | 保存接口在持久化后自动创建一份版本快照(双轨目录:优先 name,回退 id);快照失败不影响保存结果,仅 WARN 日志 |
| 分类绑定 | 工作流分类(categoryId)通过独立接口设置;categoryId = null 表示取消分类 |
http(s)://<host>:<port>/api/workflows
2438(见 application.yml: server.port)/api/workflows(与对外发布的 /api/v1/workflows 隔离,前者用于平台内部管理,后者需 X-API-Key 鉴权)| 场景 | Content-Type |
|---|---|
| 请求体(JSON) | application/json |
| 响应体 | application/json |
所有接口统一返回 Result<T>:
{
"code": 200,
"message": "success",
"data": { ... }
}
错误时:
{
"code": 400,
"message": "工作流名称必须为 kebab-case 格式(仅小写字母、数字、短横线,禁用大写/空格/下划线/中文): 我的流程",
"data": null
}
| code | 含义 | 触发场景 |
|---|---|---|
200 |
成功 | 正常返回 |
400 |
业务校验失败 | 名称非法、名称重复、图结构不合法、工作流不存在 |
500 |
服务器内部错误 | 未捕获异常(堆栈已记录到日志,响应体不泄漏) |
业务异常(
BusinessException)默认 code = 400,HTTP 状态码仍为 200,由Result.code区分;系统异常 HTTP 状态码 = 500。
| 字段 | 类型 | 说明 |
|---|---|---|
id |
number | 工作流主键(内部使用,URL 不依赖) |
name |
string | kebab-case 唯一标识,URL 路由使用此字段;旧数据迁移后默认值为 String.valueOf(id)(纯数字天然合法) |
displayName |
string | 中文显示名,可为任意字符 |
description |
string | 工作流描述(用途、输入输出、注意事项等) |
graphData |
string | 节点和边数据,JSON 字符串;空图 = {"nodes":[],"edges":[]} |
categoryId |
number | 分类 ID,可空 |
categoryName |
string | 分类名称(仅响应体,由后端关联查询填充) |
tags |
array | 关联标签列表(仅响应体) |
createdAt / updatedAt |
string (ISO-8601) | 创建/更新时间 |
| # | 方法 | 路径 | 用途 |
|---|---|---|---|
| 3.1 | GET |
/api/workflows |
分页查询工作流列表(支持搜索、分类过滤) |
| 3.2 | GET |
/api/workflows/{name} |
查询单个工作流详情 |
| 3.3 | POST |
/api/workflows |
创建工作流(仅元数据,图数据默认为空) |
| 3.4 | POST |
/api/workflows/{name}/save |
保存工作流(元数据 + 图数据,支持部分更新 + 自动版本快照) |
| 3.5 | POST |
/api/workflows/{name}/delete |
删除工作流 |
| 3.6 | PUT |
/api/workflows/{name}/category |
设置/取消工作流分类 |
请求
GET /api/workflows?search={kw}&categoryId={id}&page={page}&size={size}
查询参数
| 参数 | 类型 | 必填 | 默认 | 说明 |
|---|---|---|---|---|
search |
string | 否 | 空 | 关键字,匹配 name / displayName / description(大小写不敏感,子串匹配) |
categoryId |
number | 否 | 空 | 分类 ID;过滤时包含子孙分类(调用 WfCategoryService.getDescendantCategoryIds 展开后再过滤) |
page |
number | 否 | 1 |
页码,从 1 开始 |
size |
number | 否 | 12 |
每页条数 |
响应
{
"code": 200,
"message": "success",
"data": {
"items": [
{
"id": 12,
"name": "research-report",
"displayName": "研究报告生成",
"description": "根据主题生成结构化研究报告",
"graphData": "{\"nodes\":[...],\"edges\":[...]}",
"categoryId": 3,
"categoryName": "研发辅助",
"createdAt": "2026-07-01T08:30:00",
"updatedAt": "2026-07-15T10:12:33",
"tags": [
{ "id": 5, "name": "报告" },
{ "id": 8, "name": "LLM" }
]
}
],
"total": 1,
"page": 1,
"size": 12
}
}
实现说明: 列表先全量加载 (
WorkflowService.listWorkflows) 再内存过滤分页,适合工作流数量 < 1000 的场景;若规模增长需切换为数据库分页。
请求
GET /api/workflows/{name}
路径参数
| 参数 | 类型 | 必填 | 说明 |
|---|---|---|---|
name |
string | 是 | 工作流 name(kebab-case);若为纯数字字符串且无 kebab-case 匹配,回退按 id 查询(兼容旧 URL) |
响应
{
"code": 200,
"message": "success",
"data": {
"id": 12,
"name": "research-report",
"displayName": "研究报告生成",
"description": "根据主题生成结构化研究报告",
"graphData": "{\"nodes\":[...],\"edges\":[...]}",
"categoryId": 3,
"categoryName": "研发辅助",
"createdAt": "2026-07-01T08:30:00",
"updatedAt": "2026-07-15T10:12:33",
"tags": [
{ "id": 5, "name": "报告" }
]
}
}
错误码
| code | 触发场景 |
|---|---|
400 |
工作流不存在:工作流不存在: {name} |
请求
POST /api/workflows
Content-Type: application/json
请求体
{
"name": "research-report",
"displayName": "研究报告生成",
"description": "根据主题生成结构化研究报告"
}
字段说明
| 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
name |
string | 是 | kebab-case 唯一标识;服务端会执行 normalize + validate:转小写、空格/下划线转短横线、合并连续短横线、剔非法字符,最终必须匹配 ^[a-z0-9]+(-[a-z0-9]+)*$ 且长度 ≤ 64 |
displayName |
string | 否 | 中文名;为空时默认取规范化后的 name |
description |
string | 否 | 工作流描述 |
响应
{
"code": 200,
"message": "success",
"data": {
"id": 13,
"name": "research-report",
"displayName": "研究报告生成",
"description": "根据主题生成结构化研究报告",
"graphData": "{\"nodes\":[],\"edges\":[]}",
"categoryId": null,
"categoryName": null,
"createdAt": "2026-07-15T10:15:00",
"updatedAt": "2026-07-15T10:15:00",
"tags": []
}
}
创建时
graphData默认为{"nodes":[],"edges":[]}(空图),不触发图结构校验;后续通过 §3.4 保存时才校验。
错误码
| code | 触发场景 |
|---|---|
400 |
名称非法:工作流名称必须为 kebab-case 格式(仅小写字母、数字、短横线,禁用大写/空格/下划线/中文): {input} |
400 |
名称重复:工作流名称已存在: {name} |
请求
POST /api/workflows/{name}/save
Content-Type: application/json
路径参数
| 参数 | 类型 | 必填 | 说明 |
|---|---|---|---|
name |
string | 是 | 目标工作流 name(或旧数字 ID) |
请求体
{
"name": "research-report-v2",
"displayName": "研究报告生成(增强版)",
"description": "增加了多源检索与图表生成",
"graphData": "{\"nodes\":[...],\"edges\":[...]}"
}
字段说明
| 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
name |
string | 否 | 新名称;非空时执行规范化 + 唯一性校验(排除自身 ID),通过后更新;与原值相同则跳过唯一性校验 |
displayName |
string | 否 | 为 null 时保留原值;为空串时覆盖为空串 |
description |
string | 否 | 为 null 时保留原值 |
graphData |
string | 否 | 为 null 时保留原图;非空时先经 NodeTypeUtils.normalizeGraphData 规范化,再走图结构校验 |
部分更新语义: 任一字段为
null都不会被覆盖;如需清空description,请显式传""。
响应
{
"code": 200,
"message": "success",
"data": {
"id": 13,
"name": "research-report-v2",
"displayName": "研究报告生成(增强版)",
"description": "增加了多源检索与图表生成",
"graphData": "{\"nodes\":[...],\"edges\":...}",
"categoryId": 3,
"categoryName": "研发辅助",
"createdAt": "2026-07-01T08:30:00",
"updatedAt": "2026-07-15T10:20:00",
"tags": [...]
}
}
副作用:自动版本快照
保存成功后,控制器会调用 WorkflowVersionServiceImpl.createSnapshot(workflowId, name, graphData, name, null) 创建一份版本快照(双轨目录:优先 name,回退 id)。快照创建失败不影响保存结果,仅输出 WARN 日志:
[Save] 创建版本快照失败,不影响保存: {errorMessage}
快照可用于回滚(POST /api/workflows/{name}/versions/{version}/rollback)与历史对比。
错误码
| code | 触发场景 |
|---|---|
400 |
工作流不存在:工作流不存在: {name} |
400 |
新名称非法:工作流名称必须为 kebab-case 格式... |
400 |
新名称重复:工作流名称已存在: {name} |
400 |
图数据为空:工作流图数据不能为空 |
400 |
缺少输入节点:工作流必须包含至少一个用户输入节点 |
400 |
缺少输出节点:工作流必须包含至少一个输出节点 |
400 |
输入输出未连通:用户输入节点与输出节点必须通过其他节点连接起来 |
图结构校验规则
userInput 类型节点output 类型节点userInput 到任一 output 必须存在有向路径,且路径上至少包含 1 个非输出中间节点(直接 userInput → output 边不满足)DagResolver.resolve 隐式保证)请求
POST /api/workflows/{name}/delete
路径参数
| 参数 | 类型 | 必填 | 说明 |
|---|---|---|---|
name |
string | 是 | 目标工作流 name(或旧数字 ID) |
无请求体。
响应
{
"code": 200,
"message": "success",
"data": null
}
错误码
| code | 触发场景 |
|---|---|
400 |
工作流不存在:工作流不存在: {name} |
注意: 当前实现仅删除工作流记录本身,不会级联清理:
- 历史运行记录(
WorkflowRun/WorkflowRunNode)- 工作目录文件(
<data-dir>/workflow-runs/{workflowId}/{runId}/)- 版本快照目录(
<data-dir>/workflow-versions/{name|id}/)- 标签关联(
TagAssignment中entityType=workflow, entityId={id})如需彻底清理,请手动调用运行历史与版本管理接口,或编写专门的清理脚本。
请求
PUT /api/workflows/{name}/category
Content-Type: application/json
路径参数
| 参数 | 类型 | 必填 | 说明 |
|---|---|---|---|
name |
string | 是 | 目标工作流 name(或旧数字 ID) |
请求体
{
"categoryId": 3
}
字段说明
| 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
categoryId |
number | 否 | 分类 ID;传 null 表示取消分类(categoryId 置空) |
响应
{
"code": 200,
"message": "success",
"data": null
}
接口不校验
categoryId是否存在(即使分类被删除,工作流categoryId仍保留原值),列表查询时categoryName关联查询失败则为null。
错误码
| code | 触发场景 |
|---|---|
400 |
工作流不存在:工作流不存在: {name} |
curl -X POST http://localhost:2438/api/workflows \
-H "Content-Type: application/json" \
-d '{
"name": "research-report",
"displayName": "研究报告生成",
"description": "根据主题生成结构化研究报告"
}'
curl "http://localhost:2438/api/workflows?search=report&categoryId=3&page=1&size=20"
curl http://localhost:2438/api/workflows/research-report
# 兼容旧数字 ID
curl http://localhost:2438/api/workflows/12
curl -X POST http://localhost:2438/api/workflows/research-report/save \
-H "Content-Type: application/json" \
-d '{
"name": "research-report-v2",
"displayName": "研究报告生成(增强版)",
"description": "增加了多源检索",
"graphData": "{\"nodes\":[{\"id\":\"n1\",\"type\":\"userInput\",\"data\":{\"label\":\"输入\"}},{\"id\":\"n2\",\"type\":\"llm\",\"data\":{\"label\":\"生成\"}},{\"id\":\"n3\",\"type\":\"output\",\"data\":{\"label\":\"输出\"}}],\"edges\":[{\"source\":\"n1\",\"target\":\"n2\"},{\"source\":\"n2\",\"target\":\"n3\"}]}"
}'
curl -X POST http://localhost:2438/api/workflows/research-report/save \
-H "Content-Type: application/json" \
-d '{
"description": "更新后的描述,其他字段保持不变"
}'
注意:
name/displayName/graphData未传(或为null)时不会覆盖原值。
curl -X POST http://localhost:2438/api/workflows/research-report/delete
curl -X PUT http://localhost:2438/api/workflows/research-report/category \
-H "Content-Type: application/json" \
-d '{ "categoryId": 3 }'
curl -X PUT http://localhost:2438/api/workflows/research-report/category \
-H "Content-Type: application/json" \
-d '{ "categoryId": null }'
WorkflowNameNormalizer 提供 normalize 与 normalizeStrict 两个方法,创建/重命名时使用 normalizeStrict:
| 步骤 | 操作 | 示例 |
|---|---|---|
| 1. 转小写 + trim | "My Workflow" → "my workflow" |
— |
| 2. 空格/下划线转短横线 | "my workflow_v2" → "my-workflow-v2" |
— |
| 3. 合并连续短横线 | "my--workflow" → "my-workflow" |
— |
| 4. 去首尾短横线 | "-my-workflow-" → "my-workflow" |
— |
| 5. 剔除非法字符 | "my-workflow中文" → "my-workflow" |
中文、特殊符号被移除 |
| 6. 校验最终结果 | 必须匹配 ^[a-z0-9]+(-[a-z0-9]+)*$ 且 ≤ 64 字符 |
不匹配则抛 BusinessException |
典型用例:
| 输入 | 规范化后 | 是否合法 |
|---|---|---|
research-report |
research-report |
✅ |
Research Report |
research-report |
✅ |
research_report_v2 |
research-report-v2 |
✅ |
research--report |
research-report |
✅ |
研究报告 |
``(空) | ❌ 全部被剔除 |
report! |
report |
✅ |
-invalid |
invalid |
✅ |
a |
a |
✅ |
| ``(空串) | `` | ❌ 长度为 0 |
保存接口(§3.4)会自动创建版本快照,相关版本管理接口(已在 WorkflowController 实现,本文不展开):
| 接口 | 方法 | 路径 |
|---|---|---|
| 列出版本 | GET |
/api/workflows/{name}/versions |
| 查询版本内容 | GET |
/api/workflows/{name}/versions/{version}/content |
| 回滚到版本 | POST |
/api/workflows/{name}/versions/{version}/rollback |
| 修改版本说明 | PUT |
/api/workflows/{name}/versions/{version}/message |
回滚操作内部会调用 WorkflowService.updateWorkflow 恢复 graphData,并触发与 §3.4 相同的图结构校验。
| 项目 | 限制 | 说明 |
|---|---|---|
name 长度 |
≤ 64 字符 | WorkflowNameNormalizer.MAX_LENGTH |
name 字符集 |
[a-z0-9-] |
kebab-case 严格匹配 |
graphData 大小 |
无显式限制 | 受 spring.servlet.multipart.max-request-size(默认 500MB)间接约束;前端建议 < 1MB |
| 图节点数 | 无显式限制 | 实际受 DAG 解析性能约束,建议 < 100 |
| 并发更新 | 无乐观锁 | 同一工作流并发 save 可能后写覆盖先写;多用户协作场景需前端层面加锁 |
| 删除级联 | 不清理历史运行/版本/工作目录 | 见 §3.5 注意事项 |
| 名称重命名 | 允许 | 重命名后旧 name URL 失效;历史运行目录仍按原 id 寻址,不受影响 |
| 接口 | Controller 方法 | Service 方法 | 关键实现 |
|---|---|---|---|
GET /api/workflows |
WorkflowController.list |
WorkflowService.listWorkflows |
内存过滤分页 + WfCategoryService.getDescendantCategoryIds 展开子孙分类 |
GET /api/workflows/{name} |
WorkflowController.get |
WorkflowService.getWorkflow |
name 优先,回退 id(数字字符串匹配) |
POST /api/workflows |
WorkflowController.create |
WorkflowService.createWorkflow |
WorkflowNameNormalizer.normalizeStrict + existsByNameExcluding 唯一性校验 |
POST /api/workflows/{name}/save |
WorkflowController.save |
WorkflowService.updateWorkflow |
部分更新 + NodeTypeUtils.normalizeGraphData + validateWorkflowGraph 图结构校验 + 自动版本快照 |
POST /api/workflows/{name}/delete |
WorkflowController.delete |
WorkflowService.deleteWorkflow |
getWorkflow + workflowRepo.deleteById |
PUT /api/workflows/{name}/category |
WorkflowController.setCategory |
WorkflowService.updateCategory |
直接更新 categoryId(允许 null) |