蓝凌EKP18产品:核心执行流程

流程的核心就是审批,审批有通过,驳回,加签,补签,我们这节就主要讲解如何追踪一笔审批操作从调用到完成的完整路径。

一、执行流程全景图

先放一张"路线图",然后我们逐段拆解:

复制代码
用户点击"审批通过"
        │
  ┌─────▼──────────────────────────────────────────┐
  │ 阶段一:操作调度 (Operation Dispatching)         │
  │ 入口 → 权限校验 → 参数校验 → 找到匹配的操作行为   │
  │ 耗时占比: ~5%  所在层: 引擎调度层                │
  └─────┬──────────────────────────────────────────┘
        │
  ┌─────▼──────────────────────────────────────────┐
  │ 阶段二:预处理 (Prepare)                        │
  │ 保存审批意见 → 验证业务规则 → 返回目标状态       │
  │ 耗时占比: ~15%  所在层: 引擎调度层 + 业务服务层   │
  └─────┬──────────────────────────────────────────┘
        │
  ┌─────▼──────────────────────────────────────────┐
  │ 阶段三:业务执行 (Execute)                      │
  │ 修改任务状态 → 设置临时参数 → 发布操作事件       │
  │ 耗时占比: ~20%  所在层: 引擎调度层 + 业务服务层   │
  └─────┬──────────────────────────────────────────┘
        │
  ┌─────▼──────────────────────────────────────────┐
  │ 阶段四:PVM 流转 (PVM Transition)               │
  │ 接收信号 → 激活执行路径 → 结束任务 → 找下一节点  │
  │ → 启动新任务 → 执行节点行为                      │
  │ 耗时占比: ~50%  所在层: PVM 虚拟机层             │
  └─────┬──────────────────────────────────────────┘
        │
  ┌─────▼──────────────────────────────────────────┐
  │ 阶段五:持久化与通知 (Persistence & Notify)      │
  │ 更新工作项状态 → 记录流转日志 → 发送消息通知     │
  │ 耗时占比: ~10%  所在层: 持久层 + 业务服务层      │
  └─────┬──────────────────────────────────────────┘
        │
     返回成功

:耗时占比是逻辑复杂度占比,不是实际 CPU 时间。PVM 流转虽然代码执行很快,但涉及的逻辑分支最多,所以占据了最多的"理解成本"。

二、阶段一:操作调度 ------ 找到"谁来处理"

2.1 入口在哪里?

一次审批操作,无论从浏览器的 REST API 进来,还是从第三方系统的远程调用进来,最终都会汇聚到引擎层的操作调度器

操作调度器的核心是一个类型注册表 ------系统启动时,所有的操作行为(通过、驳回、转办、加签、废弃...)都会注册到一个全局的 OperationTypeManager 中。

2.2 操作类型的三要素

每一种操作都由三个要素定义:

要素 说明 示例
操作类型(OperationType) 操作的唯一标识 processor.pass(审批通过)
处理人类型(HandlerType) 谁能执行这个操作 handler(处理人)、drafter(起草人)、admin(特权人)
模型名称(ModelName) 操作适用的业务域 某个具体流程模板,或 *(全局)

这三要素组合起来,确保了不同角色、不同场景下,同一个"通过"按钮背后的逻辑可以截然不同。

2.3 操作与节点的绑定

操作不是凭空出现的------什么节点上显示什么按钮 ,是由 IOperationBinder 决定的。

以"审批通过"操作为例,它的绑定逻辑是:

如果当前节点是人工审批节点(isHandler = true),且不是自动运行(!isAutomaticRun),且不是分支节点、并发节点、子流程节点,则绑定"审批通过"操作。同时,当前工作项的类型必须是"待审"或"共享审批"。

这意味着:

  • 在起草节点上,你看不到"审批通过"按钮(因为草稿节点不是 handler 类型)

  • 在自动节点上,你也看不到(因为自动节点不产生待办)

  • 在人工分支决策节点上,按钮又不同(它需要你选择走哪个分支)

