第一章,Flowable 关键定义

我们将关键概念分为三个阶段:设计态(Design Time)启动态(Deploy Time)运行态(Run Time)

理解这些概念的核心在于掌握它们之间的**“从静态文件到动态数据”**的转化过程。


一、 设计态:绘制蓝图

在这个阶段,一切都只是存储在数据库或文件系统中的“草稿”或“图纸”,静止不动。

1. BPMN 2.0 (Business Process Model and Notation)
  • 定义:这是一套国际通用的业务流程建模符号标准
  • 实质:虽然你在前端看到的是画出来的图(圆圈、方框、箭头),但在底层,它是一段标准的 XML 代码。Flowable 引擎就是通过解析这段 XML 来理解你要怎么走流程的。
  • 地位:它是人和机器沟通的共同语言。
2. 流程模型 (Model)
  • 对应表ACT_RE_MODEL
  • 定义:这是流程图的设计草稿
  • 关键点:它还是“半成品”。你可以在设计器里反复修改它,保存它(对应 REV_ 版本号增加)。此时它还不能被运行,只是数据库里的一串 JSON 或 XML 字符串。
3. 表单 (Form)
  • 定义:流程流转过程中,用户与系统交互的界面载体。
  • 关系
    • 挂载:表单通常挂载在“开始节点”或“用户任务节点”上。
    • 数据:表单里的输入项(Input),最终会转化为流程的变量 (Variable) 存储在引擎中。

二、 启动态:发布生效 (Deployment)

这个阶段是将“草稿”变成“正式规章制度”的过程。

4. 流程部署 (Deployment)
  • 对应表ACT_RE_DEPLOYMENT
  • 定义:这是一个动作,也是一个容器。当你点击“发布”时,引擎会将你的模型打包成一个“部署包”。
  • 作用:它像一个快照(Snapshot),把这一刻的 XML、图片等资源永久固化下来。
5. 流程定义 (Process Definition)
  • 对应表ACT_RE_PROCDEF
  • 定义:这是被引擎解析后的规则对象
  • 类比:在 Java 中,它相当于 Class(类)
  • 关键点:它是只读的。一旦部署产生,就不能修改。如果你改了图重新部署,会生成一个新的流程定义版本(Version 2),旧版本(Version 1)依然存在但不再是最新。
  • 作用:后续所有的流程实例,都是根据这个“模具”克隆出来的。

三、 运行态:流转执行 (Runtime)

这个阶段是真正的业务流转,数据开始在表中产生和消亡。

6. 流程实例 (Process Instance)
  • 对应表ACT_RU_EXECUTION (根记录)
  • 定义:这是流程定义的一次具体执行。
  • 类比:在 Java 中,它相当于 new Object()(对象)
  • 场景:张三发起的请假单是一个实例,李四发起的请假单是另一个实例。它们共用同一个“流程定义”,但互不干扰。
7. 执行流 (Execution / Child Execution)
  • 对应表ACT_RU_EXECUTION

  • 定义:这是引擎内部的“指针”或“令牌”。它标记了流程当前走到哪一步了。

  • 关键关系

    • 单线流程:如果是 A->B->C 这种简单流程,流程实例 ID = 执行流 ID
    • 并发流程:如果遇到并行网关(同时走 A 和 B),流程实例会分裂出两个子执行流 (Child Execution)
      • 父执行流(Root):代表整个实例,原地不动。
      • 子执行流 A:指向任务 A。
      • 子执行流 B:指向任务 B。
  • 理解:Execution 是用来解决“分身术”问题的。

8. 任务 (Task / UserTask)
  • 对应表ACT_RU_TASK
  • 定义:流程运行中需要人工介入的环节。
  • 特征:它会出现在用户的“待办列表”中。只有当用户调用 complete() 方法后,该任务才会消失,执行流(Execution)才会流向下一个节点。
9. 变量 (Variable)
  • 对应表ACT_RU_VARIABLE
  • 定义:流程实例携带的数据包
  • 类型
    • 全局变量:跟着流程实例走,任何节点都能读写(如:请假天数)。
    • 局部变量:只绑定在某个任务或子执行流上,任务结束就消失(如:临时的审批备注)。

四、 流程节点与事件

构成 BPMN 图的具体元素。

10. 流程节点 (Flow Element)
  • 定义:构成流程图的基本单元。包括任务(Task)、网关(Gateway)、事件(Event)等。
11. 信号事件 (Signal Event)
  • 定义广播模式 (Broadcast)

  • 机制:类似于“发令枪”或“收音机”。

  • 一个流程抛出一个信号(Signal Throw)。

  • 所有正在等待该信号的流程实例(Signal Catch)都会被触发。

  • 场景:管理员发布一条“政策变更”信号,所有正在进行的审批流程自动跳转到“合规检查”节点。是一对多的。

12. 消息事件 (Message Event)
  • 定义点对点模式 (P2P)

  • 机制:类似于“发邮件”。

  • 消息必须有明确的接收者(通常通过 Message Name 匹配)。

  • 一个消息只能触发一个特定的流程实例或启动一个新实例。

  • 场景:支付系统回调,通知订单流程“支付成功”。是一对一的。

13. 网关 (Gateway)
  • 定义:流程的交通指挥官
  • 常见类型
    • 排他网关 (Exclusive):X 分支。 if-else,多条路只走一条(如:请假 < 3 天走经理,>= 3 天走总监)。
    • 并行网关 (Parallel)Fork-Join,所有路同时走,必须都到了才能汇合(如:会签,需要财务和行政同时审批)。

关键概念关系图谱

为了方便记忆,请记住这个层级关系:

  1. Model (草稿) --(发布)–> Deployment (部署包)
  2. Deployment --(包含)–> Process Definition (Java类/规则)
  3. Process Definition --(实例化)–> Process Instance (Java对象/具体单据)
  4. Process Instance --(包含)–> Execution (当前指针)
  5. Execution --(指向)–> Task (当前要做的事)
  6. Task + Process Instance --(携带)–> Variables (业务数据)


第二章,Flowable 关键表结构

Flowable 的数据库表结构设计非常精妙,遵循了一套严格的命名规范。理解这些表及其字段的含义,是你排查问题、进行二次开发(如自定义流程监控、报表统计)的基础。

表名通常以 ACT_ 开头(源自 Activiti),第二部分由两个字母组成,代表表的生命周期用途

  • ACT_RE_* (Repository)存储库。存放静态资源,如流程定义、流程图、部署信息。
  • ACT_RU_* (Runtime)运行时。存放流程运行期间产生的数据(正在跑的流程、待办任务)。流程结束,数据即删。
  • ACT_HI_* (History)历史。存放所有数据(已完成的 + 正在运行的)。用于审计和查询历史。
  • ACT_GE_* (General)通用。存放通用数据,如二进制文件(XML/图片)。

一、 静态资源层 (Repository - ACT_RE_*)

这一层是流程的“模具”和“底座”。

1. ACT_RE_DEPLOYMENT (部署信息表)

每一次调用 deploy() 都会在这里产生一条记录。它像一个“包裹”。

  • ID_: [主键] 部署 ID。后续所有资源文件都通过这个 ID 找到归属。
  • NAME_: 部署名称(如 “请假流程-2026版”)。
  • DEPLOY_TIME_: 部署时间。
2. ACT_RE_PROCDEF (流程定义表 - 核心)

引擎解析 XML 后生成的规则类

  • ID_: [主键] 格式通常为 key:version:randomUUID(如 leave:1:25001)。这是发起流程时 startProcessInstanceById 用的 ID。
  • KEY_: [关键] 流程标识(如 leave)。这是 XML 中 <process id="leave"> 定义的。业务开发通常用这个 ID 来 startProcessInstanceByKey
  • VERSION_: 版本号。Flowable 自动维护,每次部署相同的 Key,版本号 +1。
  • DEPLOYMENT_ID_: [外键] 关联 ACT_RE_DEPLOYMENT.ID_。表示这个定义是哪次部署带进来的。
  • RESOURCE_NAME_: 关联的 XML 文件名(如 leave.bpmn20.xml)。
  • DGRM_RESOURCE_NAME_: 关联的图片文件名(如 leave.png)。
  • SUSPENSION_STATE_: 挂起状态(1=激活,2=挂起)。挂起后无法发起新流程。
3. ACT_RE_MODEL (流程模型表 - 设计草稿)

这张表存储流程设计器(Modeler)中的设计草稿。它是可编辑、可变的,是流程定义的前身。

  • ID_: [主键] 模型 ID。模型的唯一标识。

  • KEY_: [关键] 模型标识(如 leave)。对应流程设计图中的 process id。

  • NAME_: 模型名称。在设计器列表中显示的名称(如 “请假流程草稿”)。

  • VERSION_: 版本号。这是设计草稿的版本(不同于流程定义的版本)。通常每次在设计器中点击“保存”,该版本号会增加。

  • META_INFO_: 元数据。存储 JSON 格式的扩展信息。由于 Flowable 原生字段较少,业务系统通常将流程描述关联的表单 ID图标等自定义属性序列化后存储在此字段中。

  • DEPLOYMENT_ID_: [外键] 关联 ACT_RE_DEPLOYMENT.ID_

  • 若为空 (NULL):表示该模型仅为草稿,尚未发布,引擎无法运行它。

  • 若有值:表示该模型已执行过部署操作,指向生成的那个部署包 ID。

  • EDITOR_SOURCE_VALUE_ID_: [外键] 关联 ACT_GE_BYTEARRAY.ID_。指向存储模型源文件(通常为设计器专用的 JSON 数据,而非标准 XML)的二进制记录。前端设计器回显流程图时,读取的是这个字段指向的数据。


二、 运行时层 (Runtime - ACT_RU_*)

这一层是流程的“现场”。请记住:当流程结束,这里的记录会被物理删除!

4. ACT_RU_EXECUTION (执行实例表 - 引擎的心脏)

它是流程运行的指针树干

  • ID_: [主键] 执行流 ID。

  • PROC_INST_ID_: [关键] 流程实例 ID(Root ID)。在一个简单的单线流程中,根执行流的 ID_ 等于 PROC_INST_ID_

  • PARENT_ID_: 父执行流 ID。用于构建树状结构

  • 场景:遇到并行网关时,会分裂出子执行流,子执行流的 PARENT_ID_ 指向根执行流。

  • PROC_DEF_ID_: [外键] 指向 ACT_RE_PROCDEF.ID_。表明当前是在跑哪个版本的流程。

  • ACT_ID_: [关键] 当前停留的节点 ID(XML 中的 UserTask_01)。引擎重启后,靠这个字段知道从哪里继续跑。

  • IS_ACTIVE_: 是否活跃。

  • BUSINESS_KEY_: 业务主键(如请假单 ID)。通常只存放在根执行流中。

5. ACT_RU_TASK (运行时任务表 - 待办核心)

记录所有未完成的人工任务。

  • ID_: [主键] 任务 ID。
  • EXECUTION_ID_: [外键] 关联 ACT_RU_EXECUTION.ID_。表示这个任务属于哪个执行分支。
  • PROC_INST_ID_: 流程实例 ID(为了查询方便冗余存储)。
  • NAME_: 任务名称(如 “经理审批”)。
  • TASK_DEF_KEY_: [关键] 节点定义的 Key(如 task_manager_approve)。开发中常用来判断当前任务处于哪个环节。
  • ASSIGNEE_: 办理人 ID。如果为空,说明是“候选任务”,需要签收。
  • CREATE_TIME_: 任务创建时间。
  • SUSPENSION_STATE_: 1=激活,2=挂起。
6. ACT_RU_VARIABLE (运行时变量表)

存储流程运行期间的 Key-Value 数据。

  • NAME_: 变量名(如 days)。
  • TYPE_: 变量类型(string, integer, boolean, serializable)。
  • TEXT_: 存储字符串值。
  • LONG_: 存储数字值。
  • PROC_INST_ID_: 所属流程实例。
  • TASK_ID_: [关键] 如果有值,说明是局部变量 (Local),只在当前任务有效;如果为空,说明是全局变量

