workflow-crud-api.md 19 KB

工作流增删改查接口说明(Workflow CRUD API)

版本:v1.0 适用范围:平台前端、运维工具对工作流元数据与图结构进行创建、查询、修改、删除、分类绑定等管理操作。 实现来源:backend/src/main/java/com/agent/management/controller/WorkflowController.javaservice/impl/WorkflowServiceImpl.javaparser/WorkflowNameNormalizer.java。 如需第三方系统提交运行并订阅执行流,请参考 external-workflow-api-spec.md


1. 设计要点

要点 说明
标识符 工作流对外暴露的唯一标识为 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 表示取消分类

2. 通用约定

2.1 基础路径

http(s)://<host>:<port>/api/workflows
  • 默认端口:2438(见 application.yml: server.port
  • 路径前缀:/api/workflows(与对外发布的 /api/v1/workflows 隔离,前者用于平台内部管理,后者需 X-API-Key 鉴权)

2.2 内容类型

场景 Content-Type
请求体(JSON) application/json
响应体 application/json

2.3 统一响应包络

所有接口统一返回 Result<T>

{
  "code": 200,
  "message": "success",
  "data": { ... }
}

错误时:

{
  "code": 400,
  "message": "工作流名称必须为 kebab-case 格式(仅小写字母、数字、短横线,禁用大写/空格/下划线/中文): 我的流程",
  "data": null
}

2.4 错误码

code 含义 触发场景
200 成功 正常返回
400 业务校验失败 名称非法、名称重复、图结构不合法、工作流不存在
500 服务器内部错误 未捕获异常(堆栈已记录到日志,响应体不泄漏)

业务异常(BusinessException)默认 code = 400,HTTP 状态码仍为 200,由 Result.code 区分;系统异常 HTTP 状态码 = 500。

2.5 标识符与字段

字段 类型 说明
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. 接口总览

# 方法 路径 用途
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 设置/取消工作流分类

4. 接口详细规范

3.1 分页查询工作流列表

请求

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 的场景;若规模增长需切换为数据库分页。


3.2 查询单个工作流详情

请求

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}

3.3 创建工作流

请求

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}

3.4 保存工作流(更新)

请求

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 输入输出未连通:用户输入节点与输出节点必须通过其他节点连接起来

图结构校验规则

  1. 至少 1 个 userInput 类型节点
  2. 至少 1 个 output 类型节点
  3. 从任一 userInput 到任一 output 必须存在有向路径,且路径上至少包含 1 个非输出中间节点(直接 userInput → output 边不满足)
  4. DAG 无环(由 DagResolver.resolve 隐式保证)

3.5 删除工作流

请求

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}/
  • 标签关联(TagAssignmententityType=workflow, entityId={id}

如需彻底清理,请手动调用运行历史与版本管理接口,或编写专门的清理脚本。


3.6 设置/取消工作流分类

请求

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}

5. curl 调用示例

5.1 创建工作流

curl -X POST http://localhost:2438/api/workflows \
  -H "Content-Type: application/json" \
  -d '{
    "name": "research-report",
    "displayName": "研究报告生成",
    "description": "根据主题生成结构化研究报告"
  }'

5.2 查询列表(带搜索 + 分类过滤)

curl "http://localhost:2438/api/workflows?search=report&categoryId=3&page=1&size=20"

5.3 查询单个详情

curl http://localhost:2438/api/workflows/research-report

# 兼容旧数字 ID
curl http://localhost:2438/api/workflows/12

5.4 保存(更新图结构 + 重命名)

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\"}]}"
  }'

5.5 仅修改描述(部分更新)

curl -X POST http://localhost:2438/api/workflows/research-report/save \
  -H "Content-Type: application/json" \
  -d '{
    "description": "更新后的描述,其他字段保持不变"
  }'

注意:name / displayName / graphData 未传(或为 null)时不会覆盖原值。

5.6 删除工作流

curl -X POST http://localhost:2438/api/workflows/research-report/delete

5.7 设置分类

curl -X PUT http://localhost:2438/api/workflows/research-report/category \
  -H "Content-Type: application/json" \
  -d '{ "categoryId": 3 }'

5.8 取消分类

curl -X PUT http://localhost:2438/api/workflows/research-report/category \
  -H "Content-Type: application/json" \
  -d '{ "categoryId": null }'

6. 名称规范化规则详解

WorkflowNameNormalizer 提供 normalizenormalizeStrict 两个方法,创建/重命名时使用 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

7. 与版本管理接口的协作

保存接口(§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 相同的图结构校验。


8. 限制与约束

项目 限制 说明
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 寻址,不受影响

9. 与现有内部接口的映射关系

接口 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