2.4 调度的三步校验

在真正执行操作之前,引擎会做三层校验:

第一层:权限校验。 是处理人还是特权人?当前用户有没有资格执行这个操作?对应 checkUser() 方法。

第二层:状态校验。 当前工作项是否处于可操作状态?已完成的、已废弃的都不能再操作。对应 getOperationInfo() 方法。

第三层:参数校验。 操作参数是否合法?比如手动选择的下一节点 ID 是否存在。对应 checkData() 方法。

三层校验全部通过后,操作才进入真正的执行阶段。

三、阶段二:预处理 ------ 保存痕迹,准备变天

3.1 为什么要分"预处理"和"执行"两步?

这是一个经典的两阶段设计:

预处理(prepareExecute) :只读为主,保存操作日志、验证业务规则、计算目标状态。这一步即使出错,也不应该对流程状态产生任何影响。
执行(execute) :写入为主,修改工作项状态、触发 PVM 流转。这一步是不可逆的。

将两者分离,可以在正式动手之前做足检查,避免"改了一半发现不行"的情况。

3.2 预处理做了什么?

以"审批通过"为例(HandlerPassOperationBehaviour),预处理的核心动作是:

  1. 保存审批意见 :调用 saveAuditNote(),记录谁在什么时间、说了什么话

  2. 计算目标状态 :审批通过 → 工作项目标状态 = COMPLETED(完成)

预处理的结果封装在 ExecuteResult 对象中------它只包含一个简单的状态值,告诉下一阶段"要把工作项改成什么状态"。


四、阶段三:业务执行 ------ 修改状态,发布事件

4.1 从预处理结果到实际动作

拿到 ExecuteResult 后,执行阶段检查它返回的状态。如果是 COMPLETED,说明这个操作需要完成当前工作项并推动流程向前走。

4.2 发布操作执行事件

业务执行的关键一步是发布操作事件 。审批通过会发布 HandlerPassOperationEvent,这个事件继承自 EngineEvent------它是 PVM 事件系统的一部分。

复制代码
HandlerPassOperationEvent (业务事件)
  └── extends AbstractManualOperationEvent (带操作参数的事件)
        └── extends EngineEvent (PVM 事件基类)

事件的发布是通过 TaskExecutionContext.fire(event) 调用的。这个 fire 方法最终会调用到 PVM 层的 EventExecutionWraper.fire()------也就是我们在第5课详细分析过的事件系统。

4.3 事件发布之后发生了什么?

事件被 PVM 捕获后,进入事件监听器链。对于审批通过事件,最关键的监听器是操作完成后的流转监听器------它收到了"任务已完成"的信号,然后调用了 PVM 的核心方法:

复制代码
execution.signal()

从这一刻起,流程的控制权从"业务操作层"移交到了"PVM 虚拟机层"。


五、阶段四:PVM 流转 ------ 流程引擎的心脏起搏

这是整个执行流程中最核心、也是最复杂的部分。我们在第5课和第6课已经详细分析过 PVM 的内部机制,这里从流程追踪的角度再梳理一遍。

5.1 Signal:给执行路径"发信号"

执行路径在人工节点处处于 WAITING(等待)状态。当用户完成审批操作后,signal() 方法被调用,相当于对执行路径说:"喂,醒醒,继续走。"

复制代码
执行路径状态: WAITING ──signal()──→ ACTIVE

5.2 节点收到信号后的行为

Signal 原子操作被执行,它调用当前节点的 ActivityBehaviour.signal()。对于人工审批节点,收到信号后的逻辑是:

  1. 读取操作参数(审批意见、选择的下一节点等)

  2. 验证当前节点的出口路由是否合法

  3. 调用 execution.takeDefaultTransition() 指定出口路由

  4. 执行路径状态从 ACTIVE 变为 ENDING(准备结束)

5.3 TransitionEndTask:找出下一步往哪走

