流程定义设计:一份 LogicFlow JSON 全解

系列定位 :jeeflow 系列第 4 篇(第二季「核心设计」第 1 篇) 平台 :掘金(代码密度高,原理讲透) 素材版本 :引擎 v1.8.15,pro 站实时截图 前置阅读第 2 篇 · jeeflow:98KB 的工作流引擎长什么样


一、从一张流程图说起

打开 jeeflow-pro 集成演示站(jeeflow-pro.mldong.com),在「流程设计」里点开「报销申请」:

四个节点,一条直线:

scss 复制代码
开始 → 发起申请 → 部门经理审批 → 财务复核(会签) → 总经理审批 → 结束

其中「财务复核」节点右上角挂着一个蓝色标签------并行会签

这张图是给人看的。但引擎不吃图,引擎吃的是一份 JSON

本文要回答的核心问题:这份 JSON 长什么样?它怎么把"画图"变成"跑流程"?


二、JSON 结构解剖

jeeflow 的流程定义文件是一个标准 JSON,位于 jeeflow-java/jeeflow-core/src/test/resources/flows/ 目录,五语言 demo 启动时统一加载。

以最简单的 01-simple.json(简单审批流程)为例:

json 复制代码
{
  "name": "simple",
  "displayName": "简单审批流程",
  "type": "approval",
  "instanceUrl": "/form/apply",
  "nodes": [ ... ],
  "edges": [ ... ]
}

顶层只有 5 个字段

字段 含义 示例
name 唯一编码(程序用) "simple"
displayName 显示名称(人看的) "简单审批流程"
type 流程类型 "approval"
instanceUrl 发起页路由 "/form/apply"
nodes / edges 节点数组 + 边数组 见下文

2.1 节点(nodes)

每个节点是一个对象,核心字段:

json 复制代码
{
  "id": "task1",
  "type": "snaker:task",
  "x": 300,
  "y": 200,
  "properties": {
    "width": 100,
    "height": 50,
    "form": "leave-form",
    "assignee": "leader",
    "taskType": 0,
    "performType": 0
  },
  "text": {
    "value": "上级审批"
  }
}
字段 作用
id 节点唯一标识,边通过 sourceNodeId / targetNodeId 引用
type 节点类型:snaker:start / snaker:task / snaker:decision / snaker:end
x / y 画布坐标(设计器用,引擎不关心)
properties.assignee 审批人表达式 ------引擎通过 IUserProvider SPI 解析
properties.performType 执行方式------0=普通,1=并行会签,2=串行会签
properties.countersignType 会签类型 ------PARALLEL / SEQUENTIAL / RATIO
properties.form 关联表单路由
text.value 节点显示文本

2.2 边(edges)

边定义节点间的连接关系:

json 复制代码
{
  "id": "e3",
  "sourceNodeId": "decision1",
  "targetNodeId": "task2",
  "properties": {
    "expr": "amount > 1000"
  },
  "text": {
    "value": "金额>1000"
  }
}

普通边的 properties 为空对象 {}条件边 才带 expr 字段------这就是引擎路由决策的依据。

2.3 节点类型体系

jeeflow 只有 4 种节点类型,覆盖所有流程模式:

类型 标识 作用
开始节点 snaker:start 流程入口,自动生成
任务节点 snaker:task 人工审批 / 自动任务
决策节点 snaker:decision 条件分支网关
结束节点 snaker:end 流程出口

没有"子流程节点"、没有"事件节点"、没有"定时器节点"------jeeflow 的哲学是用少量原语组合出复杂行为,而不是堆砌节点类型。


三、10 个共享流程逐一拆解

jeeflow 的测试资源目录里有 10 个共享流程 JSON01-simple10-mixed-mode),五语言引擎共用同一份文件,启动时自动加载。

3.1 总览表