三、 历史层 (History - ACT_HI_*)

这一层是流程的“档案室”。数据永远保留(除非手动清除)。

7. ACT_HI_PROCINST (历史流程实例表)

记录一次请假申请的完整生命周期。

  • ID_: 等同于运行时的 PROC_INST_ID_
  • START_USER_ID_: 发起人 ID
  • START_TIME_: 开始时间。
  • END_TIME_: 结束时间。如果为空,说明流程还在运行中。
  • DURATION_: 耗时(毫秒)。
  • DELETE_REASON_: 结束原因。正常结束为 null;如果是被“驳回/取消”,这里会记录原因。
8. ACT_HI_TASKINST (历史任务实例表)

记录每一个任务的执行情况(包括已办和待办)。

  • ID_: 任务 ID。
  • ASSIGNEE_: 实际办理人。
  • START_TIME_: 任务创建时间(进入节点的时间)。
  • END_TIME_: 任务完成时间(点击“通过”的时间)。
  • DURATION_: 审批耗时(END - START)。这是做绩效统计(谁审批最慢)的核心字段。
9. ACT_HI_ACTINST (历史活动实例表)

最详细的轨迹表。它记录了流程走过的每一个“脚印”,包括 StartEvent, EndEvent, SequenceFlow (连线), Gateway (网关), UserTask 等。

  • ACT_ID_: 节点 ID。
  • ACT_TYPE_: 节点类型(userTask, exclusiveGateway…)。
  • 作用:前端绘制“流程进度高亮图”时,就是查这张表,看哪些节点有 END_TIME_,就把哪些节点标红。

四、 通用数据层 (General - ACT_GE_*)

10. ACT_GE_BYTEARRAY (二进制资源表)

这是数据库里的“文件系统”。

  • ID_: 资源 ID。
  • NAME_: 资源名称(source, leave.bpmn, leave.png)。
  • BYTES_: [BLOB] 文件内容的二进制流。
  • DEPLOYMENT_ID_: [关联]
  • 如果有值:说明是运行态资源(部署产生的 leave.bpmn),引擎用它。
  • 如果为 NULL:说明是设计态资源(草稿 source),设计器用它。

五、 表之间字段的联系

理解这些联系是写复杂 SQL 查询的关键。

  1. 定义与部署的关联
    ACT_RE_PROCDEF.DEPLOYMENT_ID_ -> ACT_RE_DEPLOYMENT.ID_
    (定义属于哪次部署)
  2. 资源与部署的关联
    ACT_GE_BYTEARRAY.DEPLOYMENT_ID_ -> ACT_RE_DEPLOYMENT.ID_
    (XML文件属于哪次部署)
  3. 运行实例与定义的关联
    ACT_RU_EXECUTION.PROC_DEF_ID_ -> ACT_RE_PROCDEF.ID_
    (这个实例是根据哪个规则跑的)
  4. 任务与实例的关联
    ACT_RU_TASK.PROC_INST_ID_ -> ACT_RU_EXECUTION.PROC_INST_ID_
    (任务属于哪个流程单子)
    ACT_RU_TASK.EXECUTION_ID_ -> ACT_RU_EXECUTION.ID_
    (任务属于树上的哪个分支)
  5. 历史与运行时的关联
    ACT_HI_PROCINST.ID_ == ACT_RU_EXECUTION.PROC_INST_ID_
    (ID 是通用的,只是数据搬家了)


第三章,Flowable 核心类与用法


RepositoryService

RepositoryService 是 Flowable 引擎中专门用于管理静态资源的服务接口。它的核心职责是对流程定义文件(.bpmn20.xml)、图片资源、部署包(Deployment)以及模型(Model)进行CRUD(增删改查)操作。

它主要操作以下几张核心数据库表:

  • ACT_RE_DEPLOYMENT:部署信息表(一次部署包含多个资源)。
  • ACT_RE_PROCDEF:流程定义表(解析后的规则数据)。
  • ACT_GE_BYTEARRAY:二进制资源表(存储 XML 文件流、图片流)。
  • ACT_RE_MODEL:流程设计模型表(Web 设计器的草稿)。

以下是 RepositoryService 最常见的用法实例,分为部署流程查询定义读取资源流程控制模型管理五个维度。


1. 部署流程 (Deployment)

这是最基础的操作,将本地的 .bpmn 文件发布到引擎数据库中。

1.1 Classpath 资源部署

最常见的方式,读取 resources 目录下的文件。

Deployment deployment = repositoryService.createDeployment()
    .name("请假流程部署")                  // 设置部署名称
    .key("leave_deployment")              // 设置部署Key(可选)
    .addClasspathResource("processes/leave.bpmn20.xml") // 读取文件
    .addClasspathResource("processes/leave.png")        // 读取图片(可选)
    .deploy();                            // 执行入库操作

System.out.println("部署ID: " + deployment.getId());

  • 数据库影响:向 ACT_RE_DEPLOYMENT 插入 1 条记录,向 ACT_GE_BYTEARRAY 插入 2 条记录(XML+图片),向 ACT_RE_PROCDEF 插入 1 条记录。
1.2 字符串/流式部署

适用于动态生成 XML 或从前端接收 XML 字符串的场景。

String bpmnXml = "<?xml version='1.0' ..."; // 前端传来的 XML 字符串

Deployment deployment = repositoryService.createDeployment()
    .name("动态部署")
    .addString("dynamic_process.bpmn", bpmnXml) // 直接存字符串
    .deploy();


2. 查询流程定义 (Process Definition)

查询 ACT_RE_PROCDEF 表,获取已部署的流程规则信息。

2.1 基础查询
// 查询所有“leave”流程的最新版本
List<ProcessDefinition> list = repositoryService.createProcessDefinitionQuery()
    .processDefinitionKey("leave") // 流程图中的 id="leave"
    .latestVersion()               // 只查最新版本(排除旧版本)
    .orderByProcessDefinitionVersion().desc()
    .list();

for (ProcessDefinition pd : list) {
    System.out.println("ID: " + pd.getId());       // 格式:leave:1:xxxx
    System.out.println("Version: " + pd.getVersion());
    System.out.println("Name: " + pd.getName());
}

2.2 挂起与激活查询
// 查询所有处于“挂起”状态(禁止发起)的流程定义
List<ProcessDefinition> suspendedList = repositoryService.createProcessDefinitionQuery()
    .suspended()
    .list();


3. 读取静态资源 (Resources)

用于查看流程图文件或图片流,常用于前端流程图回显。

3.1 获取流程图 XML 流
String processDefinitionId = "leave:1:10001"; // 从 ProcessDefinition 获取
// 获取 BPMN XML 输入流
InputStream bpmnStream = repositoryService.getProcessModel(processDefinitionId);

// 转换为字符串打印或传给前端
// IoUtil.readUtf8(bpmnStream);

3.2 获取流程图图片流
// 获取流程定义对象
ProcessDefinition pd = repositoryService.createProcessDefinitionQuery()
    .processDefinitionId("leave:1:10001")
    .singleResult();

String diagramResourceName = pd.getDiagramResourceName(); // 获取图片文件名
String deploymentId = pd.getDeploymentId();

// 根据部署ID和资源名称读取流
InputStream imageStream = repositoryService.getResourceAsStream(deploymentId, diagramResourceName);

3.3 获取结构化模型 (BpmnModel)

这是非常重要的一个方法,用于在代码中解析流程图的节点信息(例如:获取下一个节点是不是用户任务,有没有扩展属性 signEnable)。

// 解析为 Java 对象结构
BpmnModel bpmnModel = repositoryService.getBpmnModel("leave:1:10001");

// 访问具体节点
FlowElement flowElement = bpmnModel.getFlowElement("UserTask_01");
if (flowElement instanceof UserTask) {
    UserTask task = (UserTask) flowElement;
    System.out.println("办理人表达式: " + task.getAssignee());
    // 读取扩展属性
    List<ExtensionElement> signs = task.getExtensionElements().get("signEnable");
}


4. 流程定义控制 (Control)

控制流程是否允许被发起。

4.1 挂起流程定义

挂起后,用户无法发起该流程的新实例,但正在运行的实例不受影响(除非设置级联挂起)。

// 挂起流程定义(ID),参数2:是否挂起该定义下的所有流程实例,参数3:生效时间(null代表立即)
repositoryService.suspendProcessDefinitionById("leave:1:10001", true, null);

4.2 激活流程定义
repositoryService.activateProcessDefinitionById("leave:1:10001", true, null);

4.3 删除部署

删除部署会级联删除定义、资源文件。

String deploymentId = "10001";
// 参数2 cascade: true 表示级联删除。
// 如果该流程已有正在运行的实例,cascade=false 会抛异常;cascade=true 会强制删除运行中数据
repositoryService.deleteDeployment(deploymentId, true);


5. 模型管理 (Model - 设计器专用)

这些方法主要用于操作 ACT_RE_MODEL 表,服务于前端流程设计器(保存草稿、发布草稿)。

5.1 创建/保存模型草稿
// 1. 创建元数据
Model model = repositoryService.newModel();
model.setName("请假流程草稿");
model.setKey("leave");
model.setMetaInfo("{\"description\":\"这是一个草稿\"}");
repositoryService.saveModel(model); // 插入 ACT_RE_MODEL

// 2. 保存 XML 内容(草稿源文件)
String editorSource = "{\"resourceId\":\"... JSON 格式的流程设计数据 ...\"}";
repositoryService.addModelEditorSource(model.getId(), editorSource.getBytes("UTF-8"));

5.2 将模型草稿部署为流程定义

这是将“设计”转化为“运行”的关键步骤。

// 1. 获取模型
Model modelData = repositoryService.getModel(modelId);
// 2. 获取源文件字节流
byte[] bytes = repositoryService.getModelEditorSource(modelData.getId());
// 3. 转换(假设 bytes 是 JSON 格式,需转为 BPMN XML,通常用 Jackson 或 Flowable 转换器)
// ... 转换逻辑省略,得到 bpmnBytes ...

// 4. 执行部署
Deployment deployment = repositoryService.createDeployment()
    .name(modelData.getName())
    .addString(modelData.getKey() + ".bpmn20.xml", new String(bpmnBytes))
    .deploy();


RuntimeService

RuntimeService 是 Flowable 引擎中负责管理 “正在运行” 的流程实例和执行流的核心服务接口。它的生命周期覆盖了从流程启动(Start)到流程结束(End)之间的所有动态操作。

它主要操作以下几张核心数据库表:

  • ACT_RU_EXECUTION:运行时执行实例表(核心指针)。
  • ACT_RU_VARIABLE:运行时变量表。
  • ACT_RU_IDENTITYLINK:运行时用户关系表(如发起人、参与者)。

以下是 RuntimeService 最核心的方法分类及代码实例:


1. 启动流程实例 (Start Process)

这是 RuntimeService 最基础的功能,用于根据流程定义(Definition)创建一个新的运行实例(Instance)。

1.1 通过 Key 启动(最常用)

使用流程定义的 key 启动,引擎会自动选择最新版本的流程定义。

// 1. 准备启动时的全局变量
Map<String, Object> variables = new HashMap<>();
variables.put("applicant", "zhangsan");
variables.put("days", 3);

// 2. 启动流程
// 参数1: processDefinitionKey (XML中的 id="leave")
// 参数2: businessKey (业务主键,如请假单的数据库ID "10086"),方便后期关联查询
// 参数3: variables (初始变量)
ProcessInstance instance = runtimeService.startProcessInstanceByKey("leave", "10086", variables);

System.out.println("流程实例ID: " + instance.getId());
System.out.println("流程定义ID: " + instance.getProcessDefinitionId());

  • 数据库影响:向 ACT_RU_EXECUTION 插入执行流数据,向 ACT_RU_VARIABLE 插入变量数据。
