build-flow.api/docs/x6-workflow-standard.md
xiangning f932781040 feat(rag): 集成 Microsoft Extensions AI 框架并重构对话代理服务
- 引入 Microsoft.Extensions.AI.Abstractions 依赖包
- 重构 ChatAgentService 实现,支持多轮对话历史
- 更新 IAIChatAgent 接口,统一使用 ChatMessage 消息格式
- 优化流式输出处理逻辑,提升性能表现

feat(im): 增强即时通讯会话功能支持置顶备注免打扰

- 扩展会话 DTO,新增 IsPinned、IsMuted、OtherUserRemark 属性
- 扩展 ConversationParticipant 实体,新增置顶和备注相关字段
- 实现会话置顶功能,支持按置顶时间和最后消息时间排序
- 添加会话备注功能,私聊场景支持自定义备注名称
- 完善免打扰功能,支持用户级别静音设置

feat(file): 实现文件预览服务支持多种鉴权方式

- 新增文件预览端点,支持流式返回文件内容
- 实现三种鉴权方式:授权头、JWT令牌、内部签名
- 集成 KKFileView 预览服务,支持 Office 文档在线预览
- 优化文件访问安全控制,避免直接暴露 S3 链接

feat(rag): 增强 RAG 会话管理支持知识库关联

- 扩展会话创建功能,支持关联知识库
- 新增会话更新功能,支持重命名和切换知识库
- 优化会话 DTO 结构,支持知识库 ID 关联
- 实现知识库检索增强对话功能

feat(workflow): 废弃旧工作流定义接口迁移到图形化存储

- 标记旧的节点边操作端点为废弃状态
- 实现工作流定义图形化存储迁移
- 统一使用 JSON 格式作为单一事实源
- 优化工作流定义查询性能表现

chore(deps): 更新项目依赖包版本解决版本冲突问题

- 添加 Microsoft.Extensions.AI.Abstractions 依赖
- 显式声明 OpenAI 2.11.0 版本解决 CS1705 冲突
- 更新相关依赖包至兼容版本

fix(host): 修复文件预览响应包装中间件配置问题

- 添加文件预览接口到响应绕过前缀列表
- 避免文件流被 JSON 包装和内存缓冲影响性能
- 优化文件下载和预览的中间件处理逻辑

chore(im): 修复 IM 设计时数据库上下文工厂路径问题

- 优化 ImDbContextDesignTimeFactory 路径查找逻辑
- 修复数据库迁移时连接字符串获取问题
- 更新实体映射命名空间引用
2026-07-03 15:58:58 +08:00

40 KiB
Raw Blame History

X6 工作流前后端对接标准

项目 内容
文档状态 评审稿(P0),未实现
适用范围 BuildFlow 审批流自定义流程设计器(前端 + 后端)
技术栈 前端 Vue 3 + Vben Admin v5 + AntV X6;后端 .NET 10 + FastEndpoints + MediatR + EF Core
关键决策 ① 传统审批流(非 AI 编排)② 后端 X6 JSON 单一事实源 ③ X6 自建路线

本文是标准/契约文档,不是实现教程。所有事实陈述标注「基于现有代码:路径」。实现细节另起任务。


第 1 章 背景与现状

1.1 目标场景

BuildFlow 需要一个审批流自定义流程设计器:业务用户在前端通过拖拽节点、连线的方式编排审批流程(开始 → 审批 → 抄送 → 条件分支 → 并行 → 子流程 → 定时 → 结束),保存后由后端 ProcessEngine 执行。

明确不做:AI 智能体编排(LLM/代码/MCP/知识库节点)。

1.2 前端现状

基于现有代码:build-flow.web/apps/web-antdv-next/src/

  • 技术栈:Vue 3 + TypeScript + Vue Vben Admin v5 + antdv-next + pnpm workspace。
  • 当前无任何流程相关页面(src/views/ 下只有 dashboard / system / file-manager / im / share / demos / _core,无 workflow 目录)。
  • 当前无 X6 依赖,需全新引入 @antv/x6 + @antv/x6-vue-shape。
  • API 客户端约定:基于现有代码 src/api/request.ts,统一 requestClient(responseReturn: 'data'),后端响应信封由前端层自动解包。

1.3 后端现状(关系表事实源)

基于现有代码:build-flow.api/src/modules/workflow/

1.3.1 三张关系表为唯一事实源

表名 实体 配置文件
wf_workflow_definitions WorkflowDefinition Infrastructure/Persistence/Configurations/WorkflowDefinitionConfiguration.cs
wf_workflow_nodes WorkflowNode WorkflowNodeConfiguration.cs
wf_workflow_edges WorkflowEdge WorkflowEdgeConfiguration.cs

DefinitionJson 是冗余空壳字段:

  • 基于现有代码 Domain/Entities/WorkflowDefinition.cs:21:字段注释为「完整流程图的 JSON 快照(节点+边),便于导出/导入与前端渲染」。
  • 基于现有代码 UpdateWorkflowDefinitionCommandHandler.cs:Handler 仅做 entity.DefinitionJson = request.DefinitionJson; 字符串覆盖,不与 nodes/edges 表同步,无任何一致性校验。
  • 基于 SeedData.cs:种子数据只构造 Node/Edge 实体,未写 DefinitionJson。

1.3.2 ProcessEngine 运行时只读关系表