执行路径进入 ENDING 状态后,自动触发 TransitionEndTask 原子操作。这个操作的灵魂是路由决策

复制代码
getNextRoute(execution)
    │
    ├── destination == null && parent == null  →  Route.End    (流程结束)
    ├── destination == null && parent != null  →  Route.Parent (回父路径)
    ├── destination 在范围内                      →  Route.Next   (下一节点)
    ├── destination 不在范围内                    →  Route.Parent (先回父路径)
    └── 特权跳转标记                              →  Route.Jump   (特权跳转)

对于正常的顺序流转,"审批通过"后 destination 是流程定义中预设的下一节点,所以路由结果是 Route.Next

5.4 TransitionStartTask:启动下一个节点

确定了"下一站"之后,系统调用 propagateToNext(),它做了两件事:

  1. 把当前执行路径"搬到"下一个节点上setActivity(destination),相当于把程序计数器指向下一条指令

  2. 触发启动新任务的原子操作链TransitionStartTask → EnterTaskEvent → ActivateTask → ExecuteTask

5.5 ExecuteTask:执行节点行为

ExecuteTask 是原子操作链的终点。它调用当前节点的 ActivityBehaviour.execute()

这里出现关键分叉:

节点类型 行为 执行路径状态
自动节点 执行业务逻辑,自动完成 不等待,直接 TRANSITION_END_TASK,继续下一节点
人工节点 创建待办工作项,分配处理人 设为 WAITING停在这里等人操作

如果下一节点是人工节点,整个流程就会再次暂停 ,执行路径回到 WAITING 状态,等待下一个人点击"审批通过"------然后从阶段一开始新一轮循环。


六、阶段五:持久化与通知

6.1 状态落库

在整个执行过程中,每一步状态变更都会通过 EngineWire 接口持久化到数据库。但值得注意的是,这些持久化操作不是"一步一写",而是由事务边界统一控制的。