1.2 指定发起人启动(Authentication)

Flowable 不会自动记录是谁发起的流程,需要显式设置。

// 设置当前线程的发起人ID
identityService.setAuthenticatedUserId("user_001");

// 启动流程
ProcessInstance instance = runtimeService.startProcessInstanceByKey("leave");

// 启动后通过 IdentityLink 表可以查到 starter 是 user_001


2. 查询执行实例 (Query Execution)

用于查询正在运行的流程实例 (ProcessInstance) 或执行流指针 (Execution)。

2.1 查询单个流程实例状态
// 根据流程实例ID查询
ProcessInstance processInstance = runtimeService.createProcessInstanceQuery()
    .processInstanceId("25001")
    .singleResult();

if (processInstance != null) {
    System.out.println("当前节点: " + processInstance.getActivityId()); // 获取当前停留的节点ID
    System.out.println("是否挂起: " + processInstance.isSuspended());
} else {
    System.out.println("流程已结束或不存在");
}

2.2 关联业务Key查询
// 根据业务ID反查流程实例
ProcessInstance instance = runtimeService.createProcessInstanceQuery()
    .processDefinitionKey("leave")
    .processInstanceBusinessKey("10086") // 业务表主键
    .singleResult();

2.3 查询活跃的子执行流

在并行网关或多实例任务中,会有多个活跃的 Execution

List<Execution> executions = runtimeService.createExecutionQuery()
    .processInstanceId("25001")
    .onlyChildExecutions() // 不查根节点,只查子节点
    .list();

for (Execution exe : executions) {
    System.out.println("执行流ID: " + exe.getId() + ", 当前停留节点: " + exe.getActivityId());
}


3. 变量管理 (Variables)

在流程运行过程中,动态读取或修改全局变量。

3.1 设置/更新变量
// 设置单个变量(如果存在则更新,不存在则创建)
runtimeService.setVariable("25001", "managerCheckResult", true);

// 批量设置变量
Map<String, Object> newVars = new HashMap<>();
newVars.put("auditTime", new Date());
newVars.put("score", 95);
runtimeService.setVariables("25001", newVars);

  • 注意:这里操作的是 ACT_RU_VARIABLE 表。
3.2 获取变量
// 获取所有变量
Map<String, Object> allVars = runtimeService.getVariables("25001");

// 获取指定变量
Integer days = (Integer) runtimeService.getVariable("25001", "days");

3.3 本地变量 (Local Variables)

setVariableLocal 设置的变量只绑定在当前的执行流(Execution)上,通常用于多实例子流程或特定分支的数据隔离。

// executionId 是子执行流ID,不是流程实例ID
runtimeService.setVariableLocal(executionId, "loopCounter", 1);


4. 流程控制 (Control)

对正在运行的流程进行干预,如强制删除、挂起、跳转。

4.1 删除流程实例

强制终止一个流程,通常用于“作废”操作。

// 参数1: processInstanceId
// 参数2: deleteReason (删除原因,会记录在历史表中)
runtimeService.deleteProcessInstance("25001", "用户主动撤销申请");

  • 数据库影响:删除 ACT_RU_* 所有相关数据,更新 ACT_HI_PROCINSTDELETE_REASON_
4.2 挂起/激活流程实例

挂起后,该实例无法继续流转(任务无法完成)。

// 挂起
runtimeService.suspendProcessInstanceById("25001");

// 激活
runtimeService.activateProcessInstanceById("25001");

4.3 节点跳转 (Change State)

这是 Flowable 强大的**“自由跳转”**功能,常用于实现复杂的“驳回”、“任意退回”逻辑。

// 将流程从“经理审批”节点(Activity_Manager) 强行移动到 “填写申请”节点(Activity_Apply)
runtimeService.createChangeActivityStateBuilder()
    .processInstanceId("25001")
    .moveActivityIdTo("Activity_Manager", "Activity_Apply")
    .changeState();

  • 逻辑:引擎会计算并销毁当前节点的所有 Task 和 Execution,然后在目标节点重新生成新的 Execution 和 Task。

5. 信号与消息触发 (Signal & Message)

用于处理中间捕获事件(Intermediate Catch Event),实现流程间的通信或外部触发。

5.1 触发信号事件
// 广播信号:所有订阅了 "alertSignal" 的流程实例都会收到并继续向下执行
runtimeService.signalEventReceived("alertSignal");

// 向指定执行流发送信号
runtimeService.signalEventReceived("alertSignal", executionId);

5.2 触发消息事件

消息通常是点对点的。

// 触发在这个执行流上等待的消息捕获事件
runtimeService.messageEventReceived("paymentSuccessMessage", executionId);

// 也可以通过消息名启动一个通过消息启动的流程
runtimeService.startProcessInstanceByMessage("startMsg");


TaskService

TaskService 是 Flowable 引擎中专门用于管理 “人工任务” (UserTask) 的服务接口。它的核心职责是处理所有需要人工干预的环节,包括任务的查询、领取、办理、转派以及任务相关的附属信息(如评论)管理。

它主要操作以下核心数据库表:

  • ACT_RU_TASK:运行时任务表(最核心,记录当前待办)。
  • ACT_RU_IDENTITYLINK:任务与人员的关系表(记录候选人、候选组)。
  • ACT_RU_VARIABLE:任务级别的局部变量。
  • ACT_HI_COMMENT:任务评论表(审计用)。

以下是 TaskService 最常见的方法分类及代码实例:


1. 任务查询 (Querying Tasks)

这是构建“待办列表”和“公办池”的基础。

1.1 查询“我的待办” (Direct Assignee)

查询明确指派给某个用户的任务。

// 查询分配给 "zhangsan" 的所有未挂起任务,按创建时间倒序
List<Task> todoList = taskService.createTaskQuery()
    .taskAssignee("zhangsan")   // 指定办理人
    .active()                   // 仅查询激活状态的任务
    .orderByTaskCreateTime().desc()
    .list(); // 执行查询

for (Task task : todoList) {
    System.out.println("任务ID: " + task.getId());
    System.out.println("任务名称: " + task.getName());
    System.out.println("流程实例ID: " + task.getProcessInstanceId());
}

  • SQL 逻辑SELECT * FROM ACT_RU_TASK WHERE ASSIGNEE_ = 'zhangsan' ...
1.2 查询“候选任务” (Candidate User/Group)

查询放入“公海池”的任务(即没有指定具体人,只指定了角色或部门,需要去“签收”的任务)。

// 查询 "finance" 组的成员可以看到的任务,或者直接指定给 "lisi" 作为候选人的任务
List<Task> poolList = taskService.createTaskQuery()
    .taskCandidateGroup("finance") // 候选组:财务部
    // .taskCandidateUser("lisi")  // 或者候选人:李四
    .list();

  • 原理:需要关联查询 ACT_RU_IDENTITYLINK 表。
1.3 综合查询(带业务Key和变量)
Task task = taskService.createTaskQuery()
    .processDefinitionKey("leave")          // 流程定义Key
    .processInstanceBusinessKey("10086")    // 业务主键
    .includeProcessVariables()              // 预加载流程变量(避免N+1查询)
    .singleResult();                        // 返回单个结果


2. 任务生命周期管理 (Lifecycle)

控制任务状态的流转。

2.1 签收任务 (Claim)

将一个“公海”任务(无 Assignee)锁定给自己。

String taskId = "5001";
String userId = "zhangsan";

// 签收任务
// 前提:该任务目前的 Assignee 必须为 null,且 userId 必须在 Candidate 列表中(除非是管理员)
taskService.claim(taskId, userId);

  • 数据库影响:更新 ACT_RU_TASK 表,将 ASSIGNEE_ 字段设为 "zhangsan"
2.2 归还任务 (Unclaim)

不想办了,退回公海池。

// 将 Assignee 设为 null,任务重新变为对所有候选人可见
taskService.unclaim(taskId);

2.3 完成任务 (Complete) —— 最核心方法

提交任务,驱动流程向下走。

String taskId = "5001";

// 准备流转所需的变量(例如审批结果、下一步参数)
Map<String, Object> variables = new HashMap<>();
variables.put("approved", true);
variables.put("managerComment", "同意,情况属实");

// 完成任务,并传入全局变量
// 引擎会自动删除当前任务,计算下一步节点
taskService.complete(taskId, variables);

  • 数据库影响:删除 ACT_RU_TASK 当前记录,更新 ACT_HI_TASKINST 历史记录,驱动 Execution 向下流转。
2.4 完成任务并设置局部变量 (Local Scope)

如果你希望变量只在当前任务历史里保留,不污染全局流程变量。

// 参数3 true 代表 localScope
// 这些变量在任务完成后,Runtime 表中会消失,但能在 HistoryService 查到详情
taskService.complete(taskId, variables, true);


3. 任务委派与移交 (Delegation & Assignment)

处理“转办”和“协办”场景。

3.1 移交任务 (Set Assignee / Transfer)

直接把任务甩给别人,自己不再负责。

// 将任务直接转给 "lisi"
// 类似于“改派”
taskService.setAssignee(taskId, "lisi");

3.2 委派任务 (Delegate)

A 委派给 B。B 办理完后,任务会自动回到 A 手里(通常用于咨询意见)。

// 1. 张三委派给李四
taskService.delegateTask(taskId, "lisi");
// 此时任务状态:OWNER_ = zhangsan, ASSIGNEE_ = lisi, DELEGATION_State = PENDING

// ... 李四登录系统 ...

// 2. 李四办理任务(注意:委派任务不能用 complete,要用 resolve)
// 李四做完工作,调用 resolveTask 归还给张三
taskService.resolveTask(taskId);
// 此时任务状态:ASSIGNEE_ 变回 zhangsan, DELEGATION_State = RESOLVED

// ... 张三重新看到任务 ...
// 3. 张三最终提交
taskService.complete(taskId);


4. 任务数据管理 (Data & Variables)

4.1 设置/获取任务变量
// 设置任务局部变量(Local Variable)
// 这些数据绑定在 TaskID 上,而不是 ProcessInstanceID 上
taskService.setVariableLocal(taskId, "localNote", "这是一个临时备注");

// 获取变量
Object val = taskService.getVariableLocal(taskId, "localNote");

4.2 添加审批意见 (Comment)

这是 Flowable 提供的标准审批意见存储方式。

// 参数:taskId, processInstanceId, message
taskService.addComment(taskId, processInstanceId, "同意,请尽快处理");

// 查询某任务的评论
List<Comment> comments = taskService.getTaskComments(taskId);

  • 数据库影响:插入 ACT_HI_COMMENT 表。
4.3 管理附件 (Attachment)
// 参数:attachmentType, taskId, processInstanceId, attachmentName, attachmentDescription, url
taskService.createAttachment("url", taskId, processInstanceId, "发票扫描件", "电子发票", "http://oss.aliyun.../file.pdf");


5. 独立任务管理 (Standalone Task)

创建一个不属于任何流程实例的“孤立任务”。(场景较少,用于简单的任务看板)。

// 1. 新建任务
Task newTask = taskService.newTask();
newTask.setName("临时会议任务");
newTask.setAssignee("zhangsan");

// 2. 保存入库
taskService.saveTask(newTask);

// 3. 删除任务(物理删除)
// 如果是流程中的任务,deleteTask 会报错,必须用 deleteProcessInstance
// 如果是 cascade=true,则连同历史一起删
taskService.deleteTask(newTask.getId(), true);


HistoryService

HistoryService 是 Flowable 引擎中专门用于查询历史数据(即已经发生过的数据)的服务接口。

RuntimeService(只查正在运行的数据)不同,HistoryService 查询的是 ACT_HI_* 系列表。无论流程是正在运行还是已经结束,只要配置了相应的历史级别(History Level),数据都会沉淀在这里。