基于现有代码 Application/Engine/ProcessEngine.cs(约 1191 行,方法名定位,行号会漂移):

  • 节点查找 FindNodeAsync:_dbContext.WorkflowNodes.FindAsync(nodeId)。
  • 出边查找 GetOutgoingEdgesAsync:_dbContext.WorkflowEdges.Where(e => e.SourceNodeId == nodeId)。
  • 入边查找 GetIncomingEdgesAsync:_dbContext.WorkflowEdges.Where(e => e.TargetNodeId == nodeId)。
  • 启动时加载定义全部节点(StartAsync 内):_dbContext.WorkflowNodes.Where(n => n.DefinitionId == ...)。
  • Config 解析统一走 NodeConfigParser.Parse(node.Config)。
  • DefinitionJson 在引擎路径中一次都未被读取(grep 零命中)。

1.3.3 引擎对实体字段的精确依赖(迁移影响面)

实体 引擎使用的字段 是否影响迁移
WorkflowNode Id, DefinitionId, NodeType, Name, Config ✅ 必须从 JSON 等价读取
WorkflowNode PositionX, PositionY ❌ 引擎不读(纯画布字段)
WorkflowNode FormDefinitionId ⚠️ 引擎不直接读,但 DTO 层有
WorkflowEdge SourceNodeId, TargetNodeId ✅ 拓扑遍历核心
WorkflowEdge EdgeType ✅ CompleteTaskAsync 选边(Approved 优先 / Normal 回退 / Rejected 独立)
WorkflowEdge Condition ✅ Condition/ExclusiveGateway 节点条件评估
WorkflowEdge Order ✅ Condition/ExclusiveGateway 分支评估顺序(first-match-wins)
WorkflowEdge Label ❌ 引擎不读

1.3.4 现有接口割裂(7 套)

基于现有代码 src/BuildFlow.Host/Endpoints/workflow/WorkflowDefinition/(24 个文件):

  • 定义级:Create / Update / Delete / Publish / Disable / GetById / GetList(7 个)
  • 节点级:CreateNode / UpdateNode / DeleteNode(3 个,路由 /workflow-definitions/{DefinitionId}/nodes[/{NodeId}])
  • 边级:CreateEdge / UpdateEdge / DeleteEdge(3 个)

问题:前端维护画布需对每个节点/边单独发请求,与 X6「整图 toJSON() 保存」模型不匹配。

1.3.5 响应信封

基于现有代码 shared/BuildFlow.Shared/Api/ApiResponse.cs + ApiResponseMiddleware.cs:

{ "code": 0, "data": <业务对象>, "message": "ok" }
  • code = 0 表示成功;前端 requestClient 配 responseReturn: 'data' 自动解包。
  • 时间统一序列化为 UTC 毫秒时间戳(TimestampDateTimeConverter)。
  • 命名 CamelCase。

1.3.6 现有枚举(本契约复用,不改)

基于现有代码 Domain/Enums/:

// NodeType(int):Start=0, End=1, Approval=2, Cc=3, Condition=4,
//                 Parallel=5, SubProcess=6, Timer=7, ExclusiveGateway=8
// EdgeType(int):Normal=0, Approved=1, Rejected=2
// DefinitionStatus(int):Draft=0, Published=1, Disabled=2

第 2 章 目标与边界

2.1 目标

  1. 定义与 X6 toJSON()/fromJSON() 原生兼容的 definition_json schema,作为前后端唯一对接契约。
  2. 让 definition_json 升级为后端唯一事实源,ProcessEngine 改读 JSON。
  3. 简化接口:废弃逐 Node/Edge CRUD,改为整图保存。
  4. 前端用 X6 实现可视化设计器,graph.toJSON() → 直接落库,graph.fromJSON() → 直接渲染。

2.2 边界

  • 本文档只定标准,不实现。后续实现按第 8 章阶段推进。
  • 不改变现有 NodeType / EdgeType / DefinitionStatus 枚举语义。
  • 不改变响应信封、认证、权限体系。
  • 不涉及流程实例运行时(WorkflowInstance / WorkflowTask / WorkflowToken)的存储改造——它们引用的是已发布流程定义的快照,迁移策略见第 7 章。

第 3 章 组件选型结论

3.1 结论:X6 自建路线

候选 结论 依据
@antv/xflow(X6 官方应用级方案) ❌ 不可用 仓库已声明停止维护;仅支持 React,Vben 是 Vue3 无法集成
X6 官方审批流成品 ❌ 不存在 官方 showcase 无审批流成品,无审批/抄送/条件分支节点预制模板
Vue3 审批流成品(StavinLi/Workflow-Vue3 等) ❌ 偏离方向 均为仿钉钉 DOM 树渲染,不用 X6 画布,与「引入 X6」目标冲突
X6 自建(BPMN/流程图示例 + x6-vue-shape) ✅ 采纳 贴合「引入 X6」目标;官方有 BPMN/流程图示例可参照;审批节点需自写但有参照

3.2 依赖包

包 用途 版本约束
@antv/x6 X6 主包(3.x 起插件已合并进主包,Snapline/Stencil/Transform 等无需单独安装) ^3.x(X6 当前稳定主版本,遵循仓库 pnpm catalog 规则)
@antv/x6-vue-shape Vue 组件渲染节点注册 与 @antv/x6 3.x 对齐(同主版本)

⚠️ 必须用 X6 3.x,2.x 的插件需单独安装且 API 有差异。实现时以 @antv/x6 当前发布的稳定主版本为准,并加入仓库 catalog,不要固定死小版本。安装走 build-flow.web 的 pnpm workspace,禁止 npm/yarn。

3.3 参照起点

  • X6 官方 BPMN 示例、流程图示例:源码位于 antvis/X6 仓库 site/examples/showcase/practices/demo/。
  • X6 Vue 节点文档:https://x6.antv.antgroup.com/tutorial/intermediate/vue。
  • 连接桩文档:https://x6.antv.antgroup.com/tutorial/basic/port。
  • 旧版审批 demo(请假/人事审批,X6 1.x,仅作节点样式参考):https://x6.antv.vision/zh/examples/showcase/practices/。

