工作流引擎的灵魂:状态机与 submitType

jeeflow 系列 · 第 3 篇(认识季)


画流程图的时候,一切都很美好:发起 → 部门审批 → 财务审批 → 结束,几条箭头,干净利落。

但引擎真正要回答的是另一组问题:流程发起后,现在走到哪了?审批中被人退回一步,这算什么状态?发起人撤回了,其他人在等什么?管理员要强行终止,任务们怎么办?

这些问题,流程图答不了------状态机才答得了。这是工作流引擎的"灵魂"。这篇把 jeeflow 的双层状态机和 8 种提交类型一次讲透。

本文内容对齐 jeeflow v1.8.11。本系列由 mldong 快速开发框架生态延续而来,建议按顺序阅读:第 2 篇讲了 jeeflow 的整体设计(98KB / SPI 体系 / DDD 聚合根),本篇深入状态层。


一、为什么是"双层"状态机?

一个流程实例,可能同时存在多个任务:并行分支里,task2 和 task3 同时在跑。所以状态必须分两层:

  • 实例状态:整个流程走到哪了
  • 任务状态:某一个具体审批项走到哪了

jeeflow 的状态机是跨语言契约(四语言枚举值完全一致):

状态码 含义
实例 10 进行中
实例 20 已完成(正常走到 end)
实例 30 已撤回(发起人撤回)
实例 40 强行终止(管理员终止)
实例 45 已拒绝(驳回/拒绝)
实例 50 挂起(暂停)
实例 99 已废弃
任务 10 待办
任务 20 已完成
任务 30 / 40 / 50 撤回 / 终止 / 挂起(随实例级联)
任务 99 已废弃(驳回/跳转时清理其他进行中任务)

三个容易忽略的设计点:

  1. 实例只有"完成"没有"同意"------实例 20 是终态,由最后一个任务完成后触发;
  2. 任务有"废弃"(99)------退回、跳转时,被跳过的待办任务要作废,不然"待办列表"会残留幽灵任务。这个 99 态是很多自研引擎容易漏掉的;
  3. 支线状态靠"级联" ------撤回/终止/挂起是实例级命令,任务状态随实例级联落库updateInstance 同连接持久化,v1.0.1 契约),引擎不会留下"实例停了任务还在跑"的半吊子状态。

二、submitType:业务动作怎么驱动状态

状态机是"静态地图",submitType(提交类型)就是"驾驶指令" 。这是 mldong 框架的 ProcessSubmitTypeEnum 与 jeeflow 完全对齐的枚举,8 个取值:

code 枚举 调用方行为 效果
0 APPLY executeProcessTask 发起 / 重新提交
1 AGREE executeProcessTask 同意
2 REJECT executeAndJumpToEnd 实例 → 45 已拒绝,无新待办
3 ROLLBACK 沿边回溯上一任务节点 → executeAndJumpTask 退回上一步审批人,实例保持 10
4 JUMP executeAndJumpTask(..., taskName) 跳到指定已办节点(taskName 取自可跳转列表)
5 RE_APPLY executeProcessTask 重新提交
6 ROLLBACK_TO_OPERATOR executeAndJumpToFirstTaskNode 第一个任务节点重执行、参与者强制为发起人 → 发起人收新待办,实例保持 10
20 COUNTERSIGN_DISAGREE executeProcessTask + countersignDisagreeFlag=1 会签一票否决

行为细节以 jeeflow-doc 规范(SPEC)为准;各枚举的引擎方法四语言一一对应(Java / Go / Python / Node 同签名)。

几个容易搞混的点,展开说:

拒绝(2)≠ 退回(3)。 拒绝是终止:executeAndJumpToEnd,实例直接变 45,不会再产生任何待办 。退回是回溯:沿入边向上找到最近的任务节点(跳过 decision/fork/join),重新生成任务给上一步审批人,实例保持进行中。一个判死刑,一个打回重审------语义完全不同。

