Skip to content

流程框架封装 ​

概述 ​

BPM 工作流采用一套自研的轻量流程引擎,位于 backend/app/api/v1/module_bpm/framework。它不依赖 Activiti、Flowable 等外部引擎,而是围绕「钉钉飞书风格设计器导出的 simple_model JSON 模型」实现令牌(Token)式的顺序推进。

核心设计要点:

  • 模型驱动:流程以一棵 SimpleFlowNode 节点树描述,节点的 type 决定推进行为。
  • 状态自管理:所有流程状态都落在本项目自建表中(bpm_process_instance / bpm_process_node / bpm_task),不依赖外部流程引擎的运行时表。
  • 事务一致:所有推进操作都在调用方的数据库事务内完成,节点记录与任务生成要么全部成功、要么整体回滚。
  • 阻塞即挂起:审批节点会阻塞流程,直到人工或自动动作完成;延迟器、HTTP 回调等节点通过「节点记 WAIT 挂起 + 定时/外部恢复」继续推进。

引擎核心模块 ​

引擎由一组职责单一的模块协作组成:

模块职责
engine.py引擎核心:实例创建、节点推进、审批动作、加签/转办/退回等
condition.py条件求值:条件规则组与表达式两种配置方式
candidate.py候选人计算:按候选策略解析审批人/抄送人列表
trigger.py触发器节点运行时:HTTP 请求、HTTP 回调、表单变量更新/删除
event.py轻量异步事件总线:发布/订阅
notifier.py站内信通知:待办提醒、流程结果通知
process_listener.py流程级监听器:执行配置在管理页的监听逻辑
http_listener.py节点级 HTTP 监听器:节点任务创建/完成时回调外部系统
timeout.py定时扫描:审批超时处理、延迟器唤醒
serializer.py响应组装:将引擎数据转换为前端期望的驼峰结构

流程模型 ​

simple_model 是一棵以 childNode 串联的单链,分支节点通过 conditionNodes 数组挂载多条分支。两种结构描述如下。

线性节点链(审批流主干)

分支容器(网关)

条件分支、并行分支、包容分支都是「分支容器」节点,容器内通过 conditionNodes 挂多个分支,每个分支再以 childNode 串联自己的节点体:

json
{
  "type": 51,
  "conditionNodes": [
    { "type": 50, "conditionSetting": { "defaultFlow": true }, "childNode": { "...": "分支体..." } },
    { "type": 50, "conditionSetting": { "conditionType": 2, "conditionGroups": "..." }, "childNode": { "...": "分支体..." } }
  ],
  "childNode": { "...": "汇合后的后续节点" }
}

引擎在部署(validate_simple_model)时会递归校验节点链,例如分支容器至少需要两个分支、审批节点必须配置候选策略、延迟器与时间相关节点必须有合法配置,校验不通过则拒绝部署。

节点类型 ​

引擎支持以下节点类型(对应 BpmNodeTypeEnum):

类型说明推进行为
发起人节点 10流程发起直接记录通过,子流程可配置跳过
审批人节点 11人工/自动审批阻塞等待,生成审批任务
办理人节点 13办理(无拒绝语义)同审批人节点推进
抄送节点 12知会他人记录抄送人后直接通过
延迟器节点 14等待一段时间记 WAIT 挂起,定时唤醒
触发器节点 15自动动作HTTP 请求/表单更新,或 HTTP 回调挂起
子流程节点 20调用另一流程同步屏障,等待子流程终态
条件分支 51排他网关求值选择一条分支
并行分支 52并行网关全部分支同时激活
包容分支 53包容网关命中条件的分支激活
结束节点 1流程终点实例置为通过

注:条件节点 50(分支体内的具体分支)、路由分支 54 当前版本暂未启用,分支结构由容器节点(51/52/53)承载。

节点推进机制 ​

引擎的核心方法是 _run_chain:从某个节点开始,沿 childNode 顺序推进,直到遇到「需要阻塞的节点」或流程进入终态时停止,返回链是否自然走完。

推进规则概览:

  • 发起人节点、抄送节点、结束节点、AUTO_APPROVE 自动通过节点 —— 立即落库并继续。
  • 审批/办理节点 —— 计算候选人,生成任务并阻塞(返回 False)。
  • 延迟器节点 —— 记 WAIT 挂起,返回 False 等待定时唤醒。
  • 触发器 HTTP_CALLBACK 类型 —— 记 WAIT 挂起,返回 False 等待业务方回调。
  • 子流程节点 —— 发起子流程并记 RUNNING,返回 False 等待子流程终态事件。
  • 条件/并行/包容分支 —— 处理后进入分支体,分支体阻塞则整体阻塞;分支体自然走完后由 _continue_after_branch 检查汇合条件并推进后续。