第 4 章 前端方案(设计层面)

4.1 目录落点

遵循 rag-coding-web 技能约定,全部落在 apps/web-antdv-next/src/:

src/
├── api/core/workflow.ts                              # API 定义
├── router/routes/modules/workflow.ts                 # 路由 module
└── views/workflow/
    ├── list/index.vue                                # 流程定义列表/发布/启停
    └── designer/
        ├── index.vue                                 # 设计器主页面(容器)
        ├── graph/                                    # X6 Graph 初始化与配置
        │   ├── createGraph.ts                        # Graph 工厂(grid/snapline/connecting)
        │   └── ports.ts                              # 端口 groups/items 预设
        ├── shapes/                                   # 节点注册(每个 NodeType 一个)
        │   ├── register.ts                           # 集中 register 所有 shape
        │   ├── StartNode.vue
        │   ├── ApprovalNode.vue
        │   ├── ConditionNode.vue
        │   └── ...                                   # 详见 4.4
        ├── components/                               # 设计器 UI 组件
        │   ├── StencilPanel.vue                      # 拖拽节点面板
        │   ├── Toolbar.vue                           # 保存/撤销/缩放/校验
        │   └── NodeConfigDrawer.vue                  # 节点配置抽屉
        └── composables/
            ├── useGraph.ts                           # Graph 生命周期管理
            └── useGraphPersistence.ts                # 加载/保存(toJSON/fromJSON)

上述是设计目标结构,实现时可在遵循「模块内聚」前提下调整。packages/@core、packages/effects 不改。

4.2 设计器能力清单

能力 X6 机制 说明
拖拽创建节点 Stencil 插件 分组面板,拖入后 addNode
画布缩放 mousewheel + minScale/maxScale 0.5 ~ 3
对齐线 Snapline 插件 X6 3.x 内置
连线 connecting 配置 connector: 'smooth'、allowLoop: false、validateConnection
端口显隐 node:mouseenter/mouseleave 事件 默认隐藏,hover 显示,连接后常显
节点删除 卡片内删除按钮 + node.remove() Start/End 节点禁止删除
边删除 edge:mouseenter 显示 button-remove 工具
整图保存 graph.toJSON() 一次请求覆盖整张图
整图加载 graph.fromJSON({nodes, edges}) 详情接口返回的 definitionJson 直接喂入

4.3 端口预设(所有节点统一)

四向端口,默认隐藏,hover 显示:

const PORT_GROUPS = {
  top:    { position: 'top',    attrs: { circle: { r: 3, magnet: true, fill: '#C2C8D5', stroke: '#C2C8D5' } } },
  right:  { position: 'right',  attrs: { circle: { r: 3, magnet: true, fill: '#C2C8D5', stroke: '#C2C8D5' } } },
  bottom: { position: 'bottom', attrs: { circle: { r: 3, magnet: true, fill: '#C2C8D5', stroke: '#C2C8D5' } } },
  left:   { position: 'left',   attrs: { circle: { r: 3, magnet: true, fill: '#C2C8D5', stroke: '#C2C8D5' } } },
};
const PORT_ITEMS = [
  { id: 'top', group: 'top' }, { id: 'right', group: 'right' },
  { id: 'bottom', group: 'bottom' }, { id: 'left', group: 'left' },
];

4.4 节点形态

用 @antv/x6-vue-shape 注册 Vue 组件渲染。每个 NodeType 对应一个 shape 名和一个 .vue 组件,业务配置通过 node.data 承载,组件内 node.getData() 读取。详见第 5 章 5.5 的 shape 映射表。

交互复杂的节点配置(审批人规则编辑器、条件表达式编辑器)需独立子方案,本文不展开。


第 5 章 后端数据契约(★ 核心)

5.1 顶层结构

definition_json 列存 X6 原生格式。后端统一接收/返回 {nodes, edges} 形式(X6 的 fromJSON/parseJSON 同时支持 {cells} 和 {nodes, edges} 两种等价形式,本契约选用前者更易读写):

{
  "nodes": [ /* Node 对象数组 */ ],
  "edges": [ /* Edge 对象数组 */ ]
}

空图(仅有定义壳、无节点)合法,值为 {"nodes":[],"edges":[]}。

5.2 节点标准字段表

字段 类型 必填 用途 后端是否消费
id string ✅ 节点唯一标识,格式见 5.7 ✅ 拓扑遍历
shape string ✅ 节点形状名,决定渲染器,见 5.5 ✅ 反推 NodeType
x number ✅ 画布 X 坐标(左上角) ❌ 仅渲染
y number ✅ 画布 Y 坐标(左上角) ❌ 仅渲染
width number ❌ 宽度(注册时默认) ❌ 仅渲染
height number ❌ 高度(注册时默认) ❌ 仅渲染
angle number ❌ 旋转角度 ❌ 仅渲染
visible boolean ❌ 是否可见,默认 true ❌ 仅渲染
data object ✅ 业务数据,见 5.5 ✅ 引擎读取
ports object ❌ 连接桩,见 5.4 ❌ 仅渲染(边引用 port)
attrs object ❌ SVG 样式 ❌ 仅渲染
markup array ❌ SVG 结构 ❌ 仅渲染
label string|object ❌ 文本标签 ❌ 仅渲染

后端消费原则:引擎只读 id / shape(反推 NodeType)/ data;几何/样式字段透传不解释。

5.3 边标准字段表

