Flowable工作流实战:通过RuoYi-Vue-Pro的数据库表变化,彻底搞懂流程实例的生命周期
Flowable工作流深度解析:从数据库视角透视流程实例生命周期
1. 工作流引擎的数据视角价值
当我们点击"发起流程"或"审批通过"按钮时,系统背后究竟发生了什么?对于大多数开发者而言,工作流引擎往往被视为一个黑盒,我们只关心输入和输出,却忽略了中间的数据流转过程。这种认知方式在日常开发中或许足够,但当我们需要进行性能调优、流程监控或异常排查时,数据库层面的理解就显得尤为重要。
为什么数据库视角如此关键?
- 调试效率提升:当流程出现异常时,直接检查数据库表可以快速定位问题节点
- 性能优化依据:通过分析运行时表的数据量,可以识别流程瓶颈
- 自定义扩展基础:理解数据存储结构是开发流程监控功能的前提
- 数据一致性保障:在手动修复流程状态时,需要准确知道各表的关联关系
以RuoYi-Vue-Pro整合的Flowable为例,其数据库表结构可以分为四大类:
| 表前缀 | 存储内容 | 典型表举例 |
|---|---|---|
| ACT_RE_ | 静态流程定义 | ACT_RE_PROCDEF(流程定义表) |
| ACT_RU_ | 运行时数据 | ACT_RU_TASK(运行时任务表) |
| ACT_HI_ | 历史数据 | ACT_HI_PROCINST(历史流程实例表) |
| ACT_GE_ | 通用数据 | ACT_GE_BYTEARRAY(二进制资源表) |
2. 流程实例的完整生命周期
2.1 流程定义阶段
在流程实例创建前,需要先完成流程定义。这个阶段主要涉及以下数据库操作:
-- 创建流程模型 INSERT INTO ACT_RE_MODEL (ID_, NAME_, KEY_, CATEGORY_) VALUES ('modelId', '请假流程', 'leave_process', 'OA'); -- 保存流程图 INSERT INTO ACT_GE_BYTEARRAY (ID_, NAME_, BYTES_) VALUES ('diagramId', 'leave.bpmn', [BPMN文件二进制内容]); -- 部署流程 INSERT INTO ACT_RE_DEPLOYMENT (ID_, NAME_, DEPLOY_TIME_) VALUES ('deploymentId', '请假流程部署', NOW()); INSERT INTO ACT_RE_PROCDEF (ID_, KEY_, NAME_, DEPLOYMENT_ID_, RESOURCE_NAME_) VALUES ('processDefId', 'leave_process', '请假流程', 'deploymentId', 'leave.bpmn');关键字段解析:
ACT_RE_PROCDEF.VERSION_:流程定义版本号,每次部署相同KEY的流程时会自动递增ACT_RE_PROCDEF.SUSPENSION_STATE_:1表示激活,2表示挂起ACT_GE_BYTEARRAY.BYTES_:存储BPMN文件的二进制内容
2.2 流程实例创建
当用户发起流程时,系统会在多个表中创建记录:
// 伪代码展示流程启动时的数据库操作 void startProcessInstance(String processDefinitionKey, Map<String, Object> variables) { // 在历史表中创建流程实例记录 insertInto(ACT_HI_PROCINST, ID_, PROC_DEF_ID_, START_TIME_, BUSINESS_KEY_); // 创建运行时执行实例 insertInto(ACT_RU_EXECUTION, ID_, PROC_INST_ID_, PROC_DEF_ID_, ACT_ID_); // 创建第一个任务 insertInto(ACT_RU_TASK, ID_, NAME_, ASSIGNEE_, PROC_INST_ID_, PROC_DEF_ID_); // 存储流程变量 insertInto(ACT_RU_VARIABLE, ID_, NAME_, TYPE_, PROC_INST_ID_, TEXT_); // 在RuoYi的扩展表中记录业务信息 insertInto(BPM_PROCESS_INSTANCE_EXT, id, process_definition_id, status, start_user_id); }表数据关联示意图:
ACT_HI_PROCINST │ ├── ACT_RU_EXECUTION │ ├── ACT_RU_TASK │ └── ACT_RU_VARIABLE │ └── BPM_PROCESS_INSTANCE_EXT2.3 任务流转阶段
当审批人处理任务时,系统会执行以下数据库操作:
任务完成时:
- 更新
ACT_RU_TASK的ASSIGNEE_和CLAIM_TIME_ - 在
ACT_HI_TASKINST中记录任务完成时间 - 创建新的运行时任务记录
- 更新
审批通过时:
-- 更新当前任务状态 UPDATE ACT_RU_TASK SET SUSPENSION_STATE_ = 2 WHERE ID_ = 'taskId'; -- 记录历史任务 UPDATE ACT_HI_TASKINST SET END_TIME_ = NOW(), DURATION_ = TIMESTAMPDIFF(SECOND, START_TIME_, NOW()) WHERE ID_ = 'taskId'; -- 创建新任务 INSERT INTO ACT_RU_TASK (ID_, NAME_, PROC_INST_ID_) VALUES ('newTaskId', 'HR审批', 'processInstanceId');审批驳回时:
- 在
ACT_HI_TASKINST中记录驳回原因 - 可能触发流程回退,创建新的任务节点
- 在
2.4 流程实例结束
流程结束时会清理运行时数据并更新历史记录:
-- 更新流程实例结束时间 UPDATE ACT_HI_PROCINST SET END_TIME_ = NOW(), DURATION_ = TIMESTAMPDIFF(SECOND, START_TIME_, NOW()), DELETE_REASON_ = 'completed' WHERE ID_ = 'processInstanceId'; -- 删除运行时数据 DELETE FROM ACT_RU_TASK WHERE PROC_INST_ID_ = 'processInstanceId'; DELETE FROM ACT_RU_EXECUTION WHERE PROC_INST_ID_ = 'processInstanceId'; -- 在扩展表中记录结果 UPDATE BPM_PROCESS_INSTANCE_EXT SET status = 2, result = 1, end_time = NOW() WHERE id = 'businessId';3. 关键表深度解析
3.1 运行时核心表
ACT_RU_TASK表结构:
| 字段 | 类型 | 说明 |
|---|---|---|
| ID_ | varchar | 主键 |
| PROC_INST_ID_ | varchar | 所属流程实例ID |
| NAME_ | varchar | 任务名称 |
| ASSIGNEE_ | varchar | 任务处理人 |
| CREATE_TIME_ | datetime | 创建时间 |
| SUSPENSION_STATE_ | int | 1-激活,2-挂起 |
ACT_RU_EXECUTION表关键状态:
IS_ACTIVE_:1表示当前有活跃任务IS_SCOPE_:标识是否为流程实例根节点
3.2 历史表数据分析
ACT_HI_PROCINST表字段解析:
SELECT ID_ AS 实例ID, PROC_DEF_ID_ AS 定义ID, START_TIME_ AS 开始时间, END_TIME_ AS 结束时间, DURATION_/1000 AS 耗时(秒), DELETE_REASON_ AS 结束原因 FROM ACT_HI_PROCINST WHERE END_TIME_ IS NOT NULL ORDER BY DURATION_ DESC LIMIT 10;这个查询可以帮助我们识别耗时最长的流程实例,用于性能分析。
3.3 RuoYi-Vue-Pro扩展表
BPM_TASK_EXT表增强功能:
- 记录审批意见和结果
- 存储自定义业务状态
- 增加创建人、更新时间等审计字段
// 典型的数据流转示例 taskService.complete(taskId, variables); bpmTaskExtMapper.updateByTaskId( new BpmTaskExtDO() .setTaskId(taskId) .setResult(approveResult) .setReason(comment) .setEndTime(LocalDateTime.now()) );4. 实战:构建流程监控看板
基于对表结构的理解,我们可以开发流程监控功能:
4.1 统计流程执行情况
-- 按状态统计流程实例 SELECT CASE WHEN END_TIME_ IS NULL THEN 'RUNNING' ELSE 'COMPLETED' END AS status, COUNT(*) AS count FROM ACT_HI_PROCINST GROUP BY status; -- 平均处理时间分析 SELECT t.NAME_ AS task_name, AVG(t.DURATION_/1000) AS avg_seconds, COUNT(*) AS task_count FROM ACT_HI_TASKINST t WHERE t.END_TIME_ IS NOT NULL GROUP BY t.NAME_;4.2 异常流程检测
// 检测长时间运行的任务 public List<Task> findLongRunningTasks(Duration threshold) { return taskService.createTaskQuery() .active() .list() .stream() .filter(task -> { long duration = System.currentTimeMillis() - task.getCreateTime().getTime(); return duration > threshold.toMillis(); }) .collect(Collectors.toList()); } // 检测被多次驳回的流程 public List<ProcessInstance> findFrequentlyRejectedProcesses(int rejectLimit) { String sql = "SELECT PROC_INST_ID_ FROM ACT_HI_TASKINST " + "WHERE DELETE_REASON_ LIKE '%reject%' " + "GROUP BY PROC_INST_ID_ HAVING COUNT(*) > ?"; return historyService.createNativeProcessInstanceQuery() .sql(sql) .parameter(rejectLimit) .list(); }4.3 可视化建议
基于收集的数据,可以构建以下监控视图:
- 流程耗时分布图:直方图展示不同流程的执行时间分布
- 任务处理时间热力图:按审批人展示任务处理效率
- 流程瓶颈分析:识别最常出现延迟的任务节点
- 异常流程看板:集中展示所有异常状态的流程实例
5. 性能优化实践
5.1 数据库索引建议
-- 为常用查询字段添加索引 CREATE INDEX IDX_HI_PRO_INST_END ON ACT_HI_PROCINST(END_TIME_); CREATE INDEX IDX_RU_TASK_PROCINST ON ACT_RU_TASK(PROC_INST_ID_); CREATE INDEX IDX_HI_TASK_PROCINST ON ACT_HI_TASKINST(PROC_INST_ID_);5.2 历史数据归档策略
// 示例归档逻辑 public void archiveCompletedProcesses(LocalDateTime beforeDate) { // 查询符合条件的流程实例 List<HistoricProcessInstance> instances = historyService.createHistoricProcessInstanceQuery() .finishedBefore(Date.from(beforeDate.atZone(ZoneId.systemDefault()).toInstant())) .list(); // 归档到历史表 instances.forEach(instance -> { archiveService.archiveProcessInstance(instance.getId()); runtimeService.deleteProcessInstance(instance.getId(), "archived"); }); }5.3 批量操作优化
当处理大批量流程数据时,应注意:
- 使用分页查询避免内存溢出
- 批量提交数据库操作
- 关闭不必要的历史记录
# application.yml配置示例 flowable: async-executor-activate: true async-history-enabled: true history-level: audit6. 常见问题排查指南
6.1 流程卡住排查步骤
查询流程实例当前状态:
SELECT * FROM ACT_RU_EXECUTION WHERE PROC_INST_ID_ = '流程实例ID';检查是否有活跃任务:
SELECT * FROM ACT_RU_TASK WHERE PROC_INST_ID_ = '流程实例ID';查看流程变量是否正确:
SELECT * FROM ACT_RU_VARIABLE WHERE PROC_INST_ID_ = '流程实例ID';
6.2 任务分配异常处理
当任务未正确分配时,检查:
ACT_RU_IDENTITYLINK表中是否存在对应关系候选人组设置是否正确:
SELECT * FROM ACT_RU_IDENTITYLINK WHERE TASK_ID_ = '任务ID';动态分配表达式是否评估正确
6.3 数据一致性修复
当需要手动修复数据时,建议操作顺序:
- 暂停相关流程实例
- 按照
ACT_RU_TASK→ACT_RU_EXECUTION→ACT_RU_VARIABLE的顺序更新数据 - 记录变更日志
- 恢复流程运行
-- 示例:修改任务处理人 UPDATE ACT_RU_TASK SET ASSIGNEE_ = '新处理人' WHERE ID_ = '任务ID'; -- 同步更新身份关联表 UPDATE ACT_RU_IDENTITYLINK SET USER_ID_ = '新处理人' WHERE TASK_ID_ = '任务ID' AND TYPE_ = 'assignee';