它主要操作以下核心数据库表:

  • ACT_HI_PROCINST:历史流程实例表(记录一次完整的业务流程)。
  • ACT_HI_TASKINST:历史任务实例表(记录每一个人工任务的开始、结束、耗时)。
  • ACT_HI_ACTINST:历史活动实例表(记录流程走过的每一个节点,包括网关、连线、开始/结束节点)。
  • ACT_HI_VARINST:历史变量表(记录变量的最终值或每一次变化)。

以下是 HistoryService 最常见的方法分类及代码实例:


1. 历史流程实例查询 (Historic Process Instances)

用于查询“已经结束”的流程,或者查询某个用户发起的历史所有流程(包含进行中和已结束)。

1.1 查询“我发起的”所有流程

这是“我的申请记录”功能的后端实现基础。

String currentUserId = "zhangsan";

List<HistoricProcessInstance> list = historyService.createHistoricProcessInstanceQuery()
    .startedBy(currentUserId)       // 过滤发起人
    .finished()                     // (可选) 只查已结束的。如果不加,会包含正在运行的
    .orderByProcessInstanceEndTime().desc() // 按结束时间倒序
    .list();

for (HistoricProcessInstance hpi : list) {
    System.out.println("流程ID: " + hpi.getId());
    System.out.println("耗时(毫秒): " + hpi.getDurationInMillis());
    System.out.println("开始时间: " + hpi.getStartTime());
    System.out.println("结束时间: " + hpi.getEndTime());
    System.out.println("删除原因: " + hpi.getDeleteReason()); // 如果是正常结束则为null,驳回/取消会有值
}

  • SQL 映射SELECT * FROM ACT_HI_PROCINST WHERE START_USER_ID_ = 'zhangsan' ...
1.2 查询特定业务数据的历史流程

通过业务 Key(如订单号)反查流程历史。

HistoricProcessInstance hpi = historyService.createHistoricProcessInstanceQuery()
    .processInstanceBusinessKey("ORDER_20260127")
    .singleResult();


2. 历史任务查询 (Historic Task Instances)

用于查询“我已办的任务”(Done List)。这是工作流系统中非常高频的查询。

2.1 查询“我的已办”

查询某个用户曾经处理过的所有任务。

List<HistoricTaskInstance> doneList = historyService.createHistoricTaskInstanceQuery()
    .taskAssignee("zhangsan")   // 指定办理人
    .finished()                 // 关键:必须查已完成的(finished)
    .orderByHistoricTaskInstanceEndTime().desc()
    .list();

for (HistoricTaskInstance hti : doneList) {
    System.out.println("任务ID: " + hti.getId());
    System.out.println("任务名称: " + hti.getName());
    System.out.println("办理耗时: " + hti.getDurationInMillis()); // 领取到完成的时间差
    System.out.println("处理时间: " + hti.getEndTime());
}

  • SQL 映射SELECT * FROM ACT_HI_TASKINST WHERE ASSIGNEE_ = 'zhangsan' AND END_TIME_ IS NOT NULL ...
2.2 包含流程变量查询

为了避免 N+1 查询问题,可以在查历史任务时直接把当时的流程变量带出来。

List<HistoricTaskInstance> list = historyService.createHistoricTaskInstanceQuery()
    .taskAssignee("zhangsan")
    .includeProcessVariables() // 联查变量表
    .list();

// 获取变量
Map<String, Object> vars = list.get(0).getProcessVariables();


3. 历史活动查询 (Historic Activity Instances)

这是实现 “流程进度图”“审批轨迹” 的核心方法。它能查出流程具体走过了哪些节点(包括连线、网关、自动服务任务等)。

3.1 查询流程流转轨迹
String processInstanceId = "25001";

List<HistoricActivityInstance> activities = historyService.createHistoricActivityInstanceQuery()
    .processInstanceId(processInstanceId)
    .orderByHistoricActivityInstanceStartTime().asc() // 按时间正序,还原发生顺序
    .list();

for (HistoricActivityInstance hai : activities) {
    System.out.println("节点ID: " + hai.getActivityId());
    System.out.println("节点名称: " + hai.getActivityName());
    System.out.println("节点类型: " + hai.getActivityType()); // userTask, startEvent, exclusiveGateway
    System.out.println("处理人: " + hai.getAssignee());     // 如果是 UserTask 会有人
}

  • 数据用途:前端拿到这个 List 后,可以将 BPMN 流程图中对应的节点标红,展示流程走到了哪一步,以及经过了哪些路径。

4. 历史变量查询 (Historic Variable Instances)

用于审计数据。例如:查询某个流程结束时,“请假天数”最终被改成了多少,或者查询某个变量的历史修改记录。

4.1 查询流程实例的所有历史变量
List<HistoricVariableInstance> vars = historyService.createHistoricVariableInstanceQuery()
    .processInstanceId("25001")
    .list();

for (HistoricVariableInstance var : vars) {
    System.out.println("变量名: " + var.getVariableName());
    System.out.println("变量值: " + var.getValue());
    System.out.println("最后更新时间: " + var.getLastUpdatedTime());
}

4.2 查询特定变量
HistoricVariableInstance scoreVar = historyService.createHistoricVariableInstanceQuery()
    .processInstanceId("25001")
    .variableName("score")
    .singleResult();


5. 历史清理与删除 (Delete History)

当历史数据量过大(如千万级),或者业务要求“撤销”某个归档流程时,需要删除历史数据。

5.1 删除单个历史流程实例

这将级联删除该流程实例下的所有历史任务、历史变量、历史活动记录。

// 参数: processInstanceId
historyService.deleteHistoricProcessInstance("25001");

  • SQL 影响:从 ACT_HI_PROCINST, ACT_HI_TASKINST, ACT_HI_VARINST 等表中物理删除相关行。
5.2 删除历史任务

通常不推荐单独删除任务,容易导致数据不一致。

historyService.deleteHistoricTaskInstance("taskId_1001");


6. 高级功能:本机查询 (Native Query)

当 Flowable 提供的 API 无法满足复杂的报表需求(例如复杂的 JOIN 或统计查询)时,可以使用 Native Query 直接写 SQL,但返回的是 Flowable 的实体对象。

// 统计每个用户办理了多少个任务
String sql = "SELECT count(*) FROM " + managementService.getTableName(HistoricTaskInstance.class) + 
             " T WHERE T.ASSIGNEE_ = #{assignee}";

long count = historyService.createNativeHistoricTaskInstanceQuery()
    .sql(sql)
    .parameter("assignee", "zhangsan")
    .count();



第四章,Flowable实例流程


一,管理员创建流程分类

在正式设计复杂的业务流程之前,我们需要先为它们建立一个“文件夹体系”。流程分类(Category)的作用就是对不同业务域(如人事、财务、行政)的流程进行逻辑隔离与归纳。

1. 业务交互流程

这一步是整个 BPM 系统的入口。管理员的操作路径非常直观:

  • 操作入口:登录后台管理系统,进入【工作流】->【流程分类】菜单。
  • 输入信息:点击“新增”按钮,填写分类的基础元数据。
  • 分类名称 (name):展示给用户看的名字,例如 Test03(或“人事考勤”)。
  • 分类编码 (code):系统内部识别的唯一标识,例如 Test03(或 hr_attendance)。
  • 排序 (sort):决定该分类在列表中的显示顺序,例如 3
2. 前后端数据流转

当管理员点击“确定”后,数据在系统内部经历了一次标准的 CRUD:

  • 前端请求 (Request)
    前端封装表单数据,发起 POST 请求至 /bpm/category/create 接口。
// 请求载荷示例
{
  "name": "Test03",
  "code": "Test03",
  "sort": 3
}

  • 后端处理 (Processing)
    Controller 层接收请求后,下沉至 Service 层进行核心校验。

  • 唯一性检查:这是最关键的业务规则。系统会查询 bpm_category 表,确保传入的 namecode 在全表中是唯一的。如果已存在,直接抛出异常,防止数据冲突。

  • 持久化:校验通过后,后端将数据封装为 Entity 对象。

  • 数据库落地 (Database)
    最终,一条新的记录被插入到业务表 bpm_category 中。

注意:bpm_category是 业务扩展表,而非 Flowable 的原生表。Flowable 虽然也有 Category 字段,但为了实现更复杂的层级管理和元数据控制,我们通常维护一张独立的分类表。


二,管理员创建流程表单

在定义了流程分类后,下一步是构建流程流转中承载数据的容器——流程表单。本环节通过低代码形式,将业务需求转化为系统可识别的数据结构。

1. 可视化表单设计

管理员进入后台管理系统,通过内置的表单设计器进行可视化配置。在此场景中,管理员创建了一个名为“请假表单”的业务单据,并拖拽配置了两个核心组件:

  • 请假原因:单行文本输入框。
  • 请假时间:单行文本输入框(用于输入具体天数)。
    完成布局与属性配置后,点击保存触发提交。
2. 前后端交互协议

前端将可视化的配置结果转化为 JSON 数据结构,并通过 POST 请求发送至 /bpm/form/create 接口。请求载荷包含三个核心参数:

  • name (表单名称)
    标识表单的业务名称,此处为 "请假表单"

  • conf (全局配置)
    定义表单的整体样式与行为。

    • 样式参数:如 "labelPosition": "right"(标签右对齐)、"labelWidth": "100px"(标签宽度)。
    • 按钮控制:定义了 "submitBtn"(提交按钮)显示,而 "resetBtn"(重置按钮)隐藏。
  • fields (组件字段定义)
    这是表单的核心数据模型,以数组形式定义了具体控件的属性。

    • 组件一(请假原因):字段标识为 field: "Fkkgmkfa5l8eabc",类型为 input,标题为 title: "请假原因"
    • 组件二(请假时间):字段标识为 field: "Fl6ymkfbx7rrahc",类型为 input,标题为 title: "请假时间"

注意:前端生成的随机字符串 ID(如 Fkkg...)是后续流程运行时存取具体业务值的唯一键(Key)。

3. 数据持久化存储

后端接收请求后,解析并校验参数,随后执行插入操作:

  • 目标表bpm_form(业务表单定义表)。

  • 存储逻辑

    • conffields 字段被序列化为 JSON 字符串 存储在对应的数据库列中。
    • 系统为该表单生成一个全局唯一的 id
  • 返回结果:返回创建成功状态。

此步骤生成的 formId 至关重要,它将在下一章“创建流程模型”时被引用,从而实现“流程定义”与“业务表单”的物理绑定。


三,管理员创建流程模型

在完成了分类与表单的创建后,核心环节便是构建流程模型。此步骤旨在定义业务流转的逻辑规则(BPMN 图),并将前两章创建的分类与表单进行物理绑定。

1. 模型配置与可视化设计

管理员在后台管理系统中新建流程模型,操作分为两个维度:

  • 元数据配置

    • 基本信息:定义流程名称为 请假流程,唯一标识 Key 设为 leave,并归属到 Test03 分类下。
    • 表单绑定:选择上一章创建的“请假表单”,系统在后端记录该表单的唯一标识 formId=41
  • BPMN 图形设计

    • 进入流程设计器(Modeler),绘制标准的 BPMN 2.0 流程图。
    • 配置起始节点(StartEvent),确保其关联了上述请假表单(用于发起流程时渲染)。
    • 绘制结束节点(EndEvent)以构成闭环,并点击保存。
2. 数据传输协议

前端将配置元数据与图形数据合并,通过 POST 请求发送至 /bpm/model/create 接口。核心载荷(Payload)包含:

  • 元数据name=“请假流程”, key=“leave”, category=“Test03”, formId=41。
  • 图形数据bpmnXml(由设计器生成的标准 XML 字符串)。
3. 后端处理与双表存储