字段 类型 必填 用途 后端是否消费
id string ✅ 边唯一标识 ✅ 标识
shape string ❌ 边类型,默认 wf-edge ❌
source {cell, port} ✅ 起点 {cell: nodeId, port: portId} ✅ 取 cell 作 SourceNodeId
target {cell, port} ✅ 终点 {cell: nodeId, port: portId} ✅ 取 cell 作 TargetNodeId
data object ❌ 业务数据,见 5.6 ✅ 引擎读取
label string|object ❌ 边标签 ❌ 仅渲染(引擎不读)
vertices [{x,y}] ❌ 路径控制点(弯折点) ❌ 仅渲染
router string|object ❌ 路由算法(normal/orth/manhattan) ❌ 仅渲染
connector string|object ❌ 连接器(normal/smooth/rounded) ❌ 仅渲染
attrs object ❌ 样式 ❌ 仅渲染
zIndex number ❌ 层级 ❌ 仅渲染

source/target 规则:必须用 {cell, port} 结构;cell 是节点 id(对应 5.2 的 node.id),port 是端口 id(默认 top/right/bottom/left)。后端只取 cell 做拓扑,port 仅前端连线校验用。

5.4 端口 port 结构(与 4.3 一致)

"ports": {
  "groups": { /* top/right/bottom/left 组定义,见 4.3 */ },
  "items": [
    { "id": "top",    "group": "top" },
    { "id": "right",  "group": "right" },
    { "id": "bottom", "group": "bottom" },
    { "id": "left",   "group": "left" }
  ]
}

后端不解释 ports 结构,但边的 source.port / target.port 必须命中节点 ports.items[].id。校验在发布时做(见第 6 章)。

5.5 节点 data 业务契约(关键映射)

node.shape 决定渲染器,同时反推 NodeType 枚举。业务配置统一进 node.data,替代旧的 WorkflowNode.Config(jsonb 字符串)。

NodeType 枚举值 shape 名 data 业务字段(替代旧 Config)
Start 0 wf-start 无(data 可存 title)
End 1 wf-end 无(data 可存 title)
Approval 2 wf-approval approvalMode(single/countersign/any), assigneeRule, assigneeRules(数组), minApprovals, timeoutMinutes, onEnter, onApproved, onRejected, formDefinitionId
Cc 3 wf-cc recipients(数组), formDefinitionId
Condition 4 wf-condition 无(条件表达式与顺序放在出边的 data 上,见 5.6)
Parallel 5 wf-parallel 无
SubProcess 6 wf-subprocess definitionId(子流程 GUID 字符串)
Timer 7 wf-timer delayMinutes, targetTime(ISO8601)
ExclusiveGateway 8 wf-exclusive 无(同 Condition,分支信息在出边)

data.title(节点显示名,对应旧 WorkflowNode.Name)所有节点通用,建议必填。

shape → NodeType 反推规则:后端维护一张常量映射表,shape 缺失时拒绝发布。

5.6 边 data 业务契约

data 字段 类型 对应旧字段 用途
edgeType int (枚举值 0/1/2) WorkflowEdge.EdgeType Normal=0 / Approved=1 / Rejected=2;影响 Approval 节点选边
condition string WorkflowEdge.Condition Condition/ExclusiveGateway 出边的条件表达式(简单文本或 JSON 树)
order int WorkflowEdge.Order 分支评估顺序,值小优先(first-match-wins)

几何信息(vertices / router / connector)放边顶层,不放 data;这些字段后端透传不读。

5.7 ID 规则

元素 id 格式 生成方
节点 node_<guid> 前端生成(拖入时),后端保存时校验唯一
边 edge_<guid> 前端生成(连线时),后端保存时校验唯一
端口 top / right / bottom / left(固定) 由 shape 注册预设,不单独生成

待决项(见第 9 章):是否允许后端在保存时重写 id。当前建议前端生成、后端透传,保证 source.cell/target.cell 引用稳定。

5.8 引擎字段对应表(迁移映射)

ProcessEngine 当前读实体字段 → 改造后从 JSON 路径读取:

当前实体字段 JSON 路径 说明
node.Id nodes[].id 字符串 ↔ Guid(需类型转换)
node.NodeType 由 nodes[].shape 反推 见 5.5 映射
node.Name nodes[].data.title
node.Config(jsonb 字符串) nodes[].data(对象) 直接是对象,免去 NodeConfigParser 字符串反序列化
node.DefinitionId 外层(图所属定义) 加载时由 Definition 上下文注入
node.PositionX/Y nodes[].x/y 引擎不读,仅迁移脚本用
edge.SourceNodeId edges[].source.cell
edge.TargetNodeId edges[].target.cell
edge.EdgeType edges[].data.edgeType
edge.Condition edges[].data.condition
edge.Order edges[].data.order
edge.Label edges[].label 引擎不读

5.9 完整示例(最小可运行图)

{
  "nodes": [
    {
      "id": "node_start_001",
      "shape": "wf-start",
      "x": 120, "y": 120,
      "data": { "title": "开始" },
      "ports": { "groups": { "...": "见 5.4" }, "items": [ "..." ] }
    },
    {
      "id": "node_approval_001",
      "shape": "wf-approval",
      "x": 120, "y": 280,
      "data": {
        "title": "部门经理审批",
        "approvalMode": "single",
        "assigneeRule": "role:dept-manager",
        "timeoutMinutes": 1440,
        "formDefinitionId": null
      },
      "ports": { "...": "..." }
    },
    {
      "id": "node_end_001",
      "shape": "wf-end",
      "x": 120, "y": 440,
      "data": { "title": "结束" },
      "ports": { "...": "..." }
    }
  ],
  "edges": [
    {
      "id": "edge_001",
      "shape": "wf-edge",
      "source": { "cell": "node_start_001",    "port": "bottom" },
      "target": { "cell": "node_approval_001", "port": "top" },
      "data": { "edgeType": 0 }
    },
    {
      "id": "edge_002",
      "shape": "wf-edge",
      "source": { "cell": "node_approval_001", "port": "bottom" },
      "target": { "cell": "node_end_001",      "port": "top" },
      "data": { "edgeType": 1 }
    }
  ]
}