# 文件名 场景 核心模式 关键 JSON 特征
01 simple 简单审批 单任务 1 个 task,assignee=leader
02 multi-task 串行多节点 线性串联 多个 task 首尾相连
03 decision-expr 条件分支 表达式网关 edge 带 expr 条件
04 fork-join 并行分叉/汇聚 fork+join 分叉后多路径并行,join 汇聚
05 countersign-parallel 并行会签 多人同时审批 performType:1 + countersignType:PARALLEL
06 countersign-sequential 串行会签 逐个审批 performType:2 + countersignType:SEQUENTIAL
07 countersign-ratio 比例会签 通过比例阈值 countersignType:RATIO + 比例值
08 sequential-approve 逐一审批 特殊会签模式 逐个创建任务,逐个完成
09 with-reject 驳回流程 退回上游 edge 回指前序节点
10 mixed-mode 混合模式 组合以上所有 条件分支 + 会签 + 驳回

3.2 条件分支详解(03-decision-expr)

这是最常用也最容易被误解的模式。完整 JSON 结构:

json 复制代码
{
  "name": "decision-expr",
  "displayName": "决策表达式流程",
  "type": "approval",
  "nodes": [
    {
      "id": "start",
      "type": "snaker:start",
      "text": { "value": "开始" }
    },
    {
      "id": "apply",
      "type": "snaker:task",
      "properties": {
        "assignee": "applicant",
        "taskType": 0
      },
      "text": { "value": "发起申请" }
    },
    {
      "id": "decision1",
      "type": "snaker:decision",
      "properties": {
        "expr": "amount > 1000"
      },
      "text": { "value": "金额>1000?" }
    },
    {
      "id": "task2",
      "type": "snaker:task",
      "properties": { "assignee": "manager" },
      "text": { "value": "经理审批" }
    },
    {
      "id": "task3",
      "type": "snaker:task",
      "properties": { "assignee": "director" },
      "text": { "value": "总监审批" }
    },
    {
      "id": "end",
      "type": "snaker:end",
      "text": { "value": "结束" }
    }
  ],
  "edges": [
    { "sourceNodeId": "start", "targetNodeId": "apply" },
    { "sourceNodeId": "apply", "targetNodeId": "decision1" },
    {
      "sourceNodeId": "decision1",
      "targetNodeId": "task2",
      "properties": { "expr": "amount > 1000" },
      "text": { "value": "金额>1000" }
    },
    {
      "sourceNodeId": "decision1",
      "targetNodeId": "task3",
      "properties": { "expr": "amount <= 1000" },
      "text": { "value": "金额≤1000" }
    },
    { "sourceNodeId": "task2", "targetNodeId": "end" },
    { "sourceNodeId": "task3", "targetNodeId": "end" }
  ]
}

路由逻辑

bash 复制代码
decision1 节点评估 expr → 遍历所有出边 → 找到第一条 expr 为 true 的边 → 走那条路

关键点:

  • decision1 节点自身的 properties.expr默认表达式(兜底用)
  • 每条出边的 properties.expr分支条件(优先匹配)
  • 引擎通过 IExpressionEvaluator SPI 计算表达式,不绑定任何表达式引擎(SpEL / OGNL / 自研均可)

3.3 并行会签详解(05-countersign-parallel)

json 复制代码
{
  "id": "task1",
  "type": "snaker:task",
  "properties": {
    "assignee": "userA,userB,userC",
    "performType": "1",
    "countersignType": "PARALLEL",
    "field": {
      "candidateUsers": "userA,userB,userC"
    }
  },
  "text": { "value": "会签审批" }
}

三个关键字段决定会签行为:

字段 含义
performType "1" 会签模式(0=普通,1=并行,2=串行)
countersignType "PARALLEL" 并行会签(所有人同时收到任务)
assignee "userA,userB,userC" 会签人员列表

