2018年我讲的Activiti工作流引擎------从表结构理解流程引擎的运作
文章目录
一、工作流引擎不是黑盒,是几十张表在协作
2018年给同事讲Activiti。来听的人大多用了半年工作流引擎,知道怎么写流程、怎么设任务、怎么审批。但出了问题------"流程卡住了"、"任务没人收"、"变量丢了"------就不知道怎么排查。
我的讲法是:不讲API,讲表结构。因为Activiti的几乎所有运行时状态都落在数据库里------弄懂了表,就弄懂了整个引擎。
二、23张核心表的四层结构
Activiti的23张表按前缀分为四层:
ACT_RE_* 资源表(Repository)------存流程定义
ACT_RU_* 运行时表(Runtime)------存运行中的数据
ACT_HI_* 历史表(History)------存已完成的数据
ACT_ID_* 身份表(Identity)------存用户和角色
每一层有明确的职责,数据在四层之间流转------定义发布后生成运行时数据,任务完成后写入历史。
三、资源表------流程定义怎么存的
当你在流程设计器里画一个流程图,保存后,数据写入两张表:
ACT_RE_MODEL(流程定义表):
| 字段 | 含义 |
|---|---|
| ID_ | 编号 |
| NAME_ | 名称 |
| KEY_ | 关键字,启动流程时用这个 |
| EDITOR_SOURCE_VALUE_ID_ | 关联 ACT_GE_BYTEARRAY 的流程定义XML |
| EDITOR_SOURCE_EXTRA_VALUE_ID_ | 关联流程图片的二进制数据 |
ACT_GE_BYTEARRAY(二进制数据表):存储BPMN的XML和流程图的PNG。ID关联ACT_RE_MODEL,DEPLOYMENT_ID关联ACT_RE_PROCDEF。
发布流程后,生成 ACT_RE_PROCDEF(流程发布表):
| 字段 | 含义 |
|---|---|
| ID_ | 流程定义编号 |
| KEY_ | 启动流程用 |
| DEPLOYMENT_ID_ | 关联 ACT_GE_BYTEARRAY |
| VERSION_ | 版本号,每发布一次+1 |
同一个KEY可以有多个版本,启动流程永远取最新版本。这是"在线修改流程不中断旧流程"的机制------旧实例按旧版本跑完,新实例走新版本。
四、运行时表------一个流程实例正在跑的时候
启动流程后,数据进入运行时表:
ACT_RU_EXECUTION 运行的流程实例
ACT_RU_TASK 运行中的任务(用户任务)
ACT_RU_IDENTITYLINK 任务和参与者的关联(谁可以处理这个任务)
ACT_RU_VARIABLE 流程变量(实例级别的变量,如申请金额、审批意见)
一个流程启动后的运行时状态:
ACT_RU_EXECUTION: insid=1001, procDefKey=leave
→ ACT_RU_TASK: taskid=T1, assignee=张三(等待审批)
→ ACT_RU_VARIABLE: varName=days, varValue=3(请假天数)
→ ACT_RU_IDENTITYLINK: taskid=T1, userId=张三, type=assignee
任务完成后,ACT_RU_TASK 里这条记录被删除,同时写入 ACT_HI_TASKINST(历史)。这就是 "RU存活的数据,HI存死的数据"。
五、历史表------所有的"做过的事"
除了运行时表,每次操作几乎都同时写入历史表:
ACT_HI_PROCINST 历史流程实例(包含是否已完成)
ACT_HI_TASKINST 历史任务(谁、什么时间、做了什么)
ACT_HI_VARINST 历史变量(变量名、值、修改时间)
ACT_HI_IDENTITYLINK 历史参与者(谁参与了哪个任务)
查历史不用翻日志------直接查历史表。谁在什么时间审批了什么流程、写了什么意见、流程持续了多久------都在历史表里。这就是工作流引擎的"审计能力"------不是后来补的,是设计时就落到了表结构里。
六、身份表------用户和角色不是引擎自带的
ACT_ID_GROUP 角色表
ACT_ID_USER 用户表
ACT_ID_MEMBERSHIP 角色-用户关联表
Activiti本身不维护用户和角色数据。ACT_ID_GROUP 和 ACT_ID_USER 一般做成视图,指向系统自己的用户表和角色表。引擎只关心"当前任务的参与者是谁"------至于这个人从哪个表查出来的,引擎不关心。
七、流程变量------流程的"内存"
流程变量是Activiti最重要的概念之一。它解决了"流程在节点之间传递什么信息"的问题:
java
// 启动时设置
variables.put("applyUserId", "user001");
variables.put("days", 3);
// 完成任务时传递
taskService.complete(taskId, variables);
// 下一个任务读变量
String userId = (String) taskService.getVariable(taskId, "applyUserId");
变量存在 ACT_RU_VARIABLE 里,任务完成时移入 ACT_HI_VARINST。关键点:变量必须在前置任务中设置,后续任务才能读取 。如果审批时需要"请假天数"这个变量,必须在前面的"申请"任务中 put("days", days)。
八、自定义角色表和用户表------为什么做成视图
Activiti的 ACT_ID_GROUP 等表可以做成视图,关联系统自己的用户表。为什么要这样?
因为系统已经有了一套完整的用户-角色-权限体系。在Activiti里再建一套肯定重复。用视图把系统的用户和角色"映射"到Activiti的规则里------引擎只认 ACT_ID_USER.ID_ 这个字段的值,至于它指向哪个表的数据,不关心。
九、一个流程的完整生命周期
1. 发布流程(RE_MODEL → RE_PROCDEF)
2. 启动流程(创建 RU_EXECUTION、RU_TASK)
3. 拾取任务(RU_TASK.ASSIGNEE_ = userId)
4. 完成任务(RU_TASK → 删除,写入 HI_TASKINST)
5. 流转到下一个节点(创建新的 RU_TASK)
6. 流程结束(删除 RU_EXECUTION,写入 HI_PROCINST)
每一步都能在对应的表里找到数据。查问题的思路就是顺着表链走 :流程卡住了?查 ACT_RU_TASK 看当前任务是哪个。变量丢了?查 ACT_RU_VARIABLE 看前置任务有没有设置。历史找不到了?查 ACT_HI_PROCINST 看是否已结束。
十、结语
用Activiti的人大部分停留在API层面------taskService.complete() 一调就完了。但出了问题,API层面是看不到答案的。只有清楚表结构------知道数据在哪张表、哪个字段、什么时候写入------才能在问题发生时准确定位。
这份培训PPT的目的就是这个:让人从"用API操作引擎"变成"从数据库理解引擎"。API文档几个月看一次就够了,表结构理解了就再也不忘。