第 6 章 API 契约(简化接口)

6.1 改造后接口(最小集)

遵循现有 FastEndpoints + MediatR + 资源式路由风格(基于现有代码 Endpoints/workflow/WorkflowDefinition/):

方法 路由 说明 请求要点 响应
GET /workflow-definitions 列表 分页/筛选 WorkflowDefinitionDto[](不含 nodes/edges,含 definitionJson 摘要状态)
GET /workflow-definitions/{Id} 详情 — WorkflowDefinitionDetailDto(含完整 definitionJson,前端 graph.fromJSON 直接渲染)
POST /workflow-definitions 创建 name, code, formDefinitionId 初始空图 {"nodes":[],"edges":[]}
PUT /workflow-definitions/{Id} 整图保存 name, description, definitionJson, formDefinitionId WorkflowDefinitionDto
POST /workflow-definitions/{Id}/publish 发布 — 发布时做图校验(见 6.3)
DELETE /workflow-definitions/{Id} 删除 — 空对象(沿用现有写回 "{}" 约定)

关键变化:PUT 整图保存——请求体 definitionJson 即 X6 graph.toJSON() 输出,一次请求覆盖整张图,后端不再做增量 Node/Edge 同步。

DTO 字段去向(基于现有代码 DTOs/WorkflowDefinitionDetailDto.cs):

现有 WorkflowDefinitionDetailDto 含 List<WorkflowNodeDto> Nodes + List<WorkflowEdgeDto> Edges 两个集合字段,是「关系表事实源」时代的产物。JSON 单一事实源后这两个集合变为冗余,处理方式:

  • Nodes / Edges 集合:置为 null(或空集合),不再由后端填充。前端设计器统一从 DefinitionJson 经 graph.fromJSON() 渲染,不读这两个集合。
  • DefinitionJson:成为详情接口的唯一权威负载,前端拿它直接喂 graph.fromJSON。
  • 是否删除 Nodes/Edges 字段:为避免破坏存量前端调用,P1 阶段保留字段置空,P4 关系表清理时一并从 DTO 移除。
  • WorkflowNodeDto / WorkflowEdgeDto:P4 阶段随关系表废弃而废弃。

6.2 废弃接口(过渡期保留)

以下 6 套接口标 [Obsolete],过渡期内不删(避免破坏存量调用),最终在 P4 阶段移除:

  • POST/PUT/DELETE /workflow-definitions/{DefinitionId}/nodes[/{NodeId}]
  • POST/PUT/DELETE /workflow-definitions/{DefinitionId}/edges[/{EdgeId}]

过渡期:这些接口只操作关系表(旧事实源),不写 definition_json。一旦 P1 完成 ProcessEngine 切换,关系表不再是事实源,这些接口失去意义。

6.3 发布时图校验规则

POST /publish 在状态机 Draft → Published 前,对 definition_json 做以下校验(失败返回 ValidationErrorResponse)。

校验规则依据现有 ProcessEngine 的运行时强约束(基于现有代码 Application/Engine/ProcessEngine.cs:边选择在 CompleteTaskAsync 内、Condition 评估在 ProcessConditionNodeAsync、无匹配分支抛 "No condition branch matched")。发布期校验必须覆盖引擎运行时会抛异常的所有情形,避免发布出运行即崩溃的流程。

结构校验:

  1. 唯一 Start:有且仅有一个 shape === "wf-start" 节点。
  2. 至少一个 End:存在 shape === "wf-end" 节点。
  3. 无悬空边:每条边的 source.cell / target.cell 都能在 nodes[].id 中找到。
  4. 可达性:从 Start 节点出发,所有 End 节点可达(DFS/BFS)。
  5. shape 合法:所有 shape 在 5.5 映射表内。
  6. id 唯一:节点 id、边 id 全局唯一;边的 source.cell/target.cell 引用有效。

引擎强约束校验(必须,否则运行时抛 BusinessException):

  1. Approval 节点出边完备性:每个 wf-approval 节点必须至少有一条 data.edgeType=Approved 或 data.edgeType=Normal 的出边(通过路径);若业务允许驳回,还必须有一条 data.edgeType=Rejected 的出边。否则审批任务完成时引擎找不到匹配边直接异常。
  2. Condition / ExclusiveGateway 节点分支评估:每个 wf-condition / wf-exclusive 节点的所有出边必须带 data.condition 和 data.order;强烈建议提供一条兜底边(condition 为 true 或空),否则当所有分支都不匹配时引擎抛 "No condition branch matched"。
  3. SubProcess 节点引用有效:wf-subprocess 节点的 data.definitionId 必须指向一个已发布(Published)的流程定义。

校验失败统一返回 ValidationErrorResponse(沿用现有 BuildFlow.Shared/Api/ 的校验失败信封)。


第 7 章 迁移策略(关系表 → JSON)

7.1 核心难点:已发布定义的不可变性与运行实例的版本隔离

⚠️ 本节是整个迁移方案的关键,直接决定方案是否可行。

现有代码的事实缺陷(必须先正视):

  1. WorkflowInstance 没有 Version 字段(基于现有代码 Domain/Entities/WorkflowInstance.cs):实例仅存 DefinitionId。
  2. ProcessEngine 按 DefinitionId 实时读图(基于现有代码 Application/Engine/ProcessEngine.cs 的 StartAsync):WorkflowNodes.Where(n => n.DefinitionId == instance.DefinitionId),无版本隔离。
  3. Update 没有状态校验(基于现有代码 UpdateWorkflowDefinitionCommandHandler.cs):注释说「仅 Draft 可改」,但 Handler 未强制——Published 状态的定义仍可被 PUT 覆盖。