退回发起人(6)是个闭环。 它依赖一个约定:每个流程的第一个任务节点必须是"发起申请"节点(assignee 为 applicant,解析为流程发起人)。退回发起人 = 让第一个任务节点重新执行、参与者强制改为发起人。发起人收到新待办后,改单重新提交(5 RE_APPLY),流程继续。没有这个约定,退回发起人就无从谈起------这也是 jeeflow 所有共享流程都遵守"第一节点是申请节点"的原因。

会签否决(20)是"一票否决"。 并行/串行会签中,任一参与者传 20 + countersignDisagreeFlag=1,整个会签节点按否决处理,其他还在等的任务随之废弃。

三、引擎之上的分发:门面 execute

在 jeeflow 的统一门面 JeeflowFacade.flow(action, map) 里,processTask/execute 按 submitType 分发到对应引擎方法(对齐 boot3 的 ProcessTaskController.execute):

submitType 分发目标
0 APPLY / 1 AGREE / 20 会签否决 executeProcessTask
2 REJECT executeAndJumpToEnd
3 ROLLBACK executeAndJumpTask(target=null)(沿边回溯)
4 JUMP executeAndJumpTask(target=taskName)
5 重新提交 业务方语义(发起人重新发起,门面透传)
6 退回发起人 executeAndJumpToFirstTaskNode

四、设计洞察:为什么"提交类型"暴露给调用方?

有朋友问过:提交类型不是引擎内部的事吗?为什么要把 submitType 作为接口的一等公民?

因为业务方才是流程动作的发起者 :页面上"同意/驳回/退回/跳转"按钮,对应到接口就是 submitType=1/2/3/4。mldong 生态的前端(vben5 + 流程设计器)按这套枚举渲染按钮和提交请求,引擎、接口、前端三方共用同一份语义------这就是"契约对齐"的威力:换引擎(比如 jeeflow 的四个语言实现)不换契约,前端零改动。

所以你在 jeeflow 的四个 demo 里会看到完全一致的接口行为:code=0 成功、msg 字段、submitType 全枚举、highLight/approvalRecord 独立端点------不是巧合,是契约。

结语

流程图是皮,状态机是骨,submitType 是筋。理解了"双层状态 + 8 种提交类型",你就理解了工作流引擎最核心的执行语义------后面讲流程定义、会签、退回机制、持久化演进时,都是在这张骨架上盖房子。

下一篇预告:《流程定义设计:一份 LogicFlow JSON 全解》------节点、边、表达式怎么描述一个流程?10 个共享流程 JSON 逐个拆解。

想亲手点点?四语言演示站已上线,在线体验:jeeflow-demo.mldong.com(Java :8080 / Go :8081 / Python :8100 / Node :8082 + 统一前端),发起一个流程、同意、退回、撤回,状态机怎么转一目了然。

相关链接

相关推荐
LucianaiB4 小时前
毕业老学长给我留的最后一句话是:“AI 写的,别挂我名。” 我直接 神(Seed Evolving) 来!助我!
后端
IvanCodes4 小时前
RAG 实战教程(一):RAG 工作原理与完整流程——分片、索引、召回、重排和生成
人工智能·后端·agent
CodeSheep5 小时前
稚晖君公司人事大变动,来了!
前端·后端·程序员
小满zs5 小时前
Go语言第八章(函数)
后端·go
stark张宇7 小时前
实战Go高级特性:Context超时控制、defer资源回收与Channel通信的关键避坑点
后端·go
凤山老林8 小时前
Spring Boot @Async 线上实战:从默认配置到生产级线程池治理
java·spring boot·后端
IT_陈寒9 小时前
Vue的响应式让我原地破防,原来问题出在这
前端·人工智能·后端
思考着亮9 小时前
1.RabbitMQ基本使用
后端
XuCoder9 小时前
面试官:Redis 单线程为什么还这么快?这道题答错的人真不少
后端
子兮曰9 小时前
Bun vs Node.js 深度对决:跑分快 4 倍,真实业务只剩 3%,2026 年到底该怎么选?
前端·后端·typescript