任务被审批后,引擎在 _after_task_approved 中检查多人审批是否满足条件(会签比例、或签、依次审批),满足则闭合当前节点,并从 childNode 继续推进;若链尾落在分支体内,则逐层向外做分支汇合检查。

条件与分支 ​

分支选择依赖条件求值模块 condition.py,支持两种配置方式(对应 BpmConditionTypeEnum):

  • 条件规则 RULE:由「规则组」组成。单个规则是 字段 + 运算符 + 值;组内规则按「且/或」组合,组间也按「且/或」组合。运算符涵盖 ==、!=、>、>=、<、<= 以及 包含/不包含,并支持字符串与数字的自动类型协调。
  • 条件表达式 EXPRESSION:基于 Python ast 的白名单表达式求值,仅允许变量引用、比较与逻辑运算(&&/|| 会适配为 and/or),禁止属性访问、函数调用等危险语法。表达式上下文注入了流程变量与内置变量(如发起人编号)。

分支容器语义:

  • 条件分支(排他网关):按顺序选择第一个命中的分支,未命中分支记 SKIP;可配置 defaultFlow 默认分支兜底。
  • 并行分支(并行网关):容器下所有分支同时激活并各自推进,全部已激活分支都达终态后才放行汇合后续节点。
  • 包容分支(包容网关):按条件逐个求值,命中的分支才激活,未命中记 SKIP;激活分支全部终态后汇合。

未激活(SKIP)的分支不参与汇合等待,保证分支结构正确收敛。

候选人策略 ​

审批节点与抄送节点的执行人由 candidate.py 根据「候选策略 + 参数」计算,并对结果去重。当前支持的常用策略:

策略说明
指定用户 USER(30)直接指定用户编号
指定角色 ROLE(10)取拥有该角色的全部用户
指定岗位 POST(22)取拥有该岗位的用户
部门成员 DEPT_MEMBER(20)取部门下用户
部门负责人 DEPT_LEADER(21)取部门负责人
连续多级部门负责人 MULTI_LEVEL_DEPT_LEADER(23)向上逐级取负责人
指定用户组 USER_GROUP(40)取用户组成员
发起人自己 START_USER(36)流程发起人
发起人自选 START_USER_SELECT(35)发起时由发起人填写审批人
审批人自选 APPROVE_USER_SELECT(34)当前审批人指定下一节点审批人
发起人部门负责人 START_USER_DEPT_LEADER(37)取发起人所在部门负责人
流程表达式 EXPRESSION(60)表达式解析出的用户编号(支持 startUser、${变量}、字面量等)

当审批人与发起人相同时,可配置处理策略:由本人审批、自动跳过、或转交部门负责人。当计算出的审批人为空时,可配置:自动通过、自动拒绝、指定人员、或转交流程管理员。

审批任务与多人审批 ​

审批节点激活后会生成 bpm_task 任务,多人审批方式由 approveMethod 决定:

  • 随机一人:从候选人中取一人生成任务,通过即节点完成。
  • 或签 ANY_APPROVE:任意一人通过即完成。
  • 会签 APPROVE_BY_RATIO:按通过比例(如超过 50%)判定是否完成,未达比例继续等待。
  • 依次审批 SEQUENTIAL_APPROVE:按候选人顺序逐个生成任务,前一人通过后自动为下一人建任务。

任务完成时,引擎通过乐观更新(status == RUNNING 才生效)防并发重复处理,并据审批方式判定节点是否完成。

子流程 ​

子流程节点支持在一个流程中调用另一个流程定义(calledProcessDefinitionKey),引擎以同步屏障方式等待其结束:

  • 子流程结束时通过实例终态事件回调主流程;子流程通过则主流程继续,被拒绝/取消则主流程联动终止(状态传播)。
  • 通过 inVariables 将主流程变量映射为子流程变量,通过 outVariables 在子流程结束后回传变量到主流程。
  • 子流程发起人可配置为「同主流程发起人」或「表单字段用户」,为空时按兜底策略(主流程/子流程管理员)处理。
  • 可配置是否跳过子流程的发起人节点;当前版本暂不支持异步子流程与多实例子流程。

事件总线与监听器 ​

引擎内置轻量异步事件总线(event.py),在关键节点发布事件,用于解耦通知与扩展逻辑。处理器异常不会中断主流程,仅记录日志。

事件类型:

  • bpm.process_instance.create —— 实例创建
  • bpm.process_instance.finish —— 实例终态(通过/拒绝/取消)
  • bpm.task.create —— 任务创建(待办生成)
  • bpm.task.finish —— 任务完成(仅通过时触发)