后果:在关系表时代,这三点就已构成隐患(发布后改节点会污染运行中实例);迁移到 JSON 单一事实源、且 PUT 改为整图覆盖后,隐患被放大——一个 PUT 即可让所有运行中实例读到不一致的图。

结论:JSON 单一事实源方案必须配套解决版本隔离,否则不可上线。

7.2 版本隔离方案:发布快照表(参照仓库已有先例 FormDefinitionVersion)

本方案沿用仓库已有的 FormDefinitionVersion(wf_form_definition_versions)版本快照范式,命名/分层/审计接口完全对齐,不引入新模式。

新增实体 WorkflowDefinitionSnapshot(基于现有先例 Domain/Entities/FormDefinitionVersion.cs 的结构):

字段 类型 说明
Id Guid 主键
DefinitionId Guid 所属流程定义 ID
Version int 版本号(与发布时 WorkflowDefinition.Version 对应)
DefinitionJson string? (jsonb) 该版本的完整 X6 graph JSON 快照(不可变)
Source string 触发来源:Publish(仅发布写入,区别于 FormDefinitionVersion 的 Update/Publish 双源)
ChangeSummary string? 变更摘要(可选)
审计 IAuditable CreatedBy/CreatedAt/UpdatedBy/UpdatedAt/IsDeleted/OperatorIP

EF 配置(基于现有先例 FormDefinitionVersionConfiguration.cs):

  • 表名 wf_workflow_definition_snapshots(对齐 wf_form_definition_versions 命名)。
  • DefinitionJson → HasColumnType("jsonb")。
  • 复合唯一索引 HasIndex(e => new { e.DefinitionId, e.Version })。
  • 索引 HasIndex(e => e.DefinitionId)。

写入时机(发布流程):

  1. PublishWorkflowDefinitionCommandHandler(基于现有代码)在做 Status=Published; Version++ 之前,先做 6.3 全部校验。
  2. 校验通过后,把当前 definition_json 复制成一条 WorkflowDefinitionSnapshot 记录(Version = 新版本号)。
  3. 快照写入后,引擎运行实例只读快照表,Draft 编辑主表的 definition_json 不影响已发布版本。

读取规则(ProcessEngine 改造后):

  • 引擎不再用 instance.DefinitionId 查 wf_workflow_definitions.definition_json。
  • 改为:用 instance.DefinitionId + 实例绑定的 Version 定位 wf_workflow_definition_snapshots 的不可变快照。
  • ⚠️ 这要求 WorkflowInstance 新增一个 DefinitionVersion(int)字段,在 StartInstance 时从当时的 WorkflowDefinition.Version 拷贝写入实例(实例一旦启动版本即冻结)。

实体字段新增属侵入式改动,但 WorkflowInstance.DefinitionVersion 是方案成立的最小必要条件,无法绕开。EF Migration 需加此列并回填存量实例(用对应定义当前 Version)。

7.3 Draft 编辑的 Published 锁定(修复现有 Update 漏洞)

在 UpdateWorkflowDefinitionCommandHandler 增加状态校验(基于现有代码,当前缺失):

// 仅 Draft 可编辑定义内容;Published/Disabled 状态拒绝整图 PUT
if (entity.Status != DefinitionStatus.Draft)
{
    throw new BusinessException("Only draft workflow definitions can be edited.");
}

已发布的定义如需修改,走「复制为新 Draft → 编辑 → 重新发布」流程(产出新 Version + 新快照),原 Version 快照不变,存量实例不受影响。

7.4 四步迁移路线

第一步(P1 核心):实现 JSON 读取层 + 快照机制

  • 新建 WorkflowGraph 领域值对象 + 解析器(Application/Engine/WorkflowGraphParser.cs),从 X6 JSON 解析出:节点列表、边列表、出边拓扑、节点 data。
  • 新建 WorkflowDefinitionSnapshot 实体 + EF 配置 + Migration。
  • WorkflowInstance 新增 DefinitionVersion 字段 + Migration + 回填。
  • PublishWorkflowDefinitionCommandHandler 增加 6.3 校验 + 写快照。
  • UpdateWorkflowDefinitionCommandHandler 增加 Draft 锁定(7.3)。

第二步(P1):ProcessEngine 切换为读快照

  • 引擎启动实例时:按 instance.DefinitionId + instance.DefinitionVersion 从 wf_workflow_definition_snapshots 加载不可变 JSON 到内存 WorkflowGraph。
  • 改造影响面见 7.5。
  • 此阶段起,Draft 编辑主表 definition_json 与运行实例彻底解耦。

第三步(P1):存量数据迁移脚本(支持 dry-run + 回滚,见 7.6)

  • 遍历 wf_workflow_definitions,对 definition_json 为空的记录,从 wf_workflow_nodes + wf_workflow_edges 序列化生成 X6 JSON:
    • PositionX/Y → x/y
    • 补默认 width/height/ports(按 shape 预设)
    • node.Config(字符串)→ node.data(对象),并按 NodeType 补 data.title = node.Name
    • edge.SourceNodeId/TargetNodeId → edge.source.cell/target.cell
    • edge.EdgeType/Condition/Order → edge.data.edgeType/condition/order
    • 节点/边 id 统一为 node_<guid> / edge_<guid>
  • 为每个已 Published 的定义补写一条 WorkflowDefinitionSnapshot 快照(Version 取定义当前 Version)。
  • 为每个运行中的 WorkflowInstance 回填 DefinitionVersion(取其定义当前 Version)。

