本文基于一套主流开源工作流引擎的源码结构,梳理其流程模式抽象与发送调度机制,供架构与平台工程师参考。
随着企业数字化转型的深入,流程管理系统(BPM/Workflow)已从早期的 OA 审批工具,演进为连接组织、表单、数据与外部系统的业务编排中枢。一个成熟的流程引擎不仅需要支持"甲提交→乙审批→丙归档"这类简单串行场景,还必须应对以下复杂需求:
国际上有 BPMN 2.0、Workflow Patterns(van der Aalst 等)等成熟理论,但其元素繁多、学习曲线陡峭,不利于企业业务人员快速建模。该引擎(及其 Java 版本 该引擎)在国内政企、制造、金融等领域有大量落地实践,其提出的流程引擎四大模式——线形流程、同表单分合流、异表单分合流、父子流程——是一种面向工程落地的模式化抽象,具有重要的理论总结与论文化价值。
本文围绕以下问题展开研究:
WorkNode.NodeSend_Send_5_5() 为核心的 5×5 发送算法,说明其如何调度线形、分合流、子线程等全部运行模式;TodolistModel 多人处理规则与 ReturnRole 退回规则,并说明其与发送、退回、移交、撤销算法的耦合方式;Vue3/src/WF 前端设计器与运行态页面,给出从设计到运行的端到端实现路径。维度 | Workflow Patterns / BPMN | 本文四大模式 |
|---|---|---|
受众 | 工作流研究人员、BPM 架构师 | 企业业务人员、实施顾问、二开工程师 |
粒度 | 43 种控制流模式 + 网关组合 | 4 种结构模式 + 统一操作集 |
数据视角 | 流程与数据分离(Data Object) | 表单驱动,PTable/FrmNode 与 RunModel 绑定 |
实现锚点 | 标准 XML 元素 | WF_Node.RunModel 字段 + NodeSend_Send_5_5() |
四大模式并非对 BPMN 的替代,而是对表单驱动型流程引擎在工程实践中的认知压缩(Cognitive Compression):将 Petri 网中的顺序、并行分支、同步合并、子网调用四类结构,与"同表/异表"数据维度、跨流程编排维度正交组合,形成可直接映射到数据库字段与 UI 图元的最小模式集。
第 2 章介绍相关工作;第 3 章给出总体架构与汽车类比模型;第 4 章至第 7 章分别论述四大模式;第 8 章论述四大模式覆盖性;第 9 章论述节点通用操作;第 10 章论述流程实例级操作;第 11 章论述节点向下运动核心算法;第 12 章论述多人处理规则;第 13 章论述退回规则;第 14 章给出案例验证;第 15 章总结与展望。
Petri 网、有限状态机为工作流的形式化基础提供了严格语义。van der Aalst 等提出的 Workflow Patterns 将流程控制结构归纳为 43 种模式,其中与本文四大模式密切相关的包括:
Workflow Pattern | 模式编号 | 本文四大模式对应 |
|---|---|---|
Sequence(顺序) | WCP-1 | 线形流程 |
Parallel Split(并行分支) | WCP-2, WCP-4 | 同/异表单分合流之分流节点 |
Synchronization(同步合并) | WCP-7, WCP-33 | 同/异表单分合流之合流节点 |
Sub-Process | WCP-41 | 父子流程 |
BPMN 2.0 以 Sequence Flow、Parallel Gateway、Inclusive Gateway、Call Activity 等元素表达类似语义。与 BPMN 相比,该引擎 的四大模式是面向业务人员的认知压缩:将网关、子流程、多实例活动等概念归纳为四种可组合的模式,降低建模门槛。
引擎 | 并行分支 | 子流程 | 特点 |
|---|---|---|---|
Activiti/Camunda | Parallel Gateway | Call Activity | BPMN 标准,与标准元素一一对应 |
Flowable | 同上 | 同上 | Activiti 分支,增强 REST 与 UI |
该引擎 | RunModel.FL/HL/FHL | SubFlowModel + PWorkID | 表单驱动,RunModel 显式标注,国内实践丰富 |
该引擎 的差异化在于:将节点运行模式(RunModel)作为一等公民写入 WF_Node 表,并在发送引擎中以 5×5 矩阵统一调度,而非完全依赖 BPMN 解析器。
本文并非提出全新的形式化工作流演算,而是对 该引擎 工程实践中沉淀的"四大模式 + 通用操作 + 统一发送引擎"进行系统化理论总结与实现剖析,为流程引擎教学、选型与二次开发提供参考。
该引擎 工作流子系统采用经典四层架构:
┌──────────────────────────────────────────────────────────────┐
│ 表现层 Vue3/src/WF │
│ FlowDesignerV2(设计器)/ MyFlow(运行态) │
├──────────────────────────────────────────────────────────────┤
│ 接口层 HttpHandler │
│ WF_MyFlow / WF_WorkOpt / WF_Admin_CCBPMDesigner │
├──────────────────────────────────────────────────────────────┤
│ 引擎层 BP.WF │
│ WorkNode / WorkReturn / WorkUnSend / ShiftWork │
│ WorkFlow / WorkReSend / Dev2Interface │
├──────────────────────────────────────────────────────────────┤
│ 模板层 BP.WF.Template │
│ Node / Direction / Cond / SFlow(子流程配置) │
├──────────────────────────────────────────────────────────────┤
│ 数据层 WF_Flow / WF_Node / WF_Direction │
│ WF_GenerWorkFlow / WF_GenerWorkerList / WF_Track │
└──────────────────────────────────────────────────────────────┘为帮助业务人员与开发人员建立统一心智模型,本文提出如下类比:
汽车概念 | 流程引擎概念 | 代码/存储对应 | 说明 |
|---|---|---|---|
汽车控制系统 | 流程引擎 | WorkNode, WorkReturn, WorkUnSend | 控制车辆如何启动、前进、倒车、变道、停车 |
汽车车厢 | 表单引擎 | CCForm, MapData, FrmNode | 承载业务信息的容器,随流程节点切换而更换"车厢布局" |
货物 | 业务数据 | 业务主表(PTable)、从表(MapDtl) | 真正被运输和处理的对象 |
前进(踩油门) | 发送 | NodeSend(), ActionType.Forward | 流程向下运动 |
倒车 | 退回 | WorkReturn.DoIt(), ActionType.Return | 流程向上回退 |
换司机 | 移交 | ShiftWork.Node_Shift_ToEmp(), ActionType.Shift | 更换当前节点处理人 |
取消本次行程 | 撤销 | WorkUnSend.DoUnSend(), ActionType.UnSend | 撤销最近一次发送 |
鸣笛催办 | 催办 | Flow_DoPress(), ActionType.Press | 提醒待办人尽快处理 |
左拐/右拐 | 方向条件 | Direction, Cond, Func_GenerNextStepNodes() | 根据条件选择下一节点 |
中途换人协助 | 前加签/后加签 | Node_Askfor(), AskforHelpSta | 临时增加协助处理人 |
停车等待 | 挂起(暂停) | WorkFlow.DoHungup(), WFState.Hungup | 暂停流程运行 |
报废(标记) | 逻辑删除 | DoDeleteWorkFlowByFlag(), WFState.Delete | 标记删除,数据保留 |
报废(拆解) | 物理删除 | DoDeleteWorkFlowByReal() | 彻底清除全部关联数据 |
拖车到指定路口 | 调整 | Flow_ReSend(), ActionType.Adjust | 将运行中流程跳到指定节点/人员 |
行程记录回放 | 回滚 | DoRebackFlowData() | 已完成流程恢复到历史节点 |
该类比的核心价值在于:流程引擎不负责"装什么货"(业务逻辑在表单与数据中),而负责"怎么运"(节点如何推进、如何回退、如何并行)。表单引擎与流程引擎解耦,使得同一流程引擎可驱动不同业务表单,同一表单也可被不同流程复用。
进一步地,方向条件对应汽车导航中的"路口选择":在 Func_GenerNextStepNodes() 中,引擎遍历当前节点的所有出边(WF_Direction),逐条评估 Cond 条件——表达式比较、SQL 查询、岗位/部门匹配等——决定"左拐"还是"右拐"。这与 BPMN 的 Exclusive Gateway / Inclusive Gateway 语义等价,但在 该引擎 中无需额外网关节点,条件直接附着在连线上,降低了设计器图元的数量。
**阻塞模式(BlockModel)**可类比"前方施工禁行":在节点发送前,若 BlockModel ≠ None,引擎检查子线程是否全部完成(CurrNodeAll)、指定子流程是否到达某节点(SpecSubFlowNode)、或自定义 SQL/表达式是否返回阻塞信号,满足则禁止发送。这使父子流程、分合流场景下的"等待依赖"得以声明式配置。
实体 | 表/类 | 关键字段 | 含义 |
|---|---|---|---|
流程 | WF_Flow / Flow | No, Name | 流程模板 |
节点 | WF_Node / Node | NodeID, RunModel, TodolistModel | 活动单元 |
方向 | WF_Direction / Direction | Node, ToNode | 节点连线 |
条件 | WF_Cond / Cond | 表达式/SQL | 方向生效条件 |
子流程配置 | WF_NodeSubFlow | SubFlowNo, SubFlowModel | 父子流程绑定 |
实体 | 表/类 | 关键字段 | 含义 |
|---|---|---|---|
工作实例 | WF_GenerWorkFlow | WorkID, FID, PWorkID, WFState | 一次流程运行 |
工作人员列表 | WF_GenerWorkerList | WorkID, FK_Node, FK_Emp, IsPass | 待办/已办 |
轨迹 | WF_Track | ActionType, NDFrom, NDTo | 操作审计日志 |
关联键 | 适用模式 | 含义 |
|---|---|---|
WorkID | 全部模式 | 工作实例主键,标识一次运行 |
FID | 分合流 | 子线程的 FID 指向干流程 WorkID |
PWorkID | 父子流程 | 子流程的 PWorkID 指向父流程 WorkID |
重要区分:FID 用于同一流程内的干/子线程关系;PWorkID 用于不同流程间的父/子调用关系。二者正交,可并存。
该引擎 将节点的结构角色抽象为 RunModel 枚举(定义于 EnumLib.cs):
值 | 枚举名 | 中文标签 | 所属模式 |
|---|---|---|---|
0 | Ordinary | 线形 | 模式一 |
1 | HL | 合流 | 模式二/三 |
2 | FL | 分流 | 模式二/三 |
3 | FHL | 分合流 | 模式二/三 |
4 | SubThreadSameWorkID | 同表单子线程 | 模式二 |
5 | SubThreadUnSameWorkID | 异表单子线程 | 模式三 |
父子流程(模式四)不通过 RunModel 表达,而使用 NodeType.SubFlowNode 与 SubFlowModel 枚举。
线形流程是流程引擎中最基础的模式,指节点按有向图顺序(或条件分支)推进,RunModel = Ordinary(0),同一 WorkID 在节点间传递,FID = 0。
典型拓扑:
[开始节点] → [节点A] → [节点B] → [节点C] → [结束节点]
↘ [节点D] → [节点E] ↗ (条件分支仍属线形)Direction + Cond 实现,不改变 RunModel);WorkNode.NodeSend();NodeSend_11(Node toND)——普通节点到普通节点;WorkID 保持不变,业务主表 OID = WorkID;Func_GenerNextStepNodes() 根据方向条件计算下一节点集合;Func_GenerWorkerLists() 按 DeliveryWay(按部门、按角色、按表单字段等)计算接收人;WF_GenerWorkerList 中插入下一节点待办记录;ActionType.Forward。线形流程中的分支不依赖专用网关节点,而通过方向条件实现:
Direction(WF_Direction)可配置一条或多条 Cond(条件);Func_GenerNextStepNodes() 遍历当前节点的所有出边,评估条件,返回满足条件的下一节点集合;环节 | 文件/组件 | 说明 |
|---|---|---|
设计器形状 | Vue3/src/WF/Admin/FlowDesignerV2/shapes/index.ts | mode=0,矩形,标签"线性" |
节点属性 | Vue3/src/WF/Admin/AttrNode/NodeExt.ts | 配置 TodolistModel、退回规则等 |
方向条件 | NodeContextMenu → 方向条件配置 | 写入 WF_Cond |
运行态 | MyFlow.vue → MyFlow_Init → MyFlowGener.vue | 后端返回 PageName 动态加载 |
线形流程是四大模式的基石,复杂度最低,却是理解发送引擎、多人规则、退回规则的入门路径。其 Ordinary → Ordinary 路径在 5×5 矩阵中对应 (0,0) 单元格。
同表单分合流指在**同一物理表(PTable)**上,由分流节点(RunModel.FL=2)启动多个子线程(RunModel.SubThreadSameWorkID=4),各子线程并行处理后,经合流节点(RunModel.HL=1)或分合流节点(RunModel.FHL=3)同步汇总,再继续主干流程。
典型拓扑:
[普通] → [分流 FL] ─┬→ [同表单子线程-甲] ─┐
├→ [同表单子线程-乙] ─┼→ [合流 HL] → [普通]
└→ [同表单子线程-丙] ─┘角色 | WorkID | FID | 物理表 |
|---|---|---|---|
干流程 | W0 | 0 | PTable(主表) |
子线程1 | W1 | W0 | PTable(同一表,OID=W1) |
子线程2 | W2 | W0 | PTable(同一表,OID=W2) |
合流后 | W0 | 0 | PTable(回到干流程 WorkID) |
子线程与干流程共享同一 PTable,通过不同的 OID(= 各自 WorkID)区分数据行。子线程的 FID 字段指向干流程 WorkID,建立从属关系。
方法:WorkNode.NodeSend_24_SameSheet(Node toNode)
文件:该引擎/Components/BP.WF/WF/WorkNode.cs
算法步骤:
DeliveryWay(如 ByDtlAsSubThreadEmps 从明细表行确定人员)计算子线程接收人列表;WorkID;wk.FID = 干流程 WorkID;Copy(this.HisWork));WF_GenerWorkerList 中为每个子线程插入待办;ActionType.ForwardFL。方法:WorkNode.NodeSend_53_SameSheet_To_HeLiu(Node toNode)
算法步骤:
GenerWorkerList.IsPass = 1(已办);GenerWorkFlow.AtPara 中维护 ThreadCount(已到达合流点的子线程数);passRate = numPassed / numAll × 100;passRate ≥ Node.PassRate(合流通过率,默认 100%)时:GenerWorkerList.IsPass 置为 0(待办可见);GenerHieLiuHuiZhongDtlData_2013() 将子线程从表数据汇总到主干;ActionType.ForwardHL。Node.PassRate 属性控制合流触发条件:
PassRate = 100:所有子线程完成后才触发合流(默认);PassRate = 50:过半子线程完成即可触发合流;功能 | 属性/方法 | 说明 |
|---|---|---|
增加子线程 | ThreadIsCanAdd | 分流节点是否允许运行时增加子线程 |
删除子线程 | ThreadIsCanDel | 是否允许删除子线程 |
移交子线程 | ThreadIsCanShift | 是否允许移交子线程处理人 |
合流点补加 | ThreadIsCanAddOfHL | 合流节点是否允许补加子线程 |
子线程查询 | Dev2Interface.DB_GenerHLSubFlowDtl_TB() | 查询同表单合流子线程列表 |
环节 | 文件 | 说明 |
|---|---|---|
设计器 | shapes/index.ts | 分流(2)、合流(1)、分合流(3)、同表单(4) 各有独立多边形 |
运行态 | MyFLDealThread.vue | 分合流专用处理页 |
子线程管理 | ThreadDtl.vue | 工具栏"子线程"弹窗 |
轨迹 | ActionType.ForwardFL(6) / ForwardHL(7) | 显示"分流前进""合流前进" |
WorkUnSend.UnSend_FHL() 针对分合流节点的专用撤销逻辑;WorkReturn 中的分合流退回矩阵(如子线程退分流、合流退子线程等),详见第 13 章。同表单分合流解决了"同一单据、多人并行、数据共享"的核心需求,是政企会签类场景的主力模式。其关键在于 FID 关联与 PassRate 合流控制。
异表单分合流与同表单分合流共享分流(FL)、合流(HL)、分合流(FHL)节点类型,差异在于子线程节点使用 RunModel.SubThreadUnSameWorkID=5,每个子线程可绑定独立表单/物理表。
典型拓扑:
[普通] → [分流 FL] ─┬→ [异表单子线程-法务表单] ─┐
├→ [异表单子线程-财务表单] ─┼→ [合流 HL] → [普通]
└→ [异表单子线程-技术表单] ─┘维度 | 同表单 (RunModel=4) | 异表单 (RunModel=5) |
|---|---|---|
物理表 | 与干流程相同 PTable | 独立表单/表(FrmNodes, RefOneFrmTree) |
WorkID 规则 | 每接收人一个 WorkID | 受 USSWorkIDRole 控制 |
数据复制 | Copy(HisWork) 同表复制 | GEEntityOID 独立插入 |
分流方法 | NodeSend_24_SameSheet() | NodeSend_24_UnSameSheet() |
合流方法 | NodeSend_53_SameSheet_To_HeLiu() | NodeSend_53_UnSameSheet_To_HeLiu() |
查询 API | DB_GenerHLSubFlowDtl_TB() | DB_GenerHLSubFlowDtl_YB() |
退回特性 | 标准退回 | 可启用 IsKillEtcThread(退回时终止其他子线程) |
NodeExt.cs 中定义:
值 | 含义 | 场景 |
|---|---|---|
0 | 仅生成一个 WorkID | 多人共享同一子线程实例(协作模式) |
1 | 按接收人生成 WorkID | 每人独立子线程实例(默认) |
方法:WorkNode.NodeSend_24_UnSameSheet(Nodes toNDs)
USSWorkIDRole 决定 WorkID 生成策略;FrmNodes 配置表单与节点的关联;GEEntityOID);wk.FID = 干流程 WorkID。方法:WorkNode.NodeSend_53_UnSameSheet_To_HeLiu(Node nd)
ThreadCount + PassRate);MapDtl.IsHLDtl = true)。代码中明确规定(WorkNode.cs):
这些约束保证了 5×5 发送矩阵的确定性。
环节 | 文件 | 说明 |
|---|---|---|
设计器 | shapes/index.ts | mode=5/16,标签"异表单",梯形多边形 |
属性 | NodeExt.ts | USSWorkIDRole 配置 |
退回 | ReturnWork.vue | RunModel=4/5 时启用 IsKillEtcThread |
运行态 | MyFLDealThread.vue | 与同表单共用 |
异表单分合流将流程引擎的表达能力从"同构并行"扩展到"异构并行",是复杂协同场景的关键模式。其与同表单的统一合流控制(PassRate + ThreadCount)体现了 5×5 算法的复用性。
父子流程实现跨流程实例的编排:父流程在某个节点触发子流程,子流程独立运行,结束后可反填父流程字段并驱动父流程继续。与分合流的本质区别在于:
维度 | 分合流 | 父子流程 |
|---|---|---|
关联键 | FID(同一 FlowNo 内) | PWorkID(不同 FlowNo 间) |
节点标识 | RunModel.FL/HL/SubThread* | NodeType.SubFlowNode |
配置表 | 无额外配置 | WF_NodeSubFlow |
生命周期 | 子线程随干流程结束而结束 | 子流程可独立运行、独立结束 |
FrmSubFlow.cs 定义:
类型 | 枚举值 | 触发方式 | 典型场景 |
|---|---|---|---|
手动子流程 | HandSubFlow = 0 | 用户在父流程表单点击启动 | 按需发起子审批 |
自动子流程 | AutoSubFlow = 1 | 节点发送时 CallAutoSubFlow() | 到达某节点自动创建 |
延续子流程 | YanXuFlow = 2 | 子流程结束后延续父流程 | 阶段性子任务 |
模式 | 枚举 | 含义 |
|---|---|---|
下级子流程 | SubLevel | 子流程挂到当前父 WorkID |
同级子流程 | SameLevel | 子流程挂到父流程的父级 |
[父流程-节点A] → [父流程-节点B(含子流程配置)]
↓ 自动/手动启动
[子流程-开始] → [子流程-审批] → [子流程-结束]
↓ 子流程结束
反填父流程字段 → 驱动父流程到下一节点方法:Dev2Interface.SetParentInfo(subFlowNo, subFlowWorkID, parentWorkID, ...)
UPDATE WF_GenerWorkFlow
SET PFlowNo=..., PWorkID=..., PNodeID=..., PEmp=...
WHERE WorkID=子流程WorkID入口:WF_WorkOpt.SendSingleSubFlow()
SubLevel:Node_CreateBlankWork(subFlowNo, ..., parentWorkID, ...) + SetParentInfo() SameLevel:挂到 gwf.PWorkID方法:WorkNode.CallAutoSubFlow()
InvokeTime(节点发送前/后)、条件表达式、SQL 数据源批量创建子流程实例;SubFlowHidTodolist 可隐藏父流程待办(IsPass=100)。方法:WorkNodePlus.SubFlowOver_* 系列
规则 AllSubFlowOverRole:
规则 | 行为 |
|---|---|
None | 不驱动父流程 |
SendParentFlowToNextStep | 自动发送父流程到下一步 |
OverParentFlow | 结束父流程 |
字段反填:BackCopyRole 控制子流程字段向父流程的映射。
环节 | 文件 | 说明 |
|---|---|---|
设计器 | subFlowNodes[0] | 六边形,NodeType.SubFlowNode=3 |
创建 | useVueFlowNode.ts → CreateSubFlowNode | |
配置向导 | GPN_NewSubFlow.ts | 手工/自动/延续/导航 |
实体 | SubFlowHand.ts, SubFlowAuto.ts, SubFlowYanXu.ts | |
运行态 | SubFlow.vue | 嵌入父流程表单 |
轨迹 | Track.vue | 聚合子流程实例卡片 |
动作 | ActionType.CallChildenFlow(9), StartChildenFlow(10) |
父子流程解决了跨业务域流程编排问题,是四大模式中唯一涉及多个 FlowNo 的模式。PWorkID 关联键使其与分合流的 FID 完全正交。
四大模式并非互斥,一个完整企业流程通常是它们的组合:
[线形] → [分流] → [同表单子线程×3] → [合流] → [线形]
↓
[自动启动父子流程]
↓
[子流程:线形审批]
↓
[反填父流程] → [线形] → [结束]企业需求 | 对应模式 | 说明 |
|---|---|---|
串行审批、条件分支 | 线形流程 | 通过方向条件实现分支 |
同一单据多人并行、会签 | 同表单分合流 | 共享 PTable |
并行子任务、异构表单 | 异表单分合流 | 独立表单绑定 |
跨业务域编排、主从单据 | 父子流程 | PWorkID 关联 |
复杂监管(并行+子流程+汇总) | 多模式组合 | 四大模式正交组合 |
从结构维度看,任意流程图均可分解为以下基本元素:
从操作维度看,第 9–13 章论述的通用操作(发送、退回、移交等)在所有模式下均可用统一引擎调度。因此,四大模式 + 通用操作构成了流程应用环境的最小完备集。
流程引擎在每个节点上提供一组通用操作,类比汽车驾驶行为(见 3.2 节)。本节结合 BP.WF 源码,逐一阐述各操作的触发条件、算法与轨迹记录。
操作 | 汽车类比 | 核心类/方法 | ActionType | WFState 变化 |
|---|---|---|---|---|
发送(前进) | 踩油门 | WorkNode.NodeSend() | Forward(1) | Runing |
退回(后退) | 倒车 | WorkReturn.DoIt() | Return(2) | ReturnSta(5) |
移交(换司机) | 换司机 | ShiftWork.Node_Shift_ToEmp() | Shift(3) | Shift(6) |
撤销(取消任务) | 取消行程 | WorkUnSend.DoUnSend() | UnSend(5) | 恢复上一状态 |
催办(鸣笛) | 鸣笛 | Dev2Interface.Flow_DoPress() | Press(18) | 不变 |
前加签 | 临时乘客 | Dev2Interface.Node_Askfor() | AskforHelp(24) | Askfor(8) |
后加签 | 临时乘客 | Node_Askfor() + AskforHelpSta | ForwardAskfor(25) | AskForReplay(10) |
会签(前加签扩展) | 多位乘客 | Node_HuiQian_AddEmps() | HuiQian(30) | HuiQianing |
删除 | 报废 | WorkFlow.DoDeleteWorkFlowByFlag/Real() | DeleteFlowByFlag(19) | Delete(7) |
挂起(暂停) | 停车 | WorkFlow.DoHungup() | Hungup(15) | Hungup(4) |
前端 MyFlowGener.vue
→ HttpHandler WF_WorkOpt
→ Dev2Interface.Node_SendWork()
→ WorkNode.NodeSend()
→ NodeSend_Send_5_5()NodeSend() 方法体分为三大部分(WorkNode.cs 代码注释明确,约第 7613 行):
IsOver)或逻辑删除(WFState.Delete);Flow_IsCanDoCurrentWork);BlockModel)检查;EventListNode.SendWhen 事件。GenerWorkFlow 状态;WF_Track 轨迹;CallAutoSubFlow);EventListNode.SendSuccess 事件。前端 ReturnWork.vue
→ WF_WorkOpt.DoReturnWork()
→ Dev2Interface.Node_ReturnWork()
→ WorkReturn.DoIt()returnToNodeID == 0,调用 DB_GenerWillReturnNodes(workid) 按 ReturnRole 计算可退节点列表;DoIt() 分支:ReturnToParentFlow();DoOrderReturn();TeamReturnRole==1 → DoOrderReturn();ExeReturn1_1()、ExeReturn2_4() 等;GenerWorkerList、审核轨迹;IsBackTrack 控制是否保留中间节点业务数据;WFState = ReturnSta,写 ActionType.Return 或 ReturnAndBackWay(201)。详见第 13 章退回规则。
前端 Shift.vue
→ WF_WorkOpt.Shift_Save()
→ Dev2Interface.Node_Shift()
→ ShiftWork.Node_Shift_ToEmp()TodolistModel 分支:GenerWorkerList,插入被移交人记录,重算 SDT(应完成时间);GenerWorkerList;gwf.WFState = WFState.Shift;TodoEmps 字段;ActionType.Shift,触发 EventListNode.ShitAfter 与消息推送。撤销移交:ShiftWork.DoUnShift() → ActionType.UnShift(4)。
前端在途列表
→ WF.Runing_UnSend()
→ Dev2Interface.Flow_DoUnSend()
→ WorkUnSend.DoUnSend()CancelRole:若为 None 则禁止撤销;CancelDisWhenRead 控制);IsEnableUnSendWhenHuiQian 可放开);GenerWorkerList 中 IsPass=1 的记录确定 cancelToNodeID;DoUnSendIt()、DoUnSendFeiLiu()、DoUnSendHeiLiu_Main() 等;ActionType.UnSend;Return/ReturnAndBackWay,撤销后恢复 WFState.ReturnSta。值 | 含义 |
|---|---|
OnlyNextStep | 仅允许撤销到下一步 |
None | 禁止撤销 |
NextStepAndStartNode | 可撤销到下一步和开始节点 |
SpecNodes | 可撤销到指定节点 |
前端 Press.vue
→ WF_WorkOpt.Press()
→ Dev2Interface.Flow_DoPress()GenerWorkerList(IsPass=0)获取待办人;PressWork 催办记录表(防重复:WorkID_UserNo_ToEmp_Date);Port_SendMsg(..., SMSMsgType.DoPress) 发送消息(站内信/短信/微信等);ActionType.Press;会签主持人催办:HuiQian_Press() 专门催办会签中的待办人。
该引擎 将"加签"与"会签"分为两套机制:
概念 | 说明 |
|---|---|
前加签 | 处理人先请他人协助,协助完成后由自己继续发送 |
后加签 | 处理人先发送,加签人协助后流程才到下一步 |
入口:WF_WorkOpt.Askfor() → Dev2Interface.Node_Askfor()
算法:
WFState.Askfor;GenerWorkerList 为被加签人插入待办,PassInt 存储加签模式;DealAskForState() 根据 AskforHelpSta:AfterDealSend(5):加签后直接发送 → 继续 NodeSend 到下一步(后加签语义);AfterDealSendByWorker(6):加签后由发起人发送 → WFState.AskForReplay,待办回到发起人(前加签语义);Node_AskforReply():加签人回复后触发发送或返回。方法 | 功能 |
|---|---|
Node_HuiQian_Init() | 初始化会签界面数据 |
Node_HuiQian_AddEmps() | 增加会签人(IsPass=-1) |
Node_HuiQian_AddLeader() | 激活组长(IsPass=-1→0) |
Node_HuiQianDone() | 发起会签,通知会签人 |
Node_HuiQian_Delete() | 删除会签人 |
会签完成判断在 NodeSend 发送后处理中,依据 HuiQianRole、HuiQianLeaderRole、TeamLeaderConfirmRole 决定是否全员完成。
方法:WorkFlow.DoDeleteWorkFlowByFlag()
WFState = Delete;GenerWorkerList(待办不可见);WF_Track 轨迹和业务主表数据;DoUnDeleteWorkFlowByFlag()。方法:WorkFlow.DoDeleteWorkFlowByReal()
WF_Track、GERpt、MapDtl、各节点表单数据;WF_GenerWorkFlow、WF_GenerWorkerList、WF_CCList;方法:WorkFlow.DoDeleteWorkFlowByWriteLog()
WorkFlowDeleteLog;DoDeleteWorkFlowByReal()。值 | 含义 |
|---|---|
None | 禁止删除 |
DeleteByFlag | 仅逻辑删除 |
DeleteAndWriteToLog | 写日志后物理删除 |
DeleteReal | 直接物理删除 |
ByUser | 由用户选择删除方式 |
该引擎 中"暂停"对应 挂起(Hungup)。
方法:WorkFlow.DoHungup()
WFState = Hungup;AtPara 记录挂起人、方式(HungupWay:永久/指定日期)、审批人;HungupWorkAgree() 解除挂起,或 HungupWorkReject() 拒绝;ActionType.Hungup(15) / UnHungup(16)。入口:WF_WorkOpt.Hungup_Save() → Dev2Interface.Node_HungupWork()。
节点通用操作作用于当前节点,而流程实例级操作作用于整个流程实例,通常由管理员执行。
定义:将运行中的流程实例跳转到指定节点、指定人员,使其在该节点产生待办。
入口:Dev2Interface.Flow_ReSend(workid, toNodeID, toEmps, note, model)
算法:
type=1(向前跳过):Flow_ReSendBySkip()——当前待办转已办、强制结束子流程、重建目标节点待办;WorkReSend.DoIt() 按 RunModel 矩阵调整(类似退回的逆操作);ActionType.Adjust(31)。应用场景:流程发错节点、需要管理员干预跳转、跳过某些审批环节。
定义:将已完成的流程实例恢复到指定历史节点,使其重新进入运行状态。
入口:WF_WorkOpt.Rollback_Done() → FlowExt.DoRebackFlowData()
算法:
WF_Track 轨迹重建 GenerWorkFlow 和 GenerWorkerList;WFState = ReturnSta;DoUnDeleteWorkFlowByFlag)也调用此接口。与退回的区别:
维度 | 退回 | 回滚 |
|---|---|---|
执行人 | 当前节点处理人 | 管理员 |
流程状态 | 运行中 | 已完成 |
权限 | 节点 ReturnRole | 流程 RollbackPower |
回滚权限 RollbackPower(Flow.cs):
值 | 含义 |
|---|---|
0 | 管理员 |
1 | 流程参与人 |
2 | 指定节点处理人 |
3 | 指定角色 |
4 | 指定人员 |
已在 9.8 节论述。实例级删除通过 WF_WorkOpt.DeleteFlowInstance_DoDelete() 入口,按 DelWorkFlowRole 分支调用不同删除方法。
操作 | 适用状态 | 核心方法 | 可逆性 |
|---|---|---|---|
调整 | 运行中 | Flow_ReSend() | 可再次调整 |
回滚 | 已完成 | DoRebackFlowData() | 可再次回滚 |
逻辑删除 | 任意 | DoDeleteWorkFlowByFlag() | 可恢复 |
物理删除 | 任意 | DoDeleteWorkFlowByReal() | 不可恢复 |
该引擎 的发送引擎经历了多次演进。2012 年升级的 NodeSend_Send_5_5() 方法将发送逻辑统一为 5×5 矩阵:当前节点 RunModel(行)× 目标节点 RunModel(列),每个单元格对应一个专用发送方法。这一设计避免了为每种模式编写独立引擎,保证了撤销、退回、轨迹的一致性。
该引擎 源码注释将发送逻辑称为"5×5 算法"(WorkNode.NodeSend(),2012-11-11 厦门升级),意指当前节点 5 类运行角色 × 目标节点 5 类运行角色的笛卡尔调度。其中子线程的"同表单/异表单"是同一行内的分支,而非独立行。RunModel 枚举共 6 个值(0–5),在 NodeSend_Send_5_5() 的 switch 中合并为 5 个 case 分支。
目标节点 RunModel
Ord(0) HL(1) FL(2) FHL(3) Same(4) UnSame(5)
当前 Ordinary(0) [11] [11] [11] [11] 禁止 禁止
节点 HL(1) [31] [31] [31] [31] 禁止 禁止
RunM FL(2) [11] [11] [11] [11] [24_S] [24_U]
odel FHL(3) [11] [11] 禁止 [11] [24_S] [24_U]
Same(4) 禁止 [53_S] 禁止 [53_S] [11] 禁止
UnSame(5) 禁止 [53_U] 禁止 [53_U] 禁止 [11]注:[11] = NodeSend_11();[24_S] = NodeSend_24_SameSheet();[24_U] = NodeSend_24_UnSameSheet();[31] = NodeSend_31();[53_S/U] = 合流方法。空白/禁止格在代码中 throw Exception 提示流程设计错误。
核心源码结构(WorkNode.NodeSend_Send_5_5(),该引擎/Components/BP.WF/WF/WorkNode.cs):
switch (this.HisNode.HisRunModel)
{
case RunModel.Ordinary: // 行1:普通节点
switch (toND.HisRunModel) {
case RunModel.Ordinary: this.NodeSend_11(toND); break;
case RunModel.SubThreadSameWorkID:
case RunModel.SubThreadUnSameWorkID:
throw new Exception("普通节点不能连接子线程节点");
}
break;
case RunModel.FL: // 行2:分流节点
switch (toND2.HisRunModel) {
case RunModel.SubThreadSameWorkID:
NodeSend_24_SameSheet(toND2); break;
case RunModel.SubThreadUnSameWorkID:
NodeSend_24_UnSameSheet(toNDs); break;
}
break;
case RunModel.HL: // 行3:合流节点
this.NodeSend_31(toND3); break;
case RunModel.SubThreadSameWorkID: // 行4/5:子线程
case RunModel.SubThreadUnSameWorkID:
if (toND5.HisRunModel == RunModel.HL)
NodeSend_53_SameSheet_To_HeLiu(toND5); // 或 UnSameSheet 版本
break;
}设计约束(源码强制校验,保证矩阵确定性):
workflow_error_4);workflow_error_5);单元格 | 方法 | 场景 |
|---|---|---|
(0,0) | NodeSend_11() | 线形 → 线形(最常用) |
(0,4) | NodeSend_24_SameSheet() | 线形 → 同表单子线程(经分流) |
(2,4) | NodeSend_24_SameSheet() | 分流 → 同表单子线程 |
(2,5) | NodeSend_24_UnSameSheet() | 分流 → 异表单子线程 |
(4,1) | NodeSend_53_SameSheet_To_HeLiu() | 同表单子线程 → 合流 |
(5,1) | NodeSend_53_UnSameSheet_To_HeLiu() | 异表单子线程 → 合流 |
(1,0) | NodeSend_31() | 合流 → 线形(合流后继续) |
(3,*) | 分合流行 | 分合流节点可再次分流 |
NodeSend(jumpToNode, jumpToEmp)
│
├─ 第1部分:发送前检查
│ ├─ 权限校验
│ ├─ 状态校验(已结束/已删除/会签中)
│ ├─ 阻塞模式检查
│ └─ SendWhen 事件
│
├─ 第2部分:5×5 核心算法
│ ├─ Func_GenerNextStepNodes() // 计算下一节点
│ ├─ 遍历下一节点集合
│ │ └─ switch(当前RunModel)
│ │ └─ switch(目标RunModel)
│ │ └─ 调用对应 NodeSend_XY() 方法
│ └─ 子流程自动启动 CallAutoSubFlow()
│
└─ 第3部分:发送后处理
├─ 更新 GenerWorkFlow
├─ 写 Track
├─ 多人规则(队列/协作/抢办)
├─ 会签完成判断
├─ 抄送 CC
├─ DTS 同步
├─ 消息推送
└─ SendSuccess 事件方法:Func_GenerNextStepNodes()
WF_Direction);Cond):方法:Func_GenerWorkerLists(Node toNode)
DeliveryWay(接收人规则);ByStation、ByDept、BySQL、BySelected、ByDtlAsSubThreadEmps 等 20+ 种);(EmpNo, EmpName) 列表。当同一节点有多个接收人时,TodolistModel 决定他们如何协作:
值 | 枚举名 | 中文 | 行为 | 汽车类比 |
|---|---|---|---|---|
0 | QiangBan | 抢办 | 谁先处理谁生效,其他人待办自动消除 | 多人抢开一辆车 |
1 | Teamup | 协作 | 所有人都要处理,最后一人发送到下一步 | 多人轮流驾驶 |
2 | Order | 队列 | 按顺序处理,最后一人发送到下一步 | 排队驾驶 |
3 | Sharing | 共享 | 需申请后才能处理 | 需拿钥匙才能开 |
4 | TeamupGroupLeader | 协作组长 | 组员协作 + 组长确认 | 车队队长最后验收 |
定义位置:EnumLib.cs 第 640 行;节点属性 Node.TodolistModel(WF/Node.cs 第 2633 行)。
行为:
GenerWorkerList(IsPass=0);IsPass 置为 1(自动已办);适用场景:值班审批、客服处理——谁有空谁处理。
行为:
GenerWorkerList(IsPass=0);IsPass 置为 1;IsAllPassed()。适用场景:多部门会签、联合审批——所有人都必须发表意见。
行为:
GenerWorkerList,仅第一人 IsPass=0,其余 IsPass=-1(未到);IsPass 从 -1 变为 0;DealOradeNode() 方法管理队列推进;适用场景:逐级审批——必须按职级顺序处理。
行为:
适用场景:任务池、工单抢单。
行为:
TeamLeaderConfirmRole 计算;会签是协作模式的扩展,通过 HuiQianRole 控制:
HuiQianRole | 含义 |
|---|---|
Teamup(1) | 协作会签 |
TeamupGroupLeader(4) | 组长会签 |
组长会签规则 HuiQianLeaderRole:
值 | 含义 |
|---|---|
OnlyOne | 仅一个组长 |
LastOneMain | 最后一个组长为主持人 |
EveryOneMain | 每人都可以是主持人 |
操作 | 抢办 | 协作 | 队列 |
|---|---|---|---|
发送 | 一人发送即可 | 最后一人发送 | 按序发送 |
退回 | 标准退回 | TeamReturnRole 控制 | DoOrderReturn() |
移交 | UPDATE 单人 | 替换 GenerWorkerList | 替换当前队列项 |
撤销 | 标准撤销 | 协作节点特殊处理 | 恢复队列状态 |
每个节点可配置 ReturnRole,决定处理人可以退回到哪些历史节点:
值 | 枚举名 | 中文 | 行为 |
|---|---|---|---|
0 | CanNotReturn | 不能退回 | 该节点禁止退回操作 |
1 | ReturnPreviousNode | 仅上一节点 | 只能退回到直接前驱节点 |
2 | ReturnAnyNodes | 任意历史节点 | 可退回到任何已处理过的节点(默认) |
3 | ReturnSpecifiedNodes | 指定节点 | 仅可退回到配置的节点列表 |
4 | ReturnStartNode | 退到开始节点 | 只能退回到流程开始节点 |
5 | ByReturnLine | 按退回线 | 由流程图上标注的退回方向决定 |
定义位置:EnumLib.cs 第 1250 行;计算可退节点:Dev2Interface.DB_GenerWillReturnNodes(workid)。
方法:DB_GenerWillReturnNodes(workid)
ReturnRole;WF_GenerWorkerList 历史记录;GenerWorkerList 而非按 ReturnRole;WorkReturn.DoIt() 按 当前 RunModel × 目标 RunModel 矩阵调用执行方法:
方法 | 场景 |
|---|---|
ExeReturn1_1() | 线形 → 线形 |
ExeReturn2_4() | 分流 → 同表单子线程 |
ExeReturn3_2() | 合流 → 分流 |
ExeReturn3_4() | 合流 → 同表单子线程 |
ExeReturn5_2() | 同表单子线程 → 分流 |
DoItOfKillEtcThread() | 子线程退分流且杀死其他子线程 |
ReturnToParentFlow() | 跨流程退回到父流程 |
DoOrderReturn() | 队列/协作模式专用退回 |
ActionType:ReturnAndBackWay(201)
行为:
GenerWorkFlow.AtPara 中记录返回路径;场景 | 规则 |
|---|---|
子线程退分流 | 可选 IsKillEtcThread:是否终止其他子线程 |
合流退子线程 | 需清理 ThreadCount |
分流节点 | 不按 ReturnRole 算,直接查 GenerWorkerList |
协作节点 | TeamReturnRole==1 时走 DoOrderReturn() |
WFState.ReturnSta;ReturnAndBackWay,撤销后保留原路返回标记;IsBackTrack 控制。环节 | 文件 | 说明 |
|---|---|---|
退回页面 | ReturnWork.vue | 展示可退节点列表 |
初始化 | WF_WorkOpt.Return_Init() | 调用 DB_GenerWillReturnNodes |
执行 | WF_WorkOpt.DoReturnWork() | 调用 Node_ReturnWork |
异表单 | ReturnWork.vue | RunModel=4/5 时显示 IsKillEtcThread 选项 |
业务描述:采购员提交采购申请,系统按明细表行自动分派给各采购专员并行询价,全部完成后汇总到采购主管处合流审批。
配置要点:
节点 | RunModel | 关键属性 |
|---|---|---|
开始 | Ordinary | — |
采购员提交 | Ordinary | — |
分流 | FL(2) | DeliveryWay=ByDtlAsSubThreadEmps |
专员询价×N | SubThreadSameWorkID(4) | 共享主表 |
主管合流 | HL(1) | PassRate=100 |
结束 | Ordinary | — |
验证步骤:
WF_Track 轨迹含 ForwardFL 和 ForwardHL。业务描述:项目经理提交立项申请(主表单),系统自动分流到法务、财务、技术三方,各方填写独立评审表单,完成后合流汇总到总经理审批。
配置要点:
节点 | RunModel | 关键属性 |
|---|---|---|
开始 | Ordinary | — |
项目经理提交 | Ordinary | 主表单 Frm_Project |
分流 | FL(2) | 出边 3 条,分别连向 3 个子线程 |
法务评审 | SubThreadUnSameWorkID(5) | 绑定 Frm_LegalReview |
财务评审 | SubThreadUnSameWorkID(5) | 绑定 Frm_FinanceReview |
技术评审 | SubThreadUnSameWorkID(5) | 绑定 Frm_TechReview |
总经理合流 | HL(1) | PassRate=100,合流从表 IsHLDtl=true |
结束 | Ordinary | — |
数据流:
WorkID=W0,FID=0,主表 Frm_Project 的 OID=W0;W1(FID=W0)、W2(FID=W0)、W3(FID=W0),各自绑定不同物理表;Frm_LegalReview,财务填写 Frm_FinanceReview,互不干扰;ThreadCount 递增至 3,触发合流;NodeSend_53_UnSameSheet_To_HeLiu() 将各子表单关键字段汇总到主表合流从表;验证步骤:
WF_Track 含 3 条 SubThreadForward 与 1 条 ForwardHL;IsKillEtcThread,验证其余子线程被终止。业务描述:合同审批流程到达"法务审核"节点时自动启动法务子流程;子流程独立走完法务审批链后,反填合同审批单字段并自动驱动父流程到下一节点。
配置要点:
配置项 | 值 | 说明 |
|---|---|---|
父流程节点 | NodeType.SubFlowNode | 六边形子流程节点 |
子流程类型 | AutoSubFlow(1) | 节点发送时自动触发 |
子流程模式 | SubLevel | PWorkID 指向父 WorkID |
启动时机 | InvokeTime = 发送后 | CallAutoSubFlow() |
全部结束规则 | SendParentFlowToNextStep | 子流程全结束后自动发送父流程 |
字段反填 | BackCopyRole | 子流程"法务意见"→父流程"法务审核意见" |
待办隐藏 | SubFlowHidTodolist=true | 父节点 IsPass=80,待办不可见 |
生命周期:
NodeSend_Send_5_5() 正常发送后,触发 CallAutoSubFlow();W_sub,PWorkID=W_parent,PFlowNo=合同审批,PNodeID=法务审核节点;IsPass=80);SubFlowOver_SendParentFlowToNextStep() 检测全部子流程结束;NodeSend 到下一节点;ActionType.StartChildenFlow(10) 与 CallChildenFlow(9)。验证步骤:
PWorkID 正确;Flow_DoPress(workID, msg, isPressSubFlow=true) 可递归催办子流程待办人。业务描述:员工提交请假申请,部门经理与 HR 按队列模式逐级审批,最后由分管领导协作会签后归档。全程为线形流程,无分合流。
配置要点:
节点 | RunModel | TodolistModel | 说明 |
|---|---|---|---|
开始 | Ordinary | — | 员工填写 |
部门经理 | Ordinary | Order(2) | 若多人则按职级队列 |
HR 审批 | Ordinary | QiangBan(0) | 多人抢办,谁先处理谁生效 |
分管领导 | Ordinary | Teamup(1) | 协作会签,全员完成后发送 |
结束 | Ordinary | — | 归档 |
验证要点:
IsPass=-1(未到)与顺序激活;NodeSend_11();ReturnRole=ReturnPreviousNode,部门经理退回后员工修改再提交。操作 | 线形 | 同表单分合流 | 异表单分合流 | 父子流程 |
|---|---|---|---|---|
发送 | ✓ | ✓ | ✓ | ✓ |
退回 | ✓ | ✓ | ✓ | ✓ |
移交 | ✓ | ✓ | ✓ | ✓ |
撤销 | ✓ | ✓ | ✓ | ✓ |
催办 | ✓ | ✓ | ✓ | ✓ |
加签 | ✓ | ✓ | ✓ | — |
挂起 | ✓ | ✓ | ✓ | ✓ |
调整 | ✓ | ✓ | ✓ | — |
回滚 | ✓ | ✓ | ✓ | ✓ |
RunModel 枚举和 5×5 发送矩阵统一调度全部模式,使得发送、退回、撤销、轨迹、待办查询共用一套基础设施。
WorkID 标识实例,FID 承载分合流关系,PWorkID 承载父子流程关系,三者正交。
TodolistModel 与 ReturnRole,保证复杂场景下行为一致。
RunModel 形状模板实现设计态可视化,运行态由后端 PageName 动态路由。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。