引擎行为

  1. 流程到达该节点时,一次性为所有会签人创建任务
  2. 每个任务独立审批,互不阻塞
  3. 所有人通过后,流程才流转到下一节点
  4. 任一节点驳回 → 整体会签驳回(一票否决,submitType=20

跨语言差异:Java 引擎一次建全部任务;Python/Node 引擎逐个创建。行为一致,实现策略不同------这是"契约对齐,实现自由"的典型体现。


四、从 JSON 到业务:pro 站的 9 个真实场景

共享 JSON 是"教科书",pro 站(jeeflow-pro.mldong.com)里跑的是"实战"。

4.1 流程定义列表

superAdmin 登录后进入「流程定义」,可以看到 28 条记录(9 个业务场景 × 多版本):

流程名称 唯一编码 流程类型 核心模式
请假申请 biz_leave 假勤管理 条件分支(天数网关)
采购申请 biz_purchase 业务管理 条件分支(金额网关)
报销申请 biz_reimburse 人事管理 串行 + 并行会签
用印申请 biz_seal 业务管理 简单审批
加班申请 biz_overtime 业务管理 条件分支(工时网关)
合同审批 biz_contract 业务管理 条件分支(合同金额)
出差申请 biz_business_trip 业务管理 条件分支(预算网关)
岗位异动申请 biz_transfer 人事管理 串行多节点
资产领用申请 biz_asset 业务管理 简单审批

4.2 条件分支实战:请假申请

请假申请的设计器视图:

条件分支逻辑:

  • ≤3 天 → 部门经理审批
  • >3 天 → 分管领导审批

用 admin 账号发起一笔 1 天事假,流程轨迹如下:

绿色高亮路径清晰显示:开始 → 申请人(蒙立东) → 条件分支(≤3天路径) → 部门经理审批(李娜) → 结束

对应的 JSON 核心结构

json 复制代码
{
  "nodes": [
    { "id": "apply", "type": "snaker:task",
      "properties": { "assignee": "applicant" },
      "text": { "value": "发起申请" } },
    { "id": "decision_days", "type": "snaker:decision",
      "properties": { "expr": "days > 3" } },
    { "id": "task_manager", "type": "snaker:task",
      "properties": { "assignee": "deptLeader" },
      "text": { "value": "部门经理审批" } },
    { "id": "task_director", "type": "snaker:task",
      "properties": { "assignee": "chargeLeader" },
      "text": { "value": "分管领导审批" } }
  ],
  "edges": [
    { "sourceNodeId": "decision_days", "targetNodeId": "task_manager",
      "properties": { "expr": "days <= 3" },
      "text": { "value": "≤3天" } },
    { "sourceNodeId": "decision_days", "targetNodeId": "task_director",
      "properties": { "expr": "days > 3" },
      "text": { "value": ">3天" } }
  ]
}

4.3 金额网关实战:采购申请

采购申请走的是同样的条件分支模式,只是判断条件从"天数"变成了"金额":

  • ≤2 万 → 部门经理审批
  • >2 万 → 总经理审批

admin 发起一笔 10000 元采购,流程轨迹:

路径高亮显示走了"≤2万"分支,部门经理李娜审批通过。

4.4 并行会签实战:报销申请

这是最复杂的场景。设计器视图:

审批链路:申请人 → 部门经理 → 财务复核(并行会签) → 总经理 → 结束

admin 发起一笔 5000 元差旅报销,完整审批轨迹:

四个节点全部通过:

  • 申请人 ✓ 蒙立东
  • 部门经理审批 ✓ 李娜
  • 并行会签 财务复核 ✓ 赵敏、黄红、蒙立东、吴昊、郑芳、李青(6 人全部通过)
  • 总经理审批 ✓ 王强

注意:实际会签组有 6 人,不是设计器上看到的"财务复核"两个字。会签人员由 assignee 字段 + IUserProvider SPI 动态解析,可以是固定列表、部门角色、甚至表达式计算结果。

4.5 映射关系总结

共享流程 pro 站业务场景 JSON 模式
03-decision-expr 请假申请(天数网关) 条件分支
03-decision-expr 采购申请(金额网关 ≤2万) 条件分支
03-decision-expr 合同审批(10万阈值) 条件分支
03-decision-expr 出差申请(5000预算) 条件分支
03-decision-expr 加班申请(4h工时) 条件分支
05-countersign-parallel 报销申请(财务会签 ALL) 并行会签
01-simple 用印申请(单审批) 简单审批
01-simple 资产领用(单审批) 简单审批
02-multi-task 岗位异动(多节点串行) 串行多节点

9 个业务场景 = 4 种 JSON 模式的排列组合。


五、版本管理与设计器

5.1 多版本共存

pro 站的流程定义列表显示 28 条记录------同一个流程(如"采购申请")有 v3、v4 多个版本共存。

版本管理的意义:

  • 新版本不影响在途实例:已发起的流程按发起时的版本跑完
  • 新发起的走最新版本:自动路由到最高版本号
  • 回滚能力:随时切回旧版本

5.2 设计器操作

流程设计器(基于 mldong-flow-designer-plus npm 包)提供以下操作:

功能 说明
缩小/放大 画布缩放
适应 自动缩放至画布大小
清空 清除画布
查看数据 查看/编辑底层 JSON
导入 从 JSON 文件导入流程定义
保存 保存当前设计到后端
全屏 全屏编辑模式

设计器是纯前端组件,不绑定任何后端------它产出 JSON,引擎消费 JSON,两者通过 JSON 契约解耦。


六、JSON 即协议

回到开篇的问题:这份 JSON 怎么把"画图"变成"跑流程"?

答案是三层解耦:

javascript 复制代码
┌─────────────┐     JSON      ┌──────────────┐     JSON      ┌──────────────┐
│  流程设计器  │ ───────────→ │  流程定义文件  │ ───────────→ │  工作流引擎   │
│ (vben5-wf)  │   产出 JSON   │  (.json)      │   解析 JSON   │  (jeeflow)   │
└─────────────┘               └──────────────┘               └──────────────┘
      前端                          文件                         后端
   不绑定引擎                    不绑定前端                   不绑定框架
  • 设计器不绑定引擎:flow-designer / vben5-wf 都能渲染同一份 JSON
  • JSON 不绑定前端:它是纯数据,任何系统都能生成
  • 引擎不绑定框架:Java/Go/Python/Node/PHP 五语言都能解析同一份 JSON

这就是 jeeflow "一套流程定义,四种语言实现" 的根基------不是口号,是一份 JSON 文件。


参考资料


下一篇预告 :[第 5 篇 · 用 DDD 设计工作流引擎:聚合根与充血模型](#第 5 篇 · 用 DDD 设计工作流引擎:聚合根与充血模型 "#") ------ ProcessInstance 为什么必须是聚合根?ProcessTask 作为子实体的职责边界在哪?为什么贫血模型扛不住工作流场景?

相关推荐
rannn_11110 小时前
【力扣hot100】链表专题|160、206、234、141、142
java·算法·leetcode·链表·面试·开发
AI产品测评官10 小时前
突破系统割裂困局:2026企业级AI招聘架构如何迈向“原生全流程”?
大数据·微服务·架构
寒草11 小时前
【寒草呈献】当巴菲特走进 AI 投研助手
人工智能·架构
IKUN家族11 小时前
Spring IoC&DI
java·spring·rpc
ltl11 小时前
推理服务化:Triton、Ray Serve、KServe 与 PD 分离
架构
@insist12311 小时前
系统集成项目管理工程师-安全架构与云原生架构
云原生·架构·软考·安全架构·系统集成项目管理工程师·软考中项·软件水平考试
Java成神之路-12 小时前
集群架构 vs 分布式微服务架构:核心区别通俗拆解
分布式·微服务·架构
山荷枝12 小时前
03-框架--Spring
java·后端·spring