第四步(P4):关系表降级 → 最终移除

  • 关系表降级为只读快照(过渡期)。
  • 新版本:移除 WorkflowNodes / WorkflowEdges DbSet、EF Configuration,加 Migration drop 表。

7.5 ProcessEngine 改造影响面(以方法名为准)

基于现有代码 Application/Engine/ProcessEngine.cs。下表以方法名为准(行号会随代码漂移,仅作辅助定位):

当前方法 当前实现(关系表) 改造后(读 WorkflowGraph)
StartAsync 加载节点 _dbContext.WorkflowNodes.Where(DefinitionId==instance.DefinitionId) 从快照 JSON 加载 WorkflowGraph,找 shape==="wf-start"
FindNodeAsync _dbContext.WorkflowNodes.FindAsync(nodeId) 从内存 WorkflowGraph.Nodes 按 id 查
GetOutgoingEdgesAsync _dbContext.WorkflowEdges.Where(SourceNodeId==) 从内存 WorkflowGraph.Edges 按 source.cell 过滤
GetIncomingEdgesAsync _dbContext.WorkflowEdges.Where(TargetNodeId==) 从内存 WorkflowGraph.Edges 按 target.cell 过滤
ParseConfig 包装 入参 node.Config(字符串) 入参改为 node.data(对象),NodeConfigParser 适配
Timer 节点内联查边(位于 ProcessTimerNodeAsync 内) db.WorkflowEdges.Where(SourceNodeId==) 同上,改读 WorkflowGraph

改造后引擎从「每步查库」变为「启动时一次加载快照 JSON 到内存 WorkflowGraph,后续内存遍历」,性能与一致性都更优;且天然实现版本隔离(实例绑定 Version)。

7.6 迁移回滚预案

生产数据迁移必须有回滚方案:

  1. 迁移前备份:迁移脚本执行前,对 wf_workflow_definitions.definition_json 原值、wf_workflow_nodes、wf_workflow_edges 做全表 SQL 备份(pg_dump 相关表)。
  2. dry-run 模式:脚本支持 --dry-run,仅输出迁移报告(将生成多少条 JSON、多少条快照、回填多少实例),不写库。
  3. 分批 + 可重入:按 DefinitionId 分批处理,每批独立事务;脚本可重复执行(幂等:definition_json 已非空则跳过)。
  4. 回滚:若迁移后引擎异常,回滚步骤:
    • 恢复 definition_json 原值(备份还原);
    • 保留关系表数据(P4 之前关系表不 drop,是安全网);
    • ProcessEngine 通过特性开关回退到读关系表的旧路径(P1 改造时即应保留双读开关,详见 9.1)。
  5. 关系表保留期:wf_workflow_nodes / wf_workflow_edges 在 P4 之前禁止 drop,作为整个迁移期的最终回滚保障。

第 8 章 分阶段实施路线

阶段 内容 交付物
P0 文档评审(本文) ✅ 本文档定稿
P1 后端契约改造 ✅ 已实现:WorkflowGraph/WorkflowShapeMap 解析层、WorkflowDefinitionSnapshot 快照表(含 Migration)、WorkflowInstance.DefinitionVersion 字段、Published 锁定(Draft 校验)、ProcessEngine 适配层切换读快照(双读开关)、整图保存接口、WorkflowGraphValidator 发布校验(9 条规则)、WorkflowGraphMigration 存量迁移脚本(幂等,接入 DatabaseInitializer)。423 单测全通过。
P2 前端设计器骨架 ✅ 已实现:X6 画布(复刻 AgentFlow 配置)+ Stencil 拖拽面板(3 分组 9 节点)+ 连线 + 端口 hover 显隐 + 整图 toJSON/fromJSON 保存加载闭环。typecheck + build 通过。
P3 节点配置弹窗 ✅ 已实现:节点配置抽屉(双击打开,按 NodeType 动态渲染表单)含审批(approvalMode/assigneeRule/timeoutMinutes/钩子)、抄送(recipients)、子流程(definitionId)、定时(delayMinutes/targetTime)。
P4 收尾 ⏳ 待办:列表/发布页(列表页已做,启停页待补)、关系表 drop、6 套废弃接口移除。注:6 套接口已标 [Obsolete]。

第 9 章 风险与待决项

9.1 风险

风险 说明 应对
运行实例无版本隔离(现有缺陷,致命) WorkflowInstance 无 Version 字段,引擎按 DefinitionId 实时读图;发布后改图会污染运行中实例 7.2 方案强制实施:新增 DefinitionVersion 字段 + 发布快照表,引擎读不可变快照
Published 后可被 PUT 覆盖(现有漏洞) UpdateWorkflowDefinitionCommandHandler 无状态校验,已发布定义可被改 7.3 方案强制实施:Update Handler 增加 Draft 状态校验
双源一致性(迁移期) P1 期间 JSON 与关系表并存 ProcessEngine 保留特性开关(双读),默认读快照,异常可切回关系表;Draft 编辑只走 JSON
存量已运行实例的 Version 回填 迁移期已运行的实例无 DefinitionVersion,按什么值回填? 取其定义当前 Version 回填(接受「存量实例绑定到当前版本」的近似;P1 切换前应停止新发起,迁移完再恢复)
整图 JSON 体积 复杂图可能较大 监控单图大小,必要时拆分子流程(已有 SubProcess 节点)
前端节点交互复杂度 多人会签、条件表达式编辑器工作量大 P3 独立子方案,工作量预期见 9.3
NodeConfigParser 兼容 当前解析字符串,改造后是对象 适配层兼容,或迁移完成后简化

