2、核心模型:流程定义模型、节点模型、连线模型、表单模型

好,咱们今天聊聊工作流引擎的四大核心模型。说实话,这四块东西搞明白了,整个引擎的骨架你就拿捏住了。

我当年第一次设计工作流引擎时,就犯了个错——把模型揉得太紧,结果后面扩展起来,那叫一个痛苦。后来我悟了:模型设计,本质上是在做职责分离

2.1 流程定义模型:整个流程的蓝图

流程定义模型,说白了就是一张“流程图”在计算机里的映射。它描述的是:这个流程有哪些步骤?谁来做?怎么流转?

我个人习惯把流程定义模型拆成三部分:

  • 元信息:流程ID、名称、版本号、创建时间、状态(草稿/发布/停用)
  • 结构信息:包含哪些节点、哪些连线、起始节点和结束节点是谁
  • 配置信息:超时策略、通知规则、驳回规则等

嗯,这里要注意版本号。我在项目中遇到过,流程定义改了,但旧流程实例还在跑。怎么办?版本控制。每次修改都升版本,新实例用新版本,旧实例继续跑旧版本。这叫“灰度升级”。

核心要点:流程定义模型是静态的,它只描述“应该怎么做”,不关心“正在怎么做”。

一个典型的流程定义模型,用Java类表示大概长这样:

public class ProcessDefinition {
    private String id;              // 流程唯一标识
    private String name;            // 流程名称
    private int version;            // 版本号
    private String status;          // 状态:draft, published, disabled
    private List<NodeModel> nodes;  // 节点列表
    private List<LineModel> lines;  // 连线列表
    private String startNodeId;     // 起始节点ID
    private String endNodeId;       // 结束节点ID
    private Map<String, Object> config; // 扩展配置
}

2.2 节点模型:流程中的每一个步骤

节点模型,就是流程里的“一个步骤”。你想想看,一个审批流程里,有“发起申请”、“部门审批”、“总经理审批”、“结束”——每个都是节点。

节点类型我一般分这么几类:

  • 开始节点:流程的起点,只有一个
  • 结束节点:流程的终点,可以有一个或多个
  • 任务节点:需要人参与审批或处理
  • 网关节点:做分支判断,比如“金额大于5000走总经理审批,否则走部门审批”
  • 子流程节点:调用另一个流程定义

我曾经在做一个OA系统时,客户要求“会签”——就是多个人同时审批,所有人都通过才算过。这个功能就是在任务节点上加了个“多人审批策略”属性:是“全部通过”还是“一票否决”?

避坑指南:节点模型一定要预留扩展字段。我见过太多人把节点属性写死,后面加个“超时自动通过”功能,结果要改数据库表结构,那叫一个折腾。

节点模型的核心字段:

public class NodeModel {
    private String id;              // 节点ID
    private String name;            // 节点名称
    private String type;            // 节点类型:start, end, task, gateway, subprocess
    private String assigneeType;    // 分配类型:user, role, department, variable
    private String assigneeValue;   // 分配值:具体用户ID或角色ID
    private Map<String, Object> props; // 扩展属性
}

2.3 连线模型:流程的流转路径

连线模型,就是节点之间的“箭头”。它决定了流程从A走到B,还是从A走到C。

连线模型的核心就三样东西:

  • 来源节点ID:从哪来
  • 目标节点ID:到哪去
  • 流转条件:什么情况下走这条线

你想想看,为什么要有流转条件?因为网关节点需要做判断。比如:

  • 如果金额 <= 5000,走“部门审批”
  • 如果金额 > 5000,走“总经理审批”

这个条件怎么表达?我建议用表达式引擎,比如Spring的SpEL或者自己写个简单的条件解析器。别用硬编码,否则每次改条件都要改代码、重新部署。

注意:连线模型里要加一个“优先级”字段。为什么?因为当多条连线条件同时满足时,引擎需要知道先走哪条。我见过一个项目没加优先级,结果流程随机乱跳,排查了三天才发现是这个问题。

public class LineModel {
    private String id;              // 连线ID
    private String sourceNodeId;    // 来源节点ID
    private String targetNodeId;    // 目标节点ID
    private String condition;       // 流转条件表达式
    private int priority;           // 优先级,数字越小优先级越高
    private String description;     // 描述信息
}

2.4 表单模型:流程的数据载体

表单模型,就是流程里“填什么数据”。没有表单,流程就是个空壳子。

表单模型的设计,我建议遵循“表单与流程分离”的原则。什么意思?就是表单定义和流程定义是独立的,一个表单可以被多个流程复用,一个流程也可以在不同节点使用不同表单。

表单模型的核心要素:

  • 表单定义:有哪些字段?字段类型是什么?是文本框、下拉框还是日期选择器?
  • 表单数据:用户填的具体值,比如“张三”、“2024-01-01”
  • 表单权限:哪些字段在哪个节点可编辑?哪些只读?

我记得有个项目,客户要求“部门审批节点只能看到申请金额,不能看到备注信息”。这就是表单权限控制。我们在表单模型里加了个fieldPermissions字段,每个节点可以配置哪些字段可见、可编辑。

核心要点:表单模型要支持动态渲染。你不能把表单写死在代码里,而是要根据表单定义动态生成HTML。这样用户改表单时,你只需要改配置,不用改代码。

一个简单的表单字段定义:

public class FormField {
    private String fieldId;         // 字段ID
    private String label;           // 字段标签
    private String type;            // 字段类型:text, number, date, select, textarea
    private boolean required;       // 是否必填
    private String defaultValue;    // 默认值
    private List<String> options;   // 下拉选项(仅select类型使用)
    private Map<String, Object> validators; // 校验规则
}

2.5 四个模型的关系

这四个模型不是孤立的,它们协同工作:

  • 流程定义模型是容器,装着一堆节点和连线
  • 节点模型是步骤,每个步骤可以绑定一个表单
  • 连线模型是路径,决定了流程怎么走
  • 表单模型是数据,记录了每一步产生的业务信息

用一句话总结:流程定义画骨架,节点连线定路径,表单模型填血肉

个人经验:设计这四个模型时,一定要考虑“扩展性”。我习惯在每个模型里都加一个Map<String, Object> ext字段,用来存未来可能出现的扩展属性。这个习惯帮我省了至少三次大改。

好了,核心模型就讲到这里。下一章咱们聊聊流程实例模型和任务模型——也就是流程跑起来之后,这些模型怎么变成活的数据。