后端接收请求后,首先校验模型 Key 的合法性及唯一性。校验通过后,通过 Flowable 原生的 RepositoryService 执行两步核心存储操作,分别涉及模型元数据表通用字节资源表

  • 步骤一:存储模型元数据 (ACT_RE_MODEL)
    调用 repositoryService.saveModel(model) 初始化模型记录。

    • KEY_ / NAME_ / CATEGORY_:直接映射传入的基本信息。
    • META_INFO_ (关键扩展):由于 Flowable 原生模型表没有 formId 字段,框架将 formId: 41 以及描述信息封装为 JSON 字符串,存储在此字段中。这是实现“流程-表单”绑定的关键。
  • 步骤二:存储模型源文件 (ACT_GE_BYTEARRAY)
    调用 repositoryService.addModelEditorSource(id, bytes) 处理 BPMN XML 数据。

    • 数据转换:将前端传来的 bpmnXml 字符串转换为 UTF-8 字节流。
    • 持久化:将字节流作为一条记录插入 ACT_GE_BYTEARRAY 表。此表专门用于存储二进制大对象(BLOB)。
    • 建立关联:Flowable 自动将新生成的字节记录 ID 回填至 ACT_RE_MODEL 表的 EDITOR_SOURCE_VALUE_ID_ 字段。

四,版本化与发布——管理员部署流程模型

流程模型在“部署”之前,仅仅是存储在数据库中的静态设计草稿。部署操作是将设计草稿转化为引擎可执行代码(ProcessDefinition)的关键步骤,同时涉及严格的版本控制与数据快照机制。

1. 部署触发与完整性校验

管理员在流程模型列表中选择“请假流程”,点击【发布】按钮。

  • 接口交互:前端通过 POST 请求调用 /bpm/model/deploy 接口,仅需传输模型主键 id
  • 前置校验:后端根据 id 查询 ACT_RE_MODEL 表获取模型元数据及 XML 源码。系统调用 Flowable 校验器(BPMN Validator)对 XML 进行语法分析,检查是否存在游离节点、缺失的起始/结束事件或不合法的连线逻辑。
2. 核心部署:Flowable 引擎的三表联动

校验通过后,后端调用 Flowable 原生接口 repositoryService.createDeployment()...deploy() 执行部署。这是一个原子性事务,底层数据库发生如下级联变化:

  • 生成部署档案 (ACT_RE_DEPLOYMENT)
    插入一条新记录,生成全局唯一的 DEPLOYMENT_ID_。该记录标识了本次发布的版本快照时间与名称。
  • 固化资源文件 (ACT_GE_BYTEARRAY)
    引擎将模型草稿中的 XML 源码复制一份,作为不可变的资源文件插入此表,并将 DEPLOYMENT_ID_ 关联到上一条记录。

技术修正:此处实现了“设计”与“运行”的物理隔离。即便后续管理员修改了模型草稿(Model),也不会影响已部署运行的资源文件。

  • 生成流程定义 (ACT_RE_PROCDEF)
    引擎解析 XML 结构,提取流程规则,插入一条流程定义记录。
  • 版本控制:引擎自动查询该 Key(leave)的历史版本,将 VERSION_ 字段设为“最大版本号 + 1”。
  • 状态关联:该记录同时关联上述的 DEPLOYMENT_ID_ 和资源文件名称。
3. 扩展元数据存储 (bpm_process_definition_info)

由于 Flowable 原生的 ACT_RE_PROCDEF 表结构固定,无法存储业务框架特有的配置(如关联的 formId、自定义图标、描述等)。
系统在此步执行扩展逻辑:从 ACT_RE_MODELMETA_INFO_ 中提取表单 ID 等信息,结合新生成的 processDefinitionId,插入到自定义扩展表 bpm_process_definition_info 中。这确保了流程定义与业务表单在运行时的强绑定。

4. 模型状态回写

部署的最后一步是更新设计草稿的状态。系统将新生成的 deploymentId 回写到 ACT_RE_MODEL 表的 DEPLOYMENT_ID_ 字段。

  • 字段非空:表示该模型已发布,且指向最新的部署版本。
  • 逻辑闭环:至此,流程模型完成了从“设计态”到“运行态”的转化,具备了发起流程实例的能力。
5. 深入解析:设计态与运行态的资源隔离(ACT_GE_BYTEARRAY 实例分析)

在执行部署操作后,若观察底层的通用字节资源表 ACT_GE_BYTEARRAY,会发现同一个“请假流程”存在两类截然不同的 XML 资源记录。这并非数据冗余,而是 Flowable 架构中为了实现“设计态”与“运行态”物理隔离而设计的核心机制。

我们将第三章(创建模型)与第四章(部署模型)产生的两条关键记录进行对比分析(参考数据库实例数据):

第一类记录:设计态源文件(Editor Source)

  • 来源:产生于第三章“创建流程模型”,对应 repositoryService.saveModel 操作。

  • 实例特征

    • NAME_:固定为 source。这是 Flowable 设计器专用的标识,代表这是“设计草稿”。
    • DEPLOYMENT_ID_(Null)。关键特征。因为它是草稿,尚未发布,所以不关联任何部署记录。
    • REV_7(示例值)。高版本号表明管理员在设计器中点击了 7 次保存。
  • 技术含义:这条记录是可变的。它仅供前端流程设计器(Model Editor)回显和编辑使用。无论管理员如何修改这条记录,都不会影响线上正在运行的业务。

第二类记录:运行态定义文件(Deployment Resource)

  • 来源:产生于第四章“部署流程模型”,对应 repositoryService.deploy 操作。

  • 实例特征

    • NAME_:变更为具体的资源名称 leave.bpmn(格式通常为 流程Key + .bpmn)。
    • DEPLOYMENT_ID_7e7559db...(示例值)。有具体值,指向 ACT_RE_DEPLOYMENT 表的一条部署记录。
    • REV_1。永远为 1。
  • 技术含义:这条记录是不可变的快照。部署动作发生时,引擎读取了上述的 source 草稿,将其转化为标准的 BPMN 2.0 XML 格式,并复制一份作为永久归档插入此表。流程引擎(Process Engine)在流转任务时,只读取这条记录,完全忽略 source 记录。


五,实例初始化——员工发起流程

在流程模型部署上线后,业务流转的实质性阶段始于“流程实例(Process Instance)”的创建。本章阐述员工提交申请时,系统如何将静态的流程定义转化为动态的运行数据。

1. 业务交互与数据封装

员工在后台管理系统前端访问已部署的“请假流程”入口。

  • 表单渲染:前端根据第二章定义的 JSON 配置渲染动态表单,员工填写业务数据:请假原因=“看病”,请假时间=“3”。
  • 提交动作:点击“发起”按钮,触发提交逻辑。
2. 接口协议与载荷定义

前端封装请求参数,发送 POST 请求至 /bpm/process-instance/create。请求载荷(Payload)包含两个核心要素:

  • processDefinitionId:指向具体的流程定义版本 ID(如 leave:1:7e82...),确保基于正确的版本发起。
  • variables:将表单业务数据封装为键值对 Map。
{
  "Fkkgmkfa...": "看病", // Key为表单设计时生成的随机字段ID
  "Fl6ymkfb...": "3"
}

3. 后端预处理(业务层)

后端 Service 层接收请求后,在调用引擎之前执行一系列业务校验与数据增强:

  • 定义校验:根据 processDefinitionId 查询 ACT_RE_PROCDEF 表,确认流程定义处于激活(Active)状态。
  • 权限与合法性校验:验证发起用户是否具备该流程的发起权限,并校验传入参数的格式合法性。
  • 上下文增强:向 variables 中注入系统级内置变量,例如:
    • PROCESS_START_USER_ID: 发起人 ID。
    • PROCESS_STATUS: 初始化为“审批中”。
4. 引擎核心执行(持久化层)

业务层构建 ProcessInstanceBuilder 并调用 .start() 方法。此操作是一个原子性事务,触发 Flowable 引擎执行节点流转多表级联写入

关键数据库操作:

  • A. 初始化执行流 (ACT_RU_EXECUTION)
    引擎并非只插入一条记录,通常会生成两条记录以构建执行树:

    • 根执行流 (Root Execution):代表流程实例本身。ID_PROC_INST_ID_ 相同,PARENT_ID_ 为空。
    • 子执行流 (Child Execution):代表当前的执行指针。PARENT_ID_ 指向根执行流 ID,ACT_ID_ 指向当前停留的节点(如第一个用户任务)。
  • B. 变量持久化 (ACT_RU_VARIABLE)
    将增强后的 variables Map 扁平化存储。每一条键值对对应表中一行记录,外键 PROC_INST_ID_ 关联上述的根执行流 ID。

  • C. 身份链路绑定 (ACT_RU_IDENTITYLINK)
    引擎会向此表插入一条记录,标记当前用户为流程的 starter(发起人)。这对于后续查询“我发起的流程”至关重要。

  • D. 任务生成 (ACT_RU_TASK)
    引擎计算流程图路径,从 StartEvent 流转至第一个节点(假设为用户任务)。

    • 在表中插入一条新任务记录。
      • ASSIGNEE_ (办理人):根据流程图配置(如 ${initiator} 或固定值)解析并填入具体用户 ID。
      • EXECUTION_ID_:关联到上述的子执行流 ID(注意:是关联子执行流,而非根实例)。
  • E. 历史审计同步 (ACT_HI_*)
    start() 不仅操作运行时表(Runtime),会同步写入历史表以保证审计可追溯:

    • ACT_HI_PROCINST:记录流程实例开始时间。
    • ACT_HI_ACTINST:记录“开始节点”已完成,“用户任务节点”已开始。
    • ACT_HI_TASKINST:记录生成的第一个任务的历史存根。
    • ACT_HI_VARINST:记录变量的历史快照。

start() 操作标志着流程实例的正式诞生。此时,数据库中形成了完整的运行时数据结构(Execution Tree + Variables + Task),且首个审批节点的办理人已能在其“待办任务”列表中查询到该记录。


六,领导查看待办列表

流程实例发起后,任务流转至审批节点。此时,系统需要精准地将“任务”分发到对应审批人的工作台中。本章阐述系统如何通过身份上下文,从引擎中检索出属于当前用户的活跃任务。

1. 交互入口与请求上下文

领导一登录后台管理系统,进入【我的待办】菜单。

  • 接口协议:前端发起 GET 请求至 /bpm/task/todo-page 接口。
  • 身份凭证:请求头(Header)中携带了 Authorization: Bearer {token}。这是后端识别“当前是谁在查任务”的唯一凭证,避免了在 URL 参数中显式传递 UserID 带来的安全隐患。
2. 后端上下文解析

后端接口接收请求后,并非直接调用引擎,而是先进行上下文处理:

  • 身份提取:通过安全框架(如 Spring Security)解析 Token,从 SecurityContextHolder 中获取当前登录用户的 ID(例如 User_Leader)。
  • 分页参数:接收前端传递的分页参数(如 pageNo=1, pageSize=10),以便应对大量待办数据的场景。
3. 引擎查询与底层 SQL 构建

后端 Service 层构建 Flowable 的 TaskQuery 对象,执行核心查询逻辑。代码层面的 taskService.createTaskQuery()... 会被引擎翻译为复杂的 SQL 语句,涉及对 运行时任务表 (ACT_RU_TASK)运行时变量表 (ACT_RU_VARIABLE) 的联合操作。

关键查询链式调用如下:

  • taskAssignee(userId)

    • SQL 映射WHERE ASSIGNEE_ = 'User_Leader'
    • 含义:精准匹配分配给当前用户的任务。
    • (注:实际业务中常用 taskCandidateOrAssigned 以涵盖“候选人”模式,但此处按您描述的直接指派模式,使用 assignee 即可)
  • active()

    • SQL 映射AND SUSPENSION_STATE_ = 1
    • 含义:仅查询激活状态的任务,过滤掉被挂起(Suspended)的任务。
  • includeProcessVariables()

    • SQL 映射LEFT JOIN ACT_RU_VARIABLE ...
    • 含义:这是一个关键的性能优化操作。它指示引擎在查询任务的同时,通过**预加载(Eager Loading)**的方式一次性拉取关联的流程变量(如请假原因、请假天数)。这避免了后续遍历任务列表时产生 “N+1” 次查询数据库的问题。
  • orderByTaskCreateTime().desc()

    • SQL 映射ORDER BY CREATE_TIME_ DESC
    • 含义:按任务创建时间倒序排列,确保最新的待办显示在最前。