9.2 待决项(需评审拍板)

  1. 节点/边 ID 生成方:当前建议前端生成 node_<guid> / edge_<guid>,后端透传。是否允许后端保存时重写 id?(影响 source.cell 引用稳定性)
  2. Published 流程的派生编辑:7.3 锁定 Published 后,是否提供「复制已发布流程 → 新 Draft」的派生编辑能力?(建议提供,作为修改已发布流程的标准路径)
  3. shape ↔ NodeType 映射表存放位置:建议放 Domain 层 WorkflowShapeMap 静态类。
  4. WorkflowGraph 是否强类型化:建议实现 WorkflowGraph/GraphNode/GraphEdge 强类型模型(record),避免裸 JsonDocument 散落。是否纳入本次标准?(建议纳入 P1)
  5. P1 切换窗口:存量实例 Version 回填存在近似(9.1),是否接受「P1 切换前暂停新发起、迁移完恢复」的停机窗口?还是要求零停机(零停机需更复杂的双写过渡)?

9.3 工作量预期(诚实声明,避免评审低估)

「X6 自建」意味着审批相关节点 UI 全部自写(基于现有代码,仓库无任何 X6 前端代码可复用):

  • P2 设计器骨架(画布/拖拽/连线/保存):~1-2 周
  • P3 节点配置弹窗是工作量重头,尤其:
    • 审批人规则编辑器(单人/会签/或签、角色/部门/指定人、加签)—— 复杂,~1-2 周
    • 条件表达式编辑器(变量选择 + 比较 + 与/或组合)—— 复杂,~1 周
    • 抄送人选择、子流程选择、定时配置 —— 较简单,~3-5 天
  • P1 后端改造(含快照表/Version 字段/Migration/引擎切换/迁移脚本):~1-1.5 周

P3 的复杂子项(审批人规则、条件表达式)建议在 P2 闭环验证后,另起详细子方案再实现,不在本文展开。


附录 A:关键文件路径清单

后端(build-flow.api/)

Domain

  • src/modules/workflow/Domain/Entities/WorkflowDefinition.cs — 定义实体(DefinitionJson 字段在此)
  • src/modules/workflow/Domain/Entities/WorkflowNode.cs — 节点实体(迁移源)
  • src/modules/workflow/Domain/Entities/WorkflowEdge.cs — 边实体(迁移源)
  • src/modules/workflow/Domain/Entities/WorkflowInstance.cs — 实例实体(待新增 DefinitionVersion 字段)
  • src/modules/workflow/Domain/Entities/FormDefinitionVersion.cs — 版本快照先例(wf_workflow_definition_snapshots 照此范式新建)
  • WorkflowDefinitionSnapshot(待新增) — 发布快照实体,参照 FormDefinitionVersion
  • src/modules/workflow/Domain/Enums/NodeType.cs — 节点类型枚举(复用)
  • src/modules/workflow/Domain/Enums/EdgeType.cs — 边类型枚举(复用)
  • src/modules/workflow/Domain/Enums/DefinitionStatus.cs — 定义状态枚举(复用)

Application

  • src/modules/workflow/Application/Engine/ProcessEngine.cs — 流程引擎(改造核心,~1191 行)
  • src/modules/workflow/Application/Engine/NodeConfigParser.cs — Config 解析器(需适配)
  • src/modules/workflow/Application/Features/WorkflowDefinitions/Commands/UpdateWorkflowDefinitionCommand.cs — 更新命令
  • src/modules/workflow/Application/Features/WorkflowDefinitions/Handlers/UpdateWorkflowDefinitionCommandHandler.cs — 更新处理器(仅覆盖字符串)
  • src/modules/workflow/Application/Features/WorkflowDefinitions/DTOs/WorkflowDefinitionDto.cs
  • src/modules/workflow/Application/Features/WorkflowDefinitions/DTOs/WorkflowDefinitionDetailDto.cs
  • src/modules/workflow/Application/Features/WorkflowDefinitions/DTOs/WorkflowNodeDto.cs
  • src/modules/workflow/Application/Features/WorkflowDefinitions/DTOs/WorkflowEdgeDto.cs

Infrastructure

  • src/modules/workflow/Infrastructure/Persistence/WorkflowDbContext.cs — DbSet 定义(WorkflowNodes/WorkflowEdges)
  • src/modules/workflow/Infrastructure/Persistence/Configurations/WorkflowDefinitionConfiguration.cs
  • src/modules/workflow/Infrastructure/Persistence/Configurations/WorkflowNodeConfiguration.cs
  • src/modules/workflow/Infrastructure/Persistence/Configurations/WorkflowEdgeConfiguration.cs
  • src/modules/workflow/Infrastructure/Persistence/SeedData.cs — 种子数据

Host

  • src/BuildFlow.Host/Endpoints/workflow/WorkflowDefinition/ — 24 个 Endpoint/Request 文件(含 6 套待废弃 Node/Edge 接口)

Shared

  • src/shared/BuildFlow.Shared/Api/ApiResponse.cs — 响应信封
  • src/shared/BuildFlow.Shared/Api/ApiResponseMiddleware.cs — 响应包裹中间件

前端(build-flow.web/apps/web-antdv-next/)

  • src/api/request.ts — API 客户端(requestClient)
  • src/router/routes/modules/ — 路由 module 目录(待新增 workflow.ts)
  • src/views/ — 页面目录(待新增 workflow/)
  • src/api/core/ — API 模块目录(待新增 workflow.ts)

参考文档

  • X6 官方文档:https://x6.antv.antgroup.com/
  • X6 Vue 节点:https://x6.antv.antgroup.com/tutorial/intermediate/vue
  • X6 连接桩:https://x6.antv.antgroup.com/tutorial/basic/port
  • rag-coding-web 技能:BuildFlow 管理端前端开发约定