基于事件总线的两类监听器:

  • 流程级监听器 process_listener:在管理页配置的全局监听器,绑定到上述事件。支持两种值类型:
    • class:按 module.path:attr 动态导入并调用处理函数;
    • expression:基于 ast 白名单的表达式求值(结果仅记录日志)。
    • 任务完成监听仅在任务通过时触发。
  • 节点级 HTTP 监听器 http_listener:配置在审批/办理节点上的 taskCreateListener / taskCompleteListener,在任务创建或(通过时)完成时向外部系统发起 HTTP 回调,参数与请求头支持固定值或流程变量取值。

两类监听器均在引擎模块加载时通过 register() 幂等订阅(见 engine.py 末尾导入处)。

触发器节点 ​

触发器节点(TRIGGER_NODE)用于在工作流中自动执行动作,由 trigger.py 按类型分发:

  • HTTP 请求 HTTP_REQUEST:同步发起 HTTP 请求,可按响应映射把返回字段写回流程变量,流程直接继续。
  • HTTP 回调 HTTP_CALLBACK:发起请求(请求体携带 taskDefineKey)后节点挂起,等待业务方调用回调恢复接口 engine.trigger_callback 推进流程(幂等设计)。
  • 表单更新 FORM_UPDATE / 表单删除 FORM_DELETE:按条件配置批量更新或删除流程变量。

HTTP 请求支持失败回滚(停留在触发器前可重试),响应回写兼容统一响应结构 { code, data }。

超时与延迟扫描 ​

审批任务的超时处理与延迟器节点唤醒由基础设施的定时任务(module_infra 的 job)周期调用,对应两个处理器:

  • module_bpm.framework.timeout:scan_timeout_tasks:扫描运行中的审批任务,按节点 timeoutHandler 配置执行——自动提醒(按 maxRemindCount 轮次、基于 Redis 去重)、自动同意、或自动拒绝。
  • module_bpm.framework.timeout:scan_delay_nodes:扫描 WAIT 状态的延迟器节点,到点(固定时长自激活起算 / 固定日期为绝对时间)后将其置为通过并从 childNode 继续推进。

超时时长使用 ISO 8601 时长格式(如 PT1H30M)。两个扫描均通过引擎侧的任务/节点状态校验与乐观更新做重复执行防护。

运行时任务操作 ​

引擎在审批任务上提供丰富的运行时操作(见 engine.py):

  • 委派 delegate_task:交由他人审批,对方完成后回到原审批人。
  • 转办 transfer_task:直接更换审批人。
  • 加签 create_sign_task / 减签 delete_sign_task:向前加签(父任务挂起,前置审批人先审)或向后加签(父任务进入审批通过中,后置审批人后审)。
  • 退回 return_task:驳回到指定审批人节点重新审批,同时收尾兄弟分支与子流程。
  • 撤回 withdraw_task:审批人撤回自己的已通过任务,若流程已推进到下游则取消下游运行中的任务与子流程后恢复节点。
  • 人工抄送 copy_task:为任务补充抄送人。

前端设计器与查看器 ​

前端位于 frontend/apps/web-ele/src/views/bpm,配合 packages/constants/src/biz-bpm-enum.ts 中的枚举与 frontend/apps/web-ele/src/api/bpm 的接口封装。

  • 设计器:流程模型支持 SIMPLE(20) 钉钉飞书风格设计器(bpm-model-editor.vue)。钉钉飞书风格设计器产出引擎直接消费的 simple_model。
  • 流程查看器:流程详情页提供 simple-bpm-viewer.vue(钉钉飞书风格模型视图)流程图渲染,展示实例流转轨迹与节点状态。
  • 隐藏路由:发起流程、流程详情、模型创建/修改、流程定义、数据报表等均以 hideInMenu 隐藏路由存在,由列表页跳转进入(见 router/routes/modules/bpm.ts)。
  • 任务操作按钮:前端按钮与 BpmTaskOperationButtonTypeEnum(通过/拒绝/转办/委派/加签/退回/抄送)一一对应,结合后端返回的可执行动作动态展示。

扩展点小结 ​

  • 事件订阅:通过 event.subscribe 注册自定义处理器,监听实例与任务生命周期。
  • 流程级监听器:用 class 或 expression 类型在管理页配置,无需改代码即可接业务逻辑。
  • 节点级 HTTP 监听器:在节点上配置外部回调地址,实现审批节点与第三方系统的联动。
  • 定时任务:通过基础设施 job 的 handlerName 挂载超时与延迟扫描,可按需调整扫描策略。