4. 数据转换与响应

引擎返回查询结果(List<Task>)后,后端不会直接将 Flowable 的原生实体返回给前端(因为包含大量引擎内部字段),而是进行对象转换(DTO/VO):

  • 基础映射:提取 Task ID, Name, CreateTime 等基础字段。
  • 变量注入:从查询结果中提取 processVariables(如 {"Fkkg...": "看病", "Fl6y...": "3"}),并将其映射为前端可读的业务字段。
  • 流程定义关联:通常还会根据 PROC_DEF_ID_ 关联查询流程定义名称(“请假流程”),以便用户知道这是什么流程的任务。

最终,前端接收到包含业务数据的标准分页列表,渲染出领导看到的待办表格。


七,流转与驱动——领导审批待办任务

当领导对待办任务进行“通过”操作时,系统不仅要处理业务层面的审批意见与签名,还需驱动 Flowable 引擎完成核心的“节点跃迁”:销毁当前任务,移动执行指针,并生成下一个任务节点。

1. 审批动作与接口协议

领导在任务详情页点击【通过】按钮,若业务场景配置了签名或审批意见,需同时提交相关数据。

  • 接口调用:前端发送 PUT 请求至 /bpm/task/approve
  • 载荷参数
  • id: 任务唯一标识(如 d7d3cb54-f212...)。
  • reason: 审批意见(可选)。
  • signatureImage: 电子签名图片(取决于配置)。
  • nextAssignee: 指定的下一节点审批人(若流程配置为手动选择)。
2. 动态规则校验(基于 BPMN 扩展属性)

后端接收请求后,首先进行基础校验(任务是否存在、流程实例是否活跃)。随后,进入基于模型的动态校验逻辑

  • 获取节点定义:根据任务的 taskDefinitionKey,从内存缓存的 BpmnModel 中定位到对应的 FlowElement 对象。
  • 解析扩展属性:读取该节点配置的 extensionElements,检查是否存在自定义标签 signEnable
  • 强制校验:若 signEnable=true,系统强制检查请求参数中是否包含 signatureImage。若缺失,直接抛出业务异常,拦截后续操作。这一机制实现了“配置驱动校验”,无需硬编码。
3. 上下文准备:预设下一审批人

在触发流转前,系统需解决“下一棒交给谁”的问题。

  • 推理与设置:根据前端传递的 nextAssignee 或业务规则,确定下一节点的受托人。
  • 变量持久化:调用 runtimeService.setVariables(),将目标用户 ID 写入 ACT_RU_VARIABLE 表。
  • 目的:Flowable 引擎在创建下一个任务时,会通过 UEL 表达式(如 ${approver})读取此变量作为新任务的 Assignee。
4. 引擎核心流转 (taskService.complete)

校验与参数准备完成后,调用 taskService.complete(taskId)。这是一个高度封装的原子操作,底层数据库发生剧烈的级联变化:

  • A. 任务销毁与归档 (Current Task)

    • 物理删除:执行 DELETE 操作,从 ACT_RU_TASK 表中移除当前领导的待办记录。
    • 历史审计同步更新 ACT_HI_TASKINST 表,标记该任务状态为 completed,并记录 END_TIME_(结束时间)与 DURATION_(任务耗时)。
  • B. 路径计算与指针移动 (Navigation)

    • 读取模型:引擎从 Deployment Cache 中读取流程定义模型(无需每次查询数据库 XML),根据当前节点的“出口连线”计算目标节点。
    • 移动指针:更新 ACT_RU_EXECUTION 表。
    • 将当前执行流的 ACT_ID_ 字段由“领导审批节点 ID”更新为“下一个节点 ID”。
    • (注:若遭遇并行网关,此处会涉及执行流的分裂或聚合操作)
  • C. 下一任务生成 (Next Task)

    • 节点实例化:若目标节点仍为 UserTask,引擎向 ACT_RU_TASK 表执行 INSERT 操作,生成一条新的待办记录。
    • 属性填充:新记录的 TASK_DEF_KEY_ 指向新节点 ID,ASSIGNEE_ 字段根据第 3 步预设的变量自动填充。
    • 历史同步:同时向 ACT_HI_TASKINST 插入一条新记录(无结束时间),标志着下一环节审计追踪的开始。


Q&A


Task表的记录是在什么时候被插入的,用户任务的插入机制是什么

这个问题触及到了 Flowable 引擎的“自动执行”与“事务一致性”机制。

一、 核心场景:从“人工审批”流转到“自动服务”

假设流程图是这样的:
【用户任务 A (经理审批)】 --> 【服务任务 B (发送邮件)】 --> 【用户任务 C (总监审批)】

1. 触发点

你调用了 taskService.complete(Task_A)。这一瞬间,数据库事务(Transaction)开启。

2. 步骤推演

第一步:处理 Task A(起点)

  • 动作:销毁 Task A。
  • DB 操作DELETE FROM ACT_RU_TASK WHERE ID_ = 'Task_A'

第二步:遇到 Task B(系统节点)—— 关键时刻!

  • 引擎判断:引擎发现下一个节点是 ServiceTask(系统节点)。

  • ACT_RU_TASK 表完全无视。引擎不会向这张表插入任何记录。因为不需要人来处理,没必要贴“告示”。

  • 代码执行:引擎会在当前的 Java 线程里,直接实例化你配置的 JavaDelegate 类,并执行 .execute(execution) 方法。

  • 比如:发送一封 SMTP 邮件,或者更新一下 ERP 系统的数据库。

  • ACT_RU_EXECUTION 表

  • 指针短暂地变为 Task B 的 ID,执行完代码后,立即跳向下一个节点。

  • (在数据库里你甚至很难抓拍到它停在 Task B 的状态,因为太快了)

第三步:遇到 Task C(终点)

  • 引擎判断:发现 Task B 后面连着的是 UserTask(总监审批)。
  • ACT_RU_TASK 表插入新记录!
  • DB 操作INSERT INTO ACT_RU_TASK (ID_, NAME_='总监审批', ...)

第四步:事务提交

  • Commit:上述所有操作(删 A -> 跑代码 -> 插 C),都在同一个数据库事务里完成。

二、 深入理解:原子性与“连带责任”

这里有一个非常重要的“原子性”概念,很多初学者容易踩坑。

因为系统节点(ServiceTask)是不停车的,所以“Task A 完成” + “Task B 执行” + “Task C 创建”这三件事,在默认情况下是绑定在同一个事务里的。

后果 1:一荣俱荣,一损俱损

如果 Task B(发送邮件)的代码报错了(比如空指针异常、网络超时):

  • 回滚 (Rollback):整个事务回滚。

  • 现象

    1. Task C 没创建
    2. Task B 没执行完
    3. 最可怕的是:Task A 复活了!因为删除操作也回滚了。
  • 用户视角:经理点击了“通过”,转圈圈,然后报错“系统异常”。刷新页面一看,任务还在自己名下,仿佛刚才什么都没发生。

后果 2:没有“中间状态”

你永远无法在数据库里查到一条“正在执行发送邮件”的任务记录。

  • ACT_RU_TASK 表里查不到。
  • ACT_RU_EXECUTION 表里也基本查不到(因为它瞬间就过去了)。
  • 只有在 ACT_HI_ACTINST (历史活动表) 里,事后能查到它曾经来过(Start Time 和 End Time 几乎只有几毫秒之差)。

Task表的Assgnee是什么时候分配的,分配机制是什么

Assignee 的分配发生在 Task 记录插入的那一刻(即 INSERT 语句执行时),但值的来源可能在插入之前就已经准备好了。Assignee 的分配主要有三种情况,时间点略有不同:

情况 A:静态指定或表达式(最常见)
  • 配置:BPMN 中写死 assignee="zhangsan" 或使用表达式 assignee="${manager}"
  • 分配时机同步分配
    • 引擎在准备执行 INSERT ACT_RU_TASK 之前,会先解析 XML 里的 assignee 属性。
    • 如果是 ${manager},引擎会去查变量表 ACT_RU_VARIABLE 找到 manager 的值。
  • 入库状态:数据库里该条记录一生成,ASSIGNEE_ 字段就有值了。
情况 B:人工手动选择
  • 配置:BPMN 中配置 assignee="${flow_assignee_UserTask_01}"(动态变量)。

  • 前置动作

    • 在调用 taskService.complete() 之前,你的代码先执行了 runtimeService.setVariable(...),把“张三”存进了变量表。
  • 分配时机读取变量并分配

    • 引擎创建新 Task 时,读取这个变量,填入 ASSIGNEE_ 字段。
  • 入库状态:数据库记录生成时,字段已有值。

情况 C:先入池,后签收(Claim 模式)
  • 配置:BPMN 中没写 assignee,但配置了 candidateGroups="dept_finance"(候选组)。
  • 分配时机延迟分配
    • Step 1 (创建时)INSERT ACT_RU_TASK 时,ASSIGNEE_ 字段是 NULL。此时任务在“公海池”。
    • Step 2 (签收时):用户点击“签收”,调用 taskService.claim(taskId, "lisi")
    • Step 3 (更新):此时触发 UPDATE ACT_RU_TASK SET ASSIGNEE_='lisi' ...

流程定义表EXECUTION执行流的记录中(非根流程),ACT_ID_字段指向执行流中用户任务的TASK_DEF_KEY_。ACT_ID_的生命周期,它的创建修改销毁都是什么时候,是由什么操作触发的

以下是 ACT_ID_ 字段在数据库层面的完整生命周期(创建、修改、销毁)及其触发机制。我们将场景设定为:并行网关分支中的一个子执行流

一、 创建 (Creation)

时机:当流程流转遇到“并发分叉点”(如并行网关 Parallel Gateway)时。

  1. 触发操作

    • 流程到达并行网关,引擎发现有多个分支。
    • 引擎决定保留(或挂起)父执行流,并为每一个分支裂变(Fork)出新的子执行流。
  2. 数据库行为 (INSERT)

    • 引擎向 ACT_RU_EXECUTION 插入一条新记录。
    • ID_:生成新的 UUID(子执行流 ID)。
    • PARENT_ID_:指向根执行流。
    • ACT_ID_初始化为该分支上第一个节点的 ID

示例
网关后连接着 ID 为 audit_task 的用户任务。

  • SQL: INSERT INTO ACT_RU_EXECUTION (ID_, ACT_ID_, ...) VALUES ('child_1', 'audit_task', ...)
  • 此时,ACT_ID_ 的值就是 'audit_task'

二、 修改 (Modification)

时机:当任务完成,流程“迈向下一站”时。

这是最关键的阶段。ACT_ID_ 的修改代表了流程进度的推进。

场景 A:从“用户任务 A”流转到“用户任务 B” (UserTask -> UserTask)
  1. 触发操作

    • 后端调用 taskService.complete(taskId)
  2. 内存计算

    • 引擎计算出当前节点的出口连线,找到目标节点(用户任务 B,ID 为 approve_task)。
  3. 数据库行为 (UPDATE)

    • 引擎不会删除当前的执行流,而是直接修改它的指针位置。
    • SQL: UPDATE ACT_RU_EXECUTION SET ACT_ID_ = 'approve_task' WHERE ID_ = 'child_1'

注意ACT_ID_ 的值永远指向“当前停留”的节点。一旦任务完成,它就指向下一个了。

场景 B:穿过“自动服务节点” (UserTask -> ServiceTask -> UserTask)

这是一个特殊情况。

  1. 触发操作taskService.complete(taskA)

  2. 过程

    • 引擎先将内存中的 actId 更新为 ServiceTask 的 ID。
    • 但不提交事务(因为 ServiceTask 不停车)。
    • 立即执行 Java 代码。
    • 执行完后,内存中的 actId 再次更新为下一个 UserTask C 的 ID。
  3. 数据库行为

    • 在最终提交事务时,数据库里的 ACT_ID_直接从 A 跳变为 C
    • 现象:你几乎无法在数据库 ACT_RU_EXECUTION 表中看到 ACT_ID_ 指向 ServiceTask,因为它发生得太快且未持久化停车。

三、 销毁 (Destruction)

时机:当当前分支走完,或者流程结束时。

场景 A:分支汇聚 (Join)

当子执行流到达并行网关的汇聚点 (Join Gateway)

  1. 触发操作

    • 分支上的最后一个任务完成 taskService.complete()
    • 流程到达汇聚网关。
  2. 数据库行为 (DELETE)

    • 引擎判断:所有分支都到了吗?
      • 如果没到齐:当前子执行流会在网关处“停车”等待(ACT_ID_ 更新为网关 ID),或者被挂起。
      • 如果到齐了:引擎会合并执行流。通常会保留一个执行流作为主干,删除其他多余的子执行流。
    • SQL: DELETE FROM ACT_RU_EXECUTION WHERE ID_ = 'child_1'
场景 B:流程结束 (EndEvent)

当执行流到达结束节点 (EndEvent)

  1. 触发操作

    • 最后一个节点的任务完成。
  2. 数据库行为 (DELETE)

    • SQL: DELETE FROM ACT_RU_EXECUTION WHERE ID_ = 'child_1'
    • 如果是根流程结束,连同根执行流一起删除。

在EXECUTION表中两个字段非常像:ID_和PROC_INST_ID_,在ACT_RU_VARIABLE表中也有EXECUTION_ID_,PROC_INST_ID_两个字段。EXECUTION不就是执行流实例么,为什么要分为两个字段,他们的区别和联系是什么

一、 核心概念区分

1. PROC_INST_ID_ (Process Instance ID)
  • 含义流程实例 ID
  • 地位:它是**“根”**。代表了这一次业务请求(比如“张三的请假单”)。
  • 特性:无论流程内部怎么分叉、怎么循环,只要在这个单子里,所有产生的执行流记录,它们的 PROC_INST_ID_ 永远相同
  • 作用:用来标识“这是哪一单业务”。
2. ID_ (Execution ID)
  • 含义执行流 ID(主键)。
  • 地位:它是**“枝”(也包含根)。代表了引擎当前的“并发指针”**。
  • 特性
  • 如果是单线流程,只有一根主干,那么 ID_ = PROC_INST_ID_
  • 如果遇到并行网关,流程会裂变出多个“子执行流”。此时,子执行流会有自己独立的 ID_,但它们的 PROC_INST_ID_ 依然指向同一个根。

二、 实例演示: 并行

流程图:开始 -> 并行网关 -> (分支1: 经理审批 / 分支2: 总监审批) -> 汇聚

当流程运行到并行网关,发生分叉时,ACT_RU_EXECUTION 表里会同时存在 3条记录

记录类型 ID_ (主键) PROC_INST_ID_ (归属) PARENT_ID_ (父级) 含义
根执行流 1001 1001 NULL 流程实例本身。它此时处于“等待/Scope”状态,作为容器管理全局变量。
子执行流 A 1002 1001 1001 分支1的指针。正停在“经理审批”节点。
子执行流 B 1003 1001 1001 分支2的指针。正停在“总监审批”节点。

结论

  • PROC_INST_ID_ (1001):告诉我们这三条记录都属于“张三的请假单”。
  • ID_ (1002/1003):告诉我们当前有两个并行的任务在处理,互不干扰。

三、 为什么 ACT_RU_VARIABLE 表也要有两个字段?

在变量表中,EXECUTION_ID_PROC_INST_ID_ 的区别对应了 “变量的作用域 (Scope)”

1. 全局变量 (Global Variable)
  • 场景:请假天数、请假原因。所有节点都能看到。

  • 存储方式

    • PROC_INST_ID_: 1001
    • EXECUTION_ID_: 1001 (指向根执行流)
  • 含义:这个变量绑在主干上,所有树枝都能读到。

2. 局部变量 (Local Variable)
  • 场景

    • 多实例会签:在这个分支里,approver 是张三;在那个分支里,approver 是李四。变量名一样,但值不同,且互不干扰。
    • 并行分支:分支 A 需要一个临时变量 temp_score,分支 B 不需要。
  • 存储方式

    • PROC_INST_ID_: 1001 (依然属于这个单子)
    • EXECUTION_ID_: 1002 (指向子执行流 A)
  • 含义:这个变量只绑在“树枝 A”上。树枝 B(ID=1003)完全看不到它。


ACT_RU_VARIABLE表都存储什么,作用域是什么,变量的生命周期是什么

当流程实例在运行时,所有动态产生的业务数据(比如请假天数、审批结果、表单填写的内容、临时计算的标志位)都暂存在这张表里。
它是一个高度灵活的Key-Value(键值对)存储结构。

一、 它存什么?(数据结构与存储策略)

这张表的设计非常通用,为了能存下 Java 中的各种数据类型,它采用了一种“类型分发 + 多列存储”的策略。

1. 核心字段解析
  • NAME_: 变量名(Key)。比如 "days", "reason", "approver".
  • TYPE_: 变量类型。Flowable 支持多种类型,引擎根据这个字段决定去读哪一列的数据。
  • string (字符串)
  • integer / long / double (数字)
  • boolean (布尔)
  • date (日期)
  • serializable (序列化对象,最复杂的一种)
2. 值存在哪里?(多列复用)

根据 TYPE_ 的不同,真正的值会被存放在不同的列中:

  • TEXT_: 存字符串(string)。
  • LONG_: 存整数(integer, long)或日期的时间戳(date)或布尔值(0/1)。
  • DOUBLE_: 存浮点数(double)。
  • TEXT2_: 存长文本(JPA 实体等)。
  • BYTEARRAY_ID_: [关键] 存二进制流 ID。
  • 如果你存的是一个 Java Object(比如 Person 对象),引擎会把它序列化成字节流,存到 ACT_GE_BYTEARRAY 表中,然后在这里记录那个 ByteArray 的 ID。

二、 它的作用域 (Scope)

这张表里的数据并非都是“全局可见”的,它通过外键来实现数据隔离

  1. 全局变量 (Global)
  • 特征PROC_INST_ID_ 有值,TASK_ID_ 为空。
  • 场景:请假天数、发起人姓名。
  • 作用:整个流程实例,从头到尾,任何节点都能读写它。
  1. 本地/局部变量 (Local)
  • 特征

  • 执行流局部EXECUTION_ID_ 指向某个子分支。只有该分支能看。

  • 任务局部TASK_ID_ 有值。

  • 场景:审批节点 A 的临时备注。

  • 作用:一旦这个任务完成,或者分支合并,这个变量就消失了,不会污染全局环境。

三、 生命周期 (Lifecycle)

这是你最关心的部分。ACT_RU_VARIABLE 的生命周期与流程实例的生命周期强绑定

1. 创建

变量是在流程运行过程中动态插入的。

  • 场景 A:流程启动时

    runtimeService.startProcessInstanceByKey("leave", variablesMap);
    
    • 动作:引擎在创建 EXECUTION 后,立刻遍历 Map,执行 INSERT INTO ACT_RU_VARIABLE
  • 场景 B:任务完成时

    taskService.complete(taskId, variablesMap);
    
    • 动作:将 Map 里的数据插入或更新到表中。
  • 场景 C:代码显式设置

    runtimeService.setVariable(executionId, "score", 100);
    
    • 动作:即时插入。
2. 存活

只要流程还没结束,这些数据就一直存在于表中。

  • 修改:如果你再次调用 setVariable("days", 5),引擎会执行 UPDATE 操作,覆盖旧值。
  • 查询:当你调用 taskService.getVariables(taskId) 时,引擎就是来查这张表。
3. 销毁

这是关键点:当流程结束(EndEvent)那一瞬间,这张表里的相关数据会被彻底清空!

  • 触发:流程走到结束节点,或者被人工强制删除。
  • 动作DELETE FROM ACT_RU_VARIABLE WHERE PROC_INST_ID_ = ...
  • 归宿:数据去哪了?
  • 在删除之前,引擎会将它们搬家到历史表 ACT_HI_VARINST
  • Runtime 表 (RU):保持轻量级,只存正在跑的数据,跑完就删,保证查询快。
  • History 表 (HI):存全量数据,用于日后审计(比如查“张三去年的请假单当时填了几天”)。

这是为您重写的博客段落。我们将抛弃截图,通过构建一个完整的“公司采购申请流程”业务场景,来模拟数据库中 ACT_RU_VARIABLE 表的真实数据形态。

四、 实例讲解

我们假设有一个 “IT设备采购流程”,当前运行到了 “技术经理审批” 节点。流程实例 ID 为 1001,当前任务 ID 为 Task-50

此时,数据库表中会存在以下四类典型数据:

1. 业务表单数据(标准键值对)

这是用户在前端表单中显式填写的内容。引擎通常将其存储为简单的字符串或数字。

  • 场景:员工申请购买一台电脑,价格 15000 元。
  • 数据库记录
NAME_ (变量名) TYPE_ (类型) TEXT_ (文本值) LONG_ (数值)
itemName string “MacBook Pro” NULL
price long NULL 15000
  • 技术解析
    • 引擎根据 TYPE_ 字段决定去读哪一列。
    • string 类型读取 TEXT_ 列。
    • integerlong 类型读取 LONG_ 列。这些数据是全局可见的,绑定在 PROC_INST_ID_ = 1001 上,后续的财务审批节点依然可以读取到价格进行校验。
2. 逻辑控制变量(系统隐式数据)

这类数据用户看不见,是后端为了控制流程走向而注入的“信号灯”。

  • 场景:流程图中定义了一个排他网关:如果 price > 5000 则走总监审批,否则直接走财务。
  • 数据库记录
NAME_ TYPE_ TEXT_ LONG_
process_status integer NULL 1 (审批中)
initiator string “zhangsan” NULL
  • 技术解析
    • process_status 是开发人员为了方便查询而自定义的状态码。
    • initiator 是引擎自动记录的发起人,用于后续节点判断“谁发起的”。这些变量同样是全局范围
3. 复杂对象数据(序列化存储)

当业务需要存储 Java 对象(如 List、Map 或自定义 POJO)时,这张表无法直接存下列,而是通过“外键”指向二进制表。

  • 场景:采购申请中包含一个详细的配件清单(鼠标、键盘、支架),后端将其封装为 List<String> accessories 对象。
  • 数据库记录
NAME_ TYPE_ TEXT_ BYTEARRAY_ID_
accessories serializable NULL Byte-999
  • 技术解析
    • TYPE_serializable
    • 引擎将 Java List 序列化为二进制流,存入 ACT_GE_BYTEARRAY 表(ID为 Byte-999)。
    • 本表只存储引用 ID。当代码调用 getVariable("accessories") 时,引擎会自动反序列化还原回 Java List 对象。
4. 任务局部变量(Local Scope)

这是最特殊的一类数据,它的生命周期极短,且作用域严格受限。

  • 场景:技术经理在审批时,认为配置过高,给了一个临时评分 score=80,这个评分只属于当前审批环节,不需要传递给后续的财务。
  • 数据库记录
NAME_ TASK_ID_ PROC_INST_ID_ LONG_
score Task-50 1001 80
  • 技术解析
    • 关键差异:注意 TASK_ID_ 字段有值(指向当前任务 Task-50)。
    • 而上述前三类数据的 TASK_ID_ 字段均为 NULL
    • 这意味着变量 score 仅在 Task-50 存活期间有效。一旦经理点击“完成”,这条记录会被立即物理删除,不会随流程流转。

在工作流flowable中,除了flowable自带的表之外,有些项目还定义了其它的扩展表:1.bpm_form:id,name,conf,fields…;2.bpm_process_definition_info:id,process_definition_id,model_id,form_conf,form_fields…。这些扩展表是怎么和flowable自带的表联系起来的,为什么要扩展表