关键的表更新包括:

  • 工作项表:状态从"已激活"变为"已完成"

  • 执行路径表 :状态从 WAITING 变为 ACTIVE 变为 ENDING 变为(新的 WAITINGENDED

  • 流程实例表:更新当前节点位置、最后操作时间

  • 审批意见表:插入新的审批记录

6.2 消息通知

持久化完成后,事件监听器链的后续环节开始运作:

  • 待办通知监听器:给下一节点的处理人发送待办提醒(站内信/邮件/企业微信/钉钉)

  • 关注通知监听器:给关注了此流程的人推送状态变更

  • 流程日志监听器:记录流转轨迹,用于后续审计和追溯


七、执行流程中的关键转折点

回顾整个流程,有几个重要节点值得单独拿出来讲:

7.1 控制权交接点 ------ signal()

这是引擎调度层和 PVM 虚拟机层的分界线。

signal() 之前,是"业务操作的世界"------保存意见、校验参数、修改状态,都是以业务语义为导向的。

signal() 之后,是"流程虚拟机的世界"------原子操作、执行路径、状态机,都是以流转逻辑为导向的。

这个边界清晰的设计,使得两套逻辑可以独立演化。

7.2 自动节点 vs 人工节点的分叉

ExecuteTask 是自动节点和人工节点的分水岭:

  • 自动节点:引擎自己跑到底,可能一口气穿过多个自动节点直到遇到人工节点

  • 人工节点:引擎停下来,"等"外部信号

这解释了为什么流程定义中经常有"连续多个自动节点"------它们在一次 signal() 调用中全部走完,对外部来说是"一步到位"的。

7.3 并行分支的特殊路径

如果"审批通过"后进入了并行网关(SplitNode),执行路径会分裂成多棵子树:

复制代码
原路径 (父) → STATE_CONCURRENT
    ├── 子路径-A → STATE_ACTIVE → ... → STATE_ENDED
    ├── 子路径-B → STATE_ACTIVE → ... → STATE_ENDED
    └── 子路径-C → STATE_ACTIVE → ... → STATE_WAITING (暂停等人)

聚合时(JoinNode),根据策略(Anyone/And)决定是否所有子路径完成才通过。


八、执行时序图(简化版)

用一张时序图来串起整个调用链:

复制代码
用户          REST API      操作调度器      操作行为      PVM虚拟机      数据库
 │               │              │              │             │            │
 │──点击通过────→│              │              │             │            │
 │               │──调用操作───→│              │             │            │
 │               │              │──权限校验    │             │            │
 │               │              │──找到Behaviour│            │            │
 │               │              │──prepare───→│             │            │
 │               │              │              │──保存审批意见─────────→│
 │               │              │              │←──返回COMPLETED        │
 │               │              │──execute────→│             │            │
 │               │              │              │──修改工作项状态────────→│
 │               │              │              │──fire(event)│            │
 │               │              │              │             │            │
 │               │              │              │  ═══ 控制权交接 ═══     │
 │               │              │              │             │            │
 │               │              │              │             │──signal()  │
 │               │              │              │             │──找路由    │
 │               │              │              │             │──结束任务  │
 │               │              │              │             │──启动下一  │
 │               │              │              │             │──执行节点  │
 │               │              │              │             │            │
 │               │              │              │             │──[人工节点]│
 │               │              │              │             │  创建待办──→│
 │               │              │              │             │  设WAITING  │
 │               │              │              │             │            │
 │               │              │              │             │──发送通知  │
 │               │←─────返回成功────────────────────────────│            │
 │←──显示成功────│              │              │             │            │

九、六大关键接口在这场"接力赛"中的角色

我们在第7课讲过的六大抽象,在这一课的流程追踪中各自扮演了具体角色:

抽象接口 在本流程中的角色
EngineWire 创建下一节点的执行路径、保存执行路径状态到数据库
ActivityBehaviour ExecuteTask 调用,决定"这个节点是自动跑还是等人"
AtomicOperation TransitionStartTask → ExecuteTask → TransitionEndTask 链式调度
JoinStrategy 如果下一节点是聚合网关,判断"所有分支都到了吗?"
ProcessServiceManager 提供审批意见保存、组织架构解析、消息通知等周边服务
ExecutionContext 贯穿始终,承载执行路径、流程定义、参数、服务容器

流程引擎的核心就是审批,审批就是用户提交,提交的过程就是检查流程操作参数是否正确,然后对流程进行预处理,比如添加审批意见,把待办置为已办,然后通知流程引擎PVM进行处理,PVM对流程当前任务进行激活,结束当前任务,查找路由,继续启动新任务,继续等待任务被执行。理解了这个过程,这样才能真正的理解我们的流程引擎。

相关推荐
VortMall1 小时前
全维度打磨细节体验,赋能商城稳定有序运营|VortMall 微服务商城 v1.3.11 版本发布
java·微服务·云原生·架构·商城系统·开源商城·vortmall
Tirzano1 小时前
java 精简使用ffmpeg
java·开发语言·ffmpeg
唐青枫1 小时前
Java Jersey 实战指南:用 JAX-RS 注解写清晰的 REST API
java
一只小小Java2 小时前
Naocs本地部署&安装3.2.3+Spring boot 3.2.0
java·spring boot·后端·nacos
JL152 小时前
Spring Boot vs Gin:Java 人搭第一个 Go REST API
java·spring boot·gin
神明不懂浪漫3 小时前
【第七章】Java中的常用类
java·开发语言·前端·经验分享·笔记
Jelena1577958579212 小时前
电商运营分析数据比价接口实战:多平台价格监控与智能决策系统
java·大数据·数据库
神明不懂浪漫13 小时前
【第五章】Java中的继承与多态
java·开发语言
AI多Agent协作实战派15 小时前
AI多Agent协作系统实战(十七):凌晨4点,我的AI系统在“假装工作“——3个bug同时爆炸的5小时
java·前端·bug