ACT_RE_MODEL的META_INFO字段记录了流程模型对应的表单的formid,与bpm_form关联;bpm_process_definition_info通过process_definition_id,model_id来与ACT_RE_PROCDEF,ACT_RE_MODEL表关联。之所以要扩展这两个表,是因为原生的flowable的表单数据要写在META_INFO_里,且不支持表单业务与模型业务的解耦。因此yudao设计了一个bpm_form表来存储设计好的表单,并用META_INFO来关联表单ID。而flowable原生ACT_RU_EXECUTION也没有表单相关字段,因此扩展了bpm_process_definition_info表实现表单与流程定义的绑定


用户/管理员取消流程实例,后端发生了什么

第一步:标记流程实例状态 (Process Instance Level)

1. 代码逻辑

// 设置流程实例级别的状态为 CANCEL
runtimeService.setVariable(id, BpmnVariableConstants.PROCESS_INSTANCE_VARIABLE_STATUS,
        BpmProcessInstanceStatusEnum.CANCEL.getStatus());
// 设置流程实例级别的取消原因
runtimeService.setVariable(id, BpmnVariableConstants.PROCESS_INSTANCE_VARIABLE_REASON, reason);

// 递归查找并处理子流程
List<ProcessInstance> childProcessInstances = runtimeService.createProcessInstanceQuery()
        .superProcessInstanceId(id).list();
childProcessInstances.forEach(...) // 递归调用自己

2. 核心含义
这一步是给整个“案件”定性。不管是主流程还是子流程,都在它们的档案袋上盖一个红章:“已取消”。

3. 数据库操作

  • 操作表ACT_RU_VARIABLE (运行时变量表)
  • 操作动作INSERT (如果变量不存在) 或 UPDATE (如果变量已存在)
  • 关键字段
    • PROC_INST_ID_ = 当前流程实例 ID (id)
    • NAME_ = "bpm_process_instance_status" / "bpm_process_instance_reason"
    • LONG_ / TEXT_ = 状态码 / 原因内容
  • 注意TASK_ID_ 字段为 NULL (因为这是流程全局变量)。

第二步:标记运行时任务状态 (Task Level)

1. 代码逻辑

// 获取当前流程实例下所有“活着”的任务
List<Task> taskList = getRunningTaskListByProcessInstanceId(processInstanceId, null, null);

taskList.forEach(task -> {
    // 防御性编程:如果任务已经是结束状态(并发场景可能发生),跳过
    Integer otherTaskStatus = (Integer) task.getTaskLocalVariables().get(...);
    if (BpmTaskStatusEnum.isEndStatus(otherTaskStatus)) return;
    
    // 【核心】调用 processTaskCanceled,内部本质是 taskService.setVariableLocal
    processTaskCanceled(task.getId());
});

2. 核心含义
这一步是给当前正在办理任务的“办事员”留遗言。
因为下一步我们就要强制杀死这些任务,如果不在这里把“取消原因”写进任务的变量里,等任务被删了,历史记录里就不知道这个任务为什么没做完就消失了。

3. 数据库操作

  • 操作表ACT_RU_VARIABLE (运行时变量表)
  • 操作动作INSERT
  • 关键字段
    • TASK_ID_ = 当前循环到的任务 ID (task.getId()) —— 这是关键,通过此字段绑定到具体任务
    • NAME_ = "bpm_task_status" / "bpm_task_reason"
    • LONG_ / TEXT_ = CANCEL (取消状态) / "系统自动取消..."

第三步:物理终止流程 (Engine Level)

1. 代码逻辑

// 1. 获取模型和结束节点
BpmnModel bpmnModel = modelService.getBpmnModelByDefinitionId(...);
EndEvent endEvent = BpmnModelUtils.getEndEvent(bpmnModel);

// 2. 获取当前所有停留节点的 Key (例如 ["UserTask_1", "UserTask_2"])
List<String> activityIds = ...;

// 3. 【核武器】执行跳转:把所有指针从当前位置,瞬间移到 EndEvent
runtimeService.createChangeActivityStateBuilder()
        .processInstanceId(processInstanceId)
        .moveActivityIdsToSingleActivityId(activityIds, endEvent.getId())
        .changeState();

2. 核心含义
这是对引擎下达的“死刑命令”。
引擎会将执行流(Execution)强制跳转到结束节点。由于到达了结束节点,引擎会自动触发“流程结束结算”流程:清理运行时数据,归档历史数据。

3. 数据库操作 (级联发生)

这一步的操作非常剧烈,涉及多张表的联动:

  • A. 运行时数据清理 (Runtime - RU)

  • ACT_RU_EXECUTION: DELETE

    • 因为跳转到了 EndEvent,流程实例判定结束,引擎删除该实例的所有执行流记录。
  • ACT_RU_TASK: DELETE

    • 当前活着的任务被物理删除。
  • ACT_RU_VARIABLE: DELETE

    • 第一步和第二步插入的所有变量,在这里会被物理删除(但别怕,看下面)。
  • B. 历史数据归档 (History - HI)

  • ACT_HI_PROCINST: UPDATE

    • 更新 END_TIME_ (结束时间),DELETE_REASON_ (通常记录为 ChangeActivityState)。
  • ACT_HI_TASKINST: UPDATE

    • 找到刚才删除的任务,更新 END_TIME_,标记为已完结/已删除。
  • ACT_HI_VARINST (历史变量表): INSERT

    • 这是最重要的一点! 引擎在删除 ACT_RU_VARIABLE 之前,会把里面所有数据(包括我们在第一步、第二步存的 CANCEL 状态和原因)搬运到这张历史表中。

为什么物理终止流程changeState()前,要先在VARIABLE表中插入流程实例和任务的STATUS和REASON

查询“我的流程”状态时,会同步查询 历史流程表(确定流程是否结束)和 历史变量表(确定业务状态 STATUS 和 REASON)。
如果没有往 VARIABLE 中插入记录就直接终止流程:

  1. 用户会看到对应的流程实例:显示为 “正常完成”(或“审批通过”)。
    • 因为引擎只知道流程“走到了终点”,不知道是被“强制取消”的,导致严重的业务误判。
  2. 对应的任务会:显示为 “不明原因结束”(或“已完成”)。
    • 历史记录里查不到该任务的取消标记,用户无法区分这是自己审批过的,还是被系统强制掐断的。

什么时候,记录会被放到ACT_HI_表中

很多人误以为“历史表”是等流程结束后才把数据“搬”过去的。
大错特错!
真相是: ACT_HI_... 表的数据是伴随着运行表 ACT_RU_... 同步生成的,就像写日记一样,发生一件记一件,而不是等过完一辈子才一次性写回忆录。
只有一种情况是真正的“搬家”(Runtime 删了,只留 History),那就是任务完成流程结束的那一瞬间。
我们用一个**“张三请假”**的例子,分三个阶段带你把数据库看透

场景设定

  • 流程:请假流程 (leave_process)
  • 操作人:张三(申请人)、李四(审批人)
  • 历史级别AuditFull(默认配置,若设为 None 则不记录历史)

第一阶段:流程刚发起(Start)

动作:张三点击“提交申请”,流程启动,并自动进入第一个节点“经理审批”。

此时,数据是同时写入 Runtime 和 History 表的:

  1. 流程实例表

    • ACT_RU_EXECUTION: INSERT 一条记录(我活着)。
    • ACT_HI_PROCINST: INSERT 一条记录(我出生了)。
    • 状态START_TIME_ 有值,END_TIME_NULL
  2. 任务表(经理审批):

    • ACT_RU_TASK: INSERT 一条记录(李四有个待办)。
    • ACT_HI_TASKINST: INSERT 一条记录(历史记录里记一笔:李四产生了一个任务)。
    • 状态START_TIME_ 有值,END_TIME_NULL
  3. 变量表(请假原因):

    • ACT_RU_VARIABLE: INSERT reason = "生病".
    • ACT_HI_VARINST: INSERT reason = "生病".

结论:在这个阶段,你查 Runtime 表和 History 表,都能查到数据,而且是一模一样的。

第二阶段:修改变量(Update)

动作:在审批前,系统自动运行了一个 Listener,把请假天数从 3 天改成了 5 天。

  1. 变量表
  • ACT_RU_VARIABLE: UPDATE days 从 3 变 5。
  • ACT_HI_VARINST: UPDATE days 从 3 变 5(如果是 Full 级别,会有 ACT_HI_DETAIL 记录修改明细)。

结论:Runtime 表变了,History 表立刻同步更新。History 表并不是死数据,它是活的审计日志。

第三阶段:任务完成/流程结束(Complete/End)—— 关键分水岭

动作:李四点击“同意”,流程走到 EndEvent 结束。
这一刻,发生了真正的“断舍离”:

  1. 任务表

    • ACT_RU_TASK: DELETE! (李四的待办列表清空了)。
    • ACT_HI_TASKINST: UPDATE
    • 把这条记录的 END_TIME_ 填上当前时间。
    • DELETE_REASON_ 填上 completed
  2. 流程实例表

    • ACT_RU_EXECUTION: DELETE! (内存释放,引擎不再维护这个实例)。
    • ACT_HI_PROCINST: UPDATE
    • END_TIME_ 填上当前时间。
  3. 变量表

    • ACT_RU_VARIABLE: DELETE! (运行时不需要这些变量了)。
    • ACT_HI_VARINST: 不变 (或者更新最后一次的值)。数据永久保留。

审批不通过,后端经过了什么操作,数据库层面有什么操作

针对默认的“审批不通过(拒绝)并结束流程”逻辑,核心流程分为三步:任务打标 -> 实例打标 -> 物理终止

1. 核心代码摘要

// 1. 【任务层】标记当前任务为 REJECT (不通过)
updateTaskStatusAndReason(task.getId(), BpmTaskStatusEnum.REJECT.getStatus(), reqVO.getReason());
// 2. 【实例层】标记整个流程为 REJECT (不通过)
processInstanceService.updateProcessInstanceReject(instance, reqVO.getReason());
// 3. 【物理层】强制跳转到结束节点 (物理终止)
moveTaskToEnd(task.getProcessInstanceId(), ...);

2. 数据库操作详解

第一步:任务打标 (Task Level)

给当前这个任务贴上“不通过”的标签,以便历史记录查询。

  • 代码updateTaskStatusAndReason(task.getId(), ...)
  • 操作表ACT_RU_VARIABLE (运行时变量表)
  • 操作动作INSERT
  • 关键数据
    • TASK_ID_: 当前任务ID (绑定在任务上)
    • NAME_: bpm_task_status
    • TEXT_: REJECT
第二步:实例打标 (Instance Level)

给整个流程实例贴上“不通过”的标签,防止前端显示为“审批通过”。

  • 代码updateProcessInstanceReject(instance, ...)
  • 操作表ACT_RU_VARIABLE (运行时变量表)
  • 操作动作INSERT
  • 关键数据
    • PROC_INST_ID_: 当前流程ID (绑定在流程上,TASK_ID_为空)
    • NAME_: bpm_process_instance_status
    • TEXT_: REJECT
第三步:物理终止 (Physical Termination)

利用 Flowable 引擎能力,强制结束流程。

  • 代码moveTaskToEnd(...) -> changeState(...)
  • 操作表
    1. ACT_RU_EXECUTION (运行时执行表): DELETE

      • 指针跳转到 EndEvent,流程判定结束,删除执行流。
    2. ACT_RU_TASK (运行时任务表): DELETE

      • 当前任务被物理删除。
    3. ACT_HI_VARINST (历史变量表): INSERT

      • 关键点:引擎在删除 Runtime 变量前,会自动把前两步写入的 REJECT 状态搬运到这张历史表。
    4. ACT_HI_PROCINST (历史流程表): UPDATE

      • 更新 END_TIME_,标记流程已结束。
Logo

汇聚全球AI编程工具,助力开发者即刻编程。

更多推荐