全国免费咨询:

13245491521

VR图标白色 VR图标黑色
X

中高端软件定制开发服务商

与我们取得联系

13245491521     13245491521

2022-11-28_Flowable 中的网关、流程变量以及历史流程

您的位置:首页 >> 新闻 >> 行业资讯

Flowable 中的网关、流程变量以及历史流程 1. 三大网关 1.1. 排他网关 1.2. 并行网关 1.3. 包容网关 2. 四种变量设置方式 2.1. 为什么需要流程变量 2.2. 流程变量的分类 2.3. 全局流程变量 2.4. 本地流程变量 2.5. 临时流程变量 3. 历史流程 3.1. 历史流程信息 3.2. 历史任务查询 3.3. 历史活动查询 3.4. 历史变量查询 3.5. 历史日志查询 3.6. 历史权限查询 3.7. 自定义查询 SQL 3.8. 历史数据记录级别 本文为稀土掘金技术社区首发签约文章,14天内禁止转载,14天后未获授权禁止转载,侵权必究! 本专栏第四篇已发布,尚未看过的小伙伴请移步这里: Flowable 开篇,流程引擎扫盲通过 Flowable-UI 来体验一把 Flowable 流程引擎搞懂 Flowable 中的流程定义和流程实例梳理流程引擎 Flowable 四大常见任务今天这篇文章,松哥和大家梳理一下 Flowable 中的网关、流程变量以及历史流程的玩法。 1. 三大网关Flowable 中网关类型其实也不少,常见的主要有三种类型,分别是: 排他网关并行网关包容网关这三个里边最常用的当然就是排他网关了,今天松哥就来和小伙伴们聊一聊这三种网关,一起来体验一把这三种网关各自的特征。 1.1. 排他网关首先就是排他网关了,这个也叫互斥网关,长得像下图这样: 排他网关可以有 N 个入口,但是只有一个有效出口。 松哥举一个例子: 假设我有一个请假流程,请假 1 天,组长审批,请假小于 3 天,项目经理审批,请假大于 3 天,总监审批,据此,我们可以绘制如下流程图: 在这个流程图中,当流程从排他网关出来的时候,我们设置一个变量,根据变量的值,来决定下一个走哪一个 Task,例如组长审批,我们做如下配置: 这个流条件表示当 days 这个变量的值小于等于 1 的时候,就会进入到组长审批这个 Task。 按照类似的方式,我们来设置经理审批: 最后,总监审批的条件如下: 最终,我们来看下这个流程对应的 XML 文件,如下: processid="demo01"name="测试流程"isExecutable="true" documentation测试流程/documentation startEventid="startEvent1"flowable:formFieldValidation="true"/startEvent exclusiveGatewayid="sid-C4E389D6-C507-4B8E-8469-2288AA5B44A5"/exclusiveGateway sequenceFlowid="sid-DF97CC8B-3AD5-447D-AE67-1082CAB7B189"sourceRef="startEvent1"targetRef="sid-C4E389D6-C507-4B8E-8469-2288AA5B44A5"/sequenceFlow userTaskid="sid-B4CD08AF-52B5-44F2-AC45-B2F5E154A5F0"name="组长审批"flowable:formFieldValidation="true"/userTask userTaskid="sid-07B7951C-4E76-4639-989C-407C610C5BA8"name="经理审批"flowable:formFieldValidation="true"/userTask userTaskid="sid-1A81B40F-D8D4-4158-B0B9-26DB8FB7DD2E"name="总监审批"flowable:formFieldValidation="true"/userTask endEventid="sid-0F56FE56-1A8C-4B47-8F0D-196700DDF7B8"/endEvent sequenceFlowid="sid-E4B4B580-F078-4BB9-B5D3-966E80737C4C"sourceRef="sid-B4CD08AF-52B5-44F2-AC45-B2F5E154A5F0"targetRef="sid-0F56FE56-1A8C-4B47-8F0D-196700DDF7B8"/sequenceFlow endEventid="sid-F05670CB-A8F4-44A3-B53D-46CFB6F65581"/endEvent sequenceFlowid="sid-3EC62E5D-ACDA-480E-93B4-C24D8F6E9042"sourceRef="sid-07B7951C-4E76-4639-989C-407C610C5BA8"targetRef="sid-F05670CB-A8F4-44A3-B53D-46CFB6F65581"/sequenceFlow endEventid="sid-52711414-1769-4EC3-9AE5-6BA426123095"/endEvent sequenceFlowid="sid-C81500B2-D1EA-429F-8402-A3D8C8CA0E29"sourceRef="sid-1A81B40F-D8D4-4158-B0B9-26DB8FB7DD2E"targetRef="sid-52711414-1769-4EC3-9AE5-6BA426123095"/sequenceFlow sequenceFlowid="sid-807C7B79-4AFA-4525-847F-4D0FE1C0F0F3"name="小于1天"sourceRef="sid-C4E389D6-C507-4B8E-8469-2288AA5B44A5"targetRef="sid-B4CD08AF-52B5-44F2-AC45-B2F5E154A5F0" conditionExpressionxsi:type="tFormalExpression"![CDATA[${days=1}]]/conditionExpression /sequenceFlow sequenceFlowid="sid-3D3DF742-BF47-4536-9EE9-747CD284A1BA"name="1-3天"sourceRef="sid-C4E389D6-C507-4B8E-8469-2288AA5B44A5"targetRef="sid-07B7951C-4E76-4639-989C-407C610C5BA8" conditionExpressionxsi:type="tFormalExpression"![CDATA[${days1days=3}]]/conditionExpression /sequenceFlow sequenceFlowid="sid-2AD41E43-AFEC-47A1-B8D1-0B4299434BF8"name="大于3天"sourceRef="sid-C4E389D6-C507-4B8E-8469-2288AA5B44A5"targetRef="sid-1A81B40F-D8D4-4158-B0B9-26DB8FB7DD2E" conditionExpressionxsi:type="tFormalExpression"![CDATA[${days3}]]/conditionExpression /sequenceFlow /process 可以看到,在 sequenceFlow 标签中,有一个 conditionExpression 标签,这个标签的内容就是具体的条件了。 现在,我们部署一下这个流程,然后按照如下方式来启动: @Test voidtest01(){ MapString,Objectvariables=newHashMap(); variables.put("days",3); ProcessInstancepi=runtimeService.startProcessInstanceByKey("demo01",variables); logger.info("id:{},activityId:{}",pi.getId(),pi.getActivityId()); } 注意,这个启动的时候,传入一个 days 变量,系统将来会根据这个变量来决定这个流程要走到哪一个 Task。流程启动成功之后,我们去观察ACT_RU_TASK表,就可以看到流程的执行是否和我们所预想的一致。 1.2. 并行网关并行网关,从名字上大概也能看出来,这种网关一般用在并行任务上,并行网关如下图: 并行网关一般是成对出现的,一个出现的并行网关用来分流,第二个出现的并行网关用来聚合。 我画一个简单的并行网关的例子,如下图: 小伙伴们看到,这是一个简化的生产笔记本的流程图,当屏幕和键盘都生产好之后,再进行组装,整个流程图中存在两个并行网关(成对出现)。 在这个流程图中,连接线上是不需要设置条件的(不同于拍他网关),这里即使你设置了条件,这个条件也是不会生效的。 我们来看下这个并行网关流程图对应的 XML 文件,如下: processid="demo01"name="测试流程"isExecutable="true" documentation测试流程/documentation startEventid="sid-4F7F76BA-526A-4D8C-B45A-02FC1C56CA47"flowable:formFieldValidation="true"/startEvent sequenceFlowid="sid-11130848-EA1F-458A-A45D-49CBC49428C8"sourceRef="sid-4F7F76BA-526A-4D8C-B45A-02FC1C56CA47"targetRef="sid-6D01D4BE-C475-4270-8745-92752EA2C038"/sequenceFlow parallelGatewayid="sid-6D01D4BE-C475-4270-8745-92752EA2C038"/parallelGateway userTaskid="sid-54DD6BFA-FE6C-4DE7-9038-3DEEAF85002C"name="生产屏幕"flowable:assignee="zhangsan"flowable:formFieldValidation="true" extensionElements modeler:initiator-can-completexmlns:modeler="http://flowable.org/modeler"![CDATA[false]]/modeler:initiator-can-complete /extensionElements /userTask sequenceFlowid="sid-8DD3383C-45D1-4EAF-9A22-702A5B9D0869"sourceRef="sid-6D01D4BE-C475-4270-8745-92752EA2C038"targetRef="sid-54DD6BFA-FE6C-4DE7-9038-3DEEAF85002C"/sequenceFlow userTaskid="sid-7797ED55-155F-4D17-8EA5-DE40434C421B"name="生产键盘"flowable:assignee="lisi"flowable:formFieldValidation="true" extensionElements modeler:initiator-can-completexmlns:modeler="http://flowable.org/modeler"![CDATA[false]]/modeler:initiator-can-complete /extensionElements /userTask sequenceFlowid="sid-6E992E8B-CF71-411D-B537-42FEDF4F4209"sourceRef="sid-6D01D4BE-C475-4270-8745-92752EA2C038"targetRef="sid-7797ED55-155F-4D17-8EA5-DE40434C421B"/sequenceFlow sequenceFlowid="sid-8DCA9516-FFED-4781-9ACC-530DC6E63755"sourceRef="sid-7797ED55-155F-4D17-8EA5-DE40434C421B"targetRef="sid-98D3C336-9AD9-4964-9CCB-496C850EE40F"/sequenceFlow sequenceFlowid="sid-EE80AE42-D021-4B9F-A91E-BD37C512EE65"sourceRef="sid-54DD6BFA-FE6C-4DE7-9038-3DEEAF85002C"targetRef="sid-98D3C336-9AD9-4964-9CCB-496C850EE40F"/sequenceFlow userTaskid="sid-4FFE361A-E2AF-4481-BACF-1E618E8C4A26"name="组装"flowable:assignee="javaboy"flowable:formFieldValidation="true" extensionElements modeler:initiator-can-completexmlns:modeler="http://flowable.org/modeler"![CDATA[false]]/modeler:initiator-can-complete /extensionElements /userTask sequenceFlowid="sid-8CABC6E8-E36A-4814-B897-817D4A9F231C"sourceRef="sid-98D3C336-9AD9-4964-9CCB-496C850EE40F"targetRef="sid-4FFE361A-E2AF-4481-BACF-1E618E8C4A26"/sequenceFlow endEventid="sid-BF02170B-8138-4867-AE01-E3B29505183D"/endEvent sequenceFlowid="sid-F72B2A15-913F-436E-8AD7-6A6FB190E197"sourceRef="sid-4FFE361A-E2AF-4481-BACF-1E618E8C4A26"targetRef="sid-BF02170B-8138-4867-AE01-E3B29505183D"/sequenceFlow parallelGatewayid="sid-98D3C336-9AD9-4964-9CCB-496C850EE40F"/parallelGateway /process 现在我们把这个流程部署并启动。 流程启动成功之后,我们发现在ACT_RU_TASK表中有两个需要执行的 Task,如下图: 这两个 Task,如果只执行掉其中一个,那么还剩下另外一个 Task,如果两个都执行了,那么你就会看到一个新的 Task,如下图(两个并行任务执行完成后,进入到下一个任务): 好啦,这就是并行网关。 1.3. 包容网关包容网关,有时候也叫相容网关、兼容网关等,如下图: 包容谁呢?包容排他网关和并行网关。也就是说,这种包容网关可以根据实际条件转为排他网关或者并行网关。 举个栗子: 假如说报销金额大于 500,zhangsan 审批,报销金额大于 1000,则需要 zhangsan 和 lisi 同时审批,且 zhangsan 和 lisi 审批无先后顺序。 据此,我绘制如下流程图: 在报销金额大于 500 上设置如下条件: 大于 1000 上设置如下条件: 接下来我们来部署好这个流程。 部署好之后,我们首先来启动流程,第一次启动的时候,我们设置报销金额为 666,如下: @Test voidtest01(){ MapString,Objectvariables=newHashMap(); variables.put("money",666); ProcessInstancepi=runtimeService.startProcessInstanceByKey("demo01",variables); logger.info("id:{},activityId:{}",pi.getId(),pi.getActivityId()); } 流程启动之后,我们在ACT_RU_TASK表中可以看到,该 zhangsan 审批了,如下: zhangsan 审批之后,就是 wangwu 审批了,我就不演示了。 假设我们启动流程的时候,报销金额为 2000,如下: @Test voidtest01(){ MapString,Objectvariables=newHashMap(); variables.put("money",2000); ProcessInstancepi=runtimeService.startProcessInstanceByKey("demo01",variables); logger.info("id:{},activityId:{}",pi.getId(),pi.getActivityId()); } 那么此时你就会看到,在ACT_RU_TASK表中,出现了两条记录,分别是 zhangsan 审批和 lisi 审批,此时这两个审批就是一个并行任务了: 接下来就按并行任务的模式来,这两个人都审批了,才会进入到 wangwu 审批。 这就是兼容网关的特点,即根据实际情况,会变成排他网关或者并行网关。 好啦,三种常见的网关就和小伙伴们分享完啦,感兴趣的小伙伴赶紧试一试吧~ 2. 四种变量设置方式 1. 三大网关 1.1. 排他网关 1.2. 并行网关 1.3. 包容网关 2. 四种变量设置方式 2.1. 为什么需要流程变量 2.2. 流程变量的分类 2.3. 全局流程变量 2.4. 本地流程变量 2.5. 临时流程变量 3. 历史流程 3.1. 历史流程信息 3.2. 历史任务查询 3.3. 历史活动查询 3.4. 历史变量查询 3.5. 历史日志查询 3.6. 历史权限查询 3.7. 自定义查询 SQL 3.8. 历史数据记录级别 在之前的文章中,松哥也有和小伙伴们使用过流程变量,然而没有和大家系统的梳理过流程变量的具体玩法以及它对应的数据表详情,今天我们就来看看 Flowable 中流程变量的详细玩法。 2.1. 为什么需要流程变量首先我们来看看为什么需要流程变量。 举一个简单的例子,假设我们有如下一个流程: 这是一个请假流程,那么谁请假、请几天、起始时间、请假理由等等,这些都需要说明,不然领导审批的依据是啥?那么如何传递这些数据,我们就需要流程变量。 2.2. 流程变量的分类整体上来说,目前流程变量可以分为三种类型: 全局流程变量:在整个流程执行期间,这个流程变量都是有效的。本地流程变量:这个只针对流程中某一个具体的 Task(任务)有效,这个任务执行完毕后,这个流程变量就失效了。临时流程变量:顾名思义就是临时的,这个不会存入到数据库中。在接下来的内容中,我会跟大家挨个介绍这些流程变量的用法。 2.3. 全局流程变量假设我们就是上面这个请假流程,我们一起来看下流程变量的设置和获取。 2.3.1 启动时设置第一种方式,就是我们可以在流程启动的时候,设置流程变量,如下: @Test voidtest01(){ MapString,Objectvariables=newHashMap(); variables.put("days",10); variables.put("reason","休息一下"); variables.put("startTime",newDate()); ProcessInstancepi=runtimeService.startProcessInstanceByKey("demo01",variables); logger.info("id:{},activityId:{}",pi.getId(),pi.getActivityId()); } 我们可以在启动的时候为流程设置变量,小伙伴们注意到,流程变量的 value 也可以是一个对象(不过这个对象要能够序列化,即实现了 Serializable 接口),然后在启动的时候传入这个变量即可。 我们在流程启动日志中搜索休息一下四个字,可以找到和流程变量相关的 SQL,一共有两条,如下: insertintoACT_HI_VARINST(ID_,PROC_INST_ID_,EXECUTION_ID_,TASK_ID_,NAME_,REV_,VAR_TYPE_,SCOPE_ID_,SUB_SCOPE_ID_,SCOPE_TYPE_,BYTEARRAY_ID_,DOUBLE_,LONG_,TEXT_,TEXT2_,CREATE_TIME_,LAST_UPDATED_TIME_)values(?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?),(?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?),(?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?) INSERTINTOACT_RU_VARIABLE(ID_,REV_,TYPE_,NAME_,PROC_INST_ID_,EXECUTION_ID_,TASK_ID_,SCOPE_ID_,SUB_SCOPE_ID_,SCOPE_TYPE_,BYTEARRAY_ID_,DOUBLE_,LONG_,TEXT_,TEXT2_)VALUES(?,1,?,?,?,?,?,?,?,?,?,?,?,?,?),(?,1,?,?,?,?,?,?,?,?,?,?,?,?,?),(?,1,?,?,?,?,?,?,?,?,?,?,?,?,?) 从标名称上大概就能看出来,ACT_HI_VARINST是存储流程执行的历史信息的,ACT_RU_VARIABLE则是保存流程运行时候的信息的。 我们打开ACT_RU_VARIABLE表来看一下: 从表中我们可以看到,每一个流程变量都有对应的流程实例 ID,这就说明这些流程变量是属于某一个流程实例的,所以我们可以按照如下方式来查询流程变量: @Test voidtest01(){ ListExecutionlist=runtimeService.createExecutionQuery().list(); for(Executionexecution:list){ Objectreason=runtimeService.getVariable(execution.getId(),"reason"); logger.info("reason:{}",reason); } } 对应的查询 SQL 如下: :==Preparing:select*fromACT_RU_VARIABLEWHEREEXECUTION_ID_=?ANDTASK_ID_isnullANDNAME_=? :==Parameters:6fdd2007-4c3a-11ed-aa7e-acde48001122(String),reason(String) :==Total:1 可以看到,这个就是去ACT_RU_VARIABLE表中进行查询,查询条件中包含了变量的名称。 当然,我们也可以直接查询某一个流程的所有变量,如下: @Test voidtest02(){ ListExecutionlist=runtimeService.createExecutionQuery().list(); for(Executionexecution:list){ MapString,Objectvariables=runtimeService.getVariables(execution.getId()); logger.info("variables:{}",variables); } } 这个对应的查询 SQL 如下: :==Preparing:select*fromACT_RU_VARIABLEWHEREEXECUTION_ID_=?ANDTASK_ID_isnull :==Parameters:6fdd2007-4c3a-11ed-aa7e-acde48001122(String) :==Total:3 可以看到,这个跟上面的那个差不多,只不过少了NAME_这个条件。 2.3.2 通过 Task 设置我们也可以在流程启动成功之后,再去设置流程变量,步骤如下: 首先启动一个流程: @Test voidtest01(){ ProcessInstancepi=runtimeService.startProcessInstanceByKey("demo01"); logger.info("id:{},activityId:{}",pi.getId(),pi.getActivityId()); } 然后设置流程变量: @Test voidtest03(){ Tasktask=taskService.createTaskQuery().singleResult(); taskService.setVariable(task.getId(),"days",10); MapString,Objectvariables=newHashMap(); variables.put("reason","休息一下"); variables.put("startTime",newDate()); taskService.setVariables(task.getId(),variables); } 查询到某一个 Task,然后设置流程变量,上面这段代码和小伙伴们演示了两种设置方式: 逐个设置直接设置一个 Map上面这个设置流程变量的方式,本质上还是往ACT_HI_VARINST和ACT_RU_VARIABLE表中插入数据。具体的 SQL 也和前面的一样,我就不贴出来了。 2.3.3 完成任务时设置也可以在完成一个任务的时候设置流程变量,如下: @Test voidtest04(){ Tasktask=taskService.createTaskQuery().singleResult(); MapString,Objectvariables=newHashMap(); variables.put("reason","休息一下"); variables.put("startTime",newDate()); variables.put("days",10); taskService.complete(task.getId(),variables); } 底层涉及到的 SQL 都跟前面一样,我就不赘述了。 2.3.4 通过流程设置由于是全局流程变量,所以我们也可以通过RuntimeService来进行设置,如下: @Test voidtest05(){ Executionexecution=runtimeService.createExecutionQuery().singleResult(); runtimeService.setVariable(execution.getId(),"days",10); MapString,Objectvariables=newHashMap(); variables.put("reason","休息一下"); variables.put("startTime",newDate()); runtimeService.setVariables(execution.getId(),variables); } 好啦,一共就是这四种方式。 2.4. 本地流程变量第三小节我们说的全局流程变量是和某一个具体的流程绑定的,而本地流程变量则不同,本地流程变量和某一个 Task 绑定。 2.4.1 通过 Task 设置假设我们启动流程之后,通过 Task 来设置一个本地流程变量,方式如下: @Test voidtest03(){ Tasktask=taskService.createTaskQuery().singleResult(); taskService.setVariableLocal(task.getId(),"days",10); MapString,Objectvariables=newHashMap(); variables.put("reason","休息一下"); variables.put("startTime",newDate()); taskService.setVariables(task.getId(),variables); } 上面这段代码中,我设置了一个本地变量,两个全局变量,设置完成后,我们去ACT_RU_VARIABLE表中来查看一下具体的效果。 大家看到,由于 days 是本地变量,所以它的 TASK_ID_ 有值,这个好理解,说明 days 这个变量和这个具体的 Task 是有关的。 此时如果我们完成这个 Task,代码如下: @Test voidtest06(){ Tasktask=taskService.createTaskQuery().singleResult(); taskService.complete(task.getId()); } 完成之后,再来查看ACT_RU_VARIABLE表,如下: 我们发现本地变量 days 已经没有了。因为上一个 Task 都已经执行完毕了,这个时候如果还是按照第三小节介绍的方式去查询变量,就查不到 days 了。此时如果需要查询到曾经的 days 变量,得去历史表中查询了,方式如下: @Test voidtest07(){ ProcessInstancepi=runtimeService.createProcessInstanceQuery().singleResult(); ListHistoricVariableInstancelist=historyService.createHistoricVariableInstanceQuery().processInstanceId(pi.getId()).list(); for(HistoricVariableInstancehvi:list){ logger.info("name:{},type:{},value:{}",hvi.getVariableName(),hvi.getVariableTypeName(),hvi.getValue()); } } 这是流程本地变量的特点,当然相关的方法还有好几个,这里列出来给小伙伴们参考: org.flowable.engine.TaskService#complete(java.lang.String, java.util.Mapjava.lang.String,java.lang.Object, boolean):在完成一个 Task 的时候,如果传递了变量,则可以通过第三个参数来控制这个变量是全局的还是本地的,true 表示这个变量是本地的。org.flowable.engine.RuntimeService#setVariableLocal:为某一个执行实例设置本地变量。org.flowable.engine.RuntimeService#setVariablesLocal:同上,批量设置。好啦,这就是本地流程变量。 2.5. 临时流程变量临时流程变量是不存数据库的,一般来说我们可以在启动流程或者完成任务的时候使用,用法如下: @Test voidtest21(){ MapString,Objectvariables=newHashMap(); variables.put("reason","休息一下"); variables.put("startTime",newDate()); ProcessInstancepi=runtimeService .createProcessInstanceBuilder() .transientVariable("days",10) .transientVariables(variables) .processDefinitionKey("demo01") .start(); logger.info("id:{},activityId:{}",pi.getId(),pi.getActivityId()); } 上面这段代码涉及到的流程变量就是临时流程变量,它是不会存入到数据库中的。 也可以在完成一个任务的时候设置临时变量,如下: @Test voidtest22(){ Tasktask=taskService.createTaskQuery().singleResult(); MapString,ObjecttransientVariables=newHashMap(); transientVariables.put("days",10); taskService.complete(task.getId(),null,transientVariables); } 这个临时变量也是不会存入到数据库中的。 好啦,关于流程变量,今天就和小伙伴们先说这么多~ 3. 历史流程 1. 三大网关 1.1. 排他网关 1.2. 并行网关 1.3. 包容网关 2. 四种变量设置方式 2.1. 为什么需要流程变量 2.2. 流程变量的分类 2.3. 全局流程变量 2.4. 本地流程变量 2.5. 临时流程变量 3. 历史流程 3.1. 历史流程信息 3.2. 历史任务查询 3.3. 历史活动查询 3.4. 历史变量查询 3.5. 历史日志查询 3.6. 历史权限查询 3.7. 自定义查询 SQL 3.8. 历史数据记录级别 在之前的文章中松哥和小伙伴们聊过,正在执行的流程信息是保存在以ACT_RU_为前缀的表中,执行完毕的流程信息则保存在以ACT_HI_为前缀的表中,也就是流程历史信息表,当然这个历史信息表继续细分的话,还有好多种,今天我们就来聊一聊这个话题。 假设我有如下一个流程: 当这个流程执行完毕后,以ACT_RU_为前缀的表中的数据均已清空,现在如果想查看刚刚执行过的流程信息,我们就得去以ACT_HI_为前缀的表中。 3.1. 历史流程信息历史流程信息查看,方式如下: @Test voidtest05(){ ListHistoricProcessInstancelist=historyService.createHistoricProcessInstanceQuery().finished().list(); for(HistoricProcessInstancehpi:list){ logger.info("name:{},startTime:{},endTime:{}",hpi.getName(),hpi.getStartTime(),hpi.getEndTime()); } } 调用的时候执行的finished()方法表示查询已经执行完毕的流程信息(从这里也可以看出,对于未执行完毕的流程信息也会保存在历史表中)。 我们来看下这个查询对应的 SQL,如下: SELECTRES.*,DEF.KEY_asPROC_DEF_KEY_,DEF.NAME_asPROC_DEF_NAME_,DEF.VERSION_asPROC_DEF_VERSION_,DEF.DEPLOYMENT_ID_asDEPLOYMENT_ID_fromACT_HI_PROCINSTRESleftouterjoinACT_RE_PROCDEFDEFonRES.PROC_DEF_ID_=DEF.ID_WHERERES.END_TIME_isnotNULLorderbyRES.ID_asc 从这个 SQL 中可以看到,这个查询本质上就是查询的ACT_HI_PROCINST表。如下图: 如果我们在查询的时候不限制流程是否执行完毕,那么我们的查询方法如下: @Test voidtest05(){ ListHistoricProcessInstancelist=historyService.createHistoricProcessInstanceQuery().list(); for(HistoricProcessInstancehpi:list){ logger.info("name:{},startTime:{},endTime:{}",hpi.getName(),hpi.getStartTime(),hpi.getEndTime()); } } 对应的查询 SQL 如下: SELECTRES.*,DEF.KEY_asPROC_DEF_KEY_,DEF.NAME_asPROC_DEF_NAME_,DEF.VERSION_asPROC_DEF_VERSION_,DEF.DEPLOYMENT_ID_asDEPLOYMENT_ID_fromACT_HI_PROCINSTRESleftouterjoinACT_RE_PROCDEFDEFonRES.PROC_DEF_ID_=DEF.ID_orderbyRES.ID_asc 和前面的 SQL 相比,后面的 SQL 少了WHERE RES.END_TIME_ is not NULL条件,也就是说,判断一个流程是否执行完毕,就看它的END_TIME_是否为空,不为空就表示流程已经执行结束了,为空就表示流程尚在执行中。 3.2. 历史任务查询刚刚我们查询的是历史流程,接下来我们来看下历史任务,也就是查询一个流程中执行过的 Task 信息,如下表示查询所有的历史流程任务: @Test voidtest06(){ ListHistoricTaskInstancelist=historyService.createHistoricTaskInstanceQuery().list(); for(HistoricTaskInstancehti:list){ logger.info("name:{},assignee:{},createTime:{},endTime:{}",hti.getName(),hti.getAssignee(),hti.getCreateTime(),hti.getEndTime()); } } 这个查询对应的 SQL 如下: SELECTRES.*fromACT_HI_TASKINSTRESorderbyRES.ID_asc 可以看到,历史任务表就是ACT_HI_TASKINST,如下图: 当然,这里还有很多其他的玩法,例如查询某一个流程已经执行完毕的历史任务,如下: @Test voidtest07(){ ListHistoricProcessInstanceinstanceList=historyService.createHistoricProcessInstanceQuery().list(); for(HistoricProcessInstancehpi:instanceList){ ListHistoricTaskInstancelist=historyService.createHistoricTaskInstanceQuery().processInstanceId(hpi.getId()).finished().list(); for(HistoricTaskInstancehti:list){ logger.info("name:{},assignee:{},createTime:{},endTime:{}",hti.getName(),hti.getAssignee(),hti.getCreateTime(),hti.getEndTime()); } } } 这个里边的查询历史任务的 SQL 如下: SELECTRES.*fromACT_HI_TASKINSTRESWHERERES.PROC_INST_ID_=?andRES.END_TIME_isnotnullorderbyRES.ID_asc 可以看到,跟前面相比,多了两个条件: 流程实例 ID流程结束时间不为 null从这里也可以看出来,这个 finish 方法的执行逻辑跟我们前面讲的是一样的。 3.3. 历史活动查询历史任务就是各种 Task,历史活动则包括跟多内容,像开始/结束节点,连线等等这些信息都算是活动,这个在之前的文章中松哥已经和大家介绍过了。 查询代码如下: @Test voidtest08(){ ListHistoricActivityInstancelist=historyService.createHistoricActivityInstanceQuery().list(); for(HistoricActivityInstancehai:list){ logger.info("name:{},startTime:{},assignee:{},type:{}",hai.getActivityName(),hai.getStartTime(),hai.getAssignee(),hai.getActivityType()); } } 这个查询对应的 SQL 如下: SELECTRES.*fromACT_HI_ACTINSTRESorderbyRES.ID_asc 可以看到,ACT_HI_ACTINST表中保存了历史活动信息。 3.4. 历史变量查询查询流程执行的历史变量,方式如下: @Test voidtest09(){ HistoricProcessInstancepi=historyService.createHistoricProcessInstanceQuery().singleResult(); ListHistoricVariableInstancelist=historyService.createHistoricVariableInstanceQuery().processInstanceId(pi.getId()).list(); for(HistoricVariableInstancehvi:list){ logger.info("name:{},type:{},value:{}",hvi.getVariableName(),hvi.getVariableTypeName(),hvi.getValue()); } } 这个查询对应的 SQL 如下: SELECTRES.*fromACT_HI_VARINSTRESWHERERES.PROC_INST_ID_=?orderbyRES.ID_asc 可以看到流程的历史变量信息保存在ACT_HI_VARINST表中。 3.5. 历史日志查询有的小伙伴看到日志这两个字可能会觉得奇怪,咦?流程执行还有日志吗?没听说过呀! 其实历史日志查询就是前面那几种的一个集大成者,用法如下: @Test voidtest10(){ HistoricProcessInstancepi=historyService.createHistoricProcessInstanceQuery().singleResult(); ProcessInstanceHistoryLoghistoryLog=historyService.createProcessInstanceHistoryLogQuery(pi.getId()) //包括历史活动 .includeActivities() //包括历史任务 .includeTasks() //包括历史变量 .includeVariables() .singleResult(); logger.info("id:{},startTime:{},endTime:{}",historyLog.getId(),historyLog.getStartTime(),historyLog.getEndTime()); ListHistoricDatahistoricData=historyLog.getHistoricData(); for(HistoricDatadata:historicData){ if(datainstanceofHistoricActivityInstance){ HistoricActivityInstancehai=(HistoricActivityInstance)data; logger.info("name:{},type:{}",hai.getActivityName(),hai.getActivityType()); } if(datainstanceofHistoricTaskInstance){ HistoricTaskInstancehti=(HistoricTaskInstance)data; logger.info("name:{},assignee:{}",hti.getName(),hti.getAssignee()); } if(datainstanceofHistoricVariableInstance){ HistoricVariableInstancehvi=(HistoricVariableInstance)data; logger.info("name:{},type:{},value:{}",hvi.getVariableName(),hvi.getVariableTypeName(),hvi.getValue()); } } } 这个里边,首先是查询基本的流程日志信息,这个本质上就是查询历史流程实例信息,对应的 SQL 如下: selectRES.*,DEF.KEY_asPROC_DEF_KEY_,DEF.NAME_asPROC_DEF_NAME_,DEF.VERSION_asPROC_DEF_VERSION_,DEF.DEPLOYMENT_ID_asDEPLOYMENT_ID_fromACT_HI_PROCINSTRESleftouterjoinACT_RE_PROCDEFDEFonRES.PROC_DEF_ID_=DEF.ID_wherePROC_INST_ID_=? 接下来我写了三个 include,每一个 include 都对应一句 SQL: includeActivities 对应的 SQL 如下: SELECTRES.*fromACT_HI_ACTINSTRESWHERERES.PROC_INST_ID_=?orderbyRES.ID_asc includeTasks 对应的 SQL 如下: SELECTRES.*fromACT_HI_TASKINSTRESWHERERES.PROC_INST_ID_=?orderbyRES.ID_asc includeVariables 对应的 SQL 如下: SELECTRES.*fromACT_HI_VARINSTRESWHERERES.PROC_INST_ID_=?orderbyRES.ID_asc 最终查询完成后,调用getHistoricData方法可以查看这些额外的数据,List 集合中存放的 HistoricData 也分为不同的类型: includeActivities 方法对应最终查询出来的类型是 HistoricActivityInstance。includeTasks 方法对应最终查询出来的类型是 HistoricTaskInstance。includeVariables 方法对应最终查询出来的类型是 HistoricVariableInstance。在遍历的时候通过类型判断去查看具体是哪一种变量类型。 综上,这个历史日志查询其实就是一个集大成者。 3.6. 历史权限查询这个是用来查询流程或者任务的处理人,例如查询流程的处理人,方式如下: @Test voidtest11(){ HistoricProcessInstancepi=historyService.createHistoricProcessInstanceQuery().singleResult(); ListHistoricIdentityLinklinks=historyService.getHistoricIdentityLinksForProcessInstance(pi.getId()); for(HistoricIdentityLinklink:links){ logger.info("userId:{}",link.getUserId()); } } 这个是查询流程对应的处理人,对应的 SQL 如下: select*fromACT_HI_IDENTITYLINKwherePROC_INST_ID_=? 如果想查询任务的处理人,对应的方式如下: @Test voidtest12(){ StringtaskName="提交请假申请"; HistoricTaskInstancehti=historyService.createHistoricTaskInstanceQuery().taskName(taskName).singleResult(); ListHistoricIdentityLinklinks=historyService.getHistoricIdentityLinksForTask(hti.getId()); for(HistoricIdentityLinklink:links){ logger.info("{}任务的处理人是{}",taskName,link.getUserId()); } } 这个查询对应的 SQL 如下: select*fromACT_HI_IDENTITYLINKwhereTASK_ID_=? 和前面的相比,其实就多了一个查询条件TASK_ID_。 3.7. 自定义查询 SQL和前面讲的很多查询类似,当我们弄懂了每一个历史查询的 API 操作的是哪一个数据表,就会发现,历史数据的查询,也可以自定义 SQL。 举个例子和小伙伴们看下,例如查询某一个流程已经执行完毕的历史任务: @Test voidtest13(){ ListHistoricProcessInstanceinstanceList=historyService.createHistoricProcessInstanceQuery().list(); for(HistoricProcessInstancehpi:instanceList){ ListHistoricTaskInstancelist=historyService.createNativeHistoricTaskInstanceQuery() .sql("SELECTRES.*fromACT_HI_TASKINSTRESWHERERES.PROC_INST_ID_=#{pid}andRES.END_TIME_isnotnullorderbyRES.ID_asc") .parameter("pid",hpi.getId()).list(); for(HistoricTaskInstancehti:list){ logger.info("name:{},assignee:{},createTime:{},endTime:{}",hti.getName(),hti.getAssignee(),hti.getCreateTime(),hti.getEndTime()); } } } flowable 底层是 MyBatis,所有 SQL 中参数的传递形式和 MyBatis 一致。 3.8. 历史数据记录级别Flowable 需要记录哪些历史数据,有一个日志级别用来描述这个事情,默认有四种级别: None: 这个表示不存储任何历史信息,好处是流程执行的时候效率会比较快,坏处是流程执行结束后,看不到曾经执行过的流程信息了。Activity: 这个会存储所有流程实例和活动实例,在流程实例结束时,顶级流程实例变量的最新值将复制到历史变量实例中,不会存储详细信息。Audit: 在 Activity 的基础上,还会存储历史详细信息,包括权限信息等。默认的日志记录级别即次。Full: 这个是在 Audit 的基础上,还会存储变量的变化信息,这会记录大量的数据,也会导致流程执行变慢。一共就这四种级别,在 Spring Boot 项目中,如果我们想要配置这个日志记录的级别,其实非常方便,直接在 application.properties 中进行配置即可,如下: flowable.history-level=none 配置加了这个配置,我们随便启动一个流程,然后去查询ACT_HI_系列的表,发现都是空的,没有数据。 如果我们将历史日志记录的级别改为 activity,那么就会记录下来流程信息以及活动信息,但是像执行的 Task 这些信息都是没有的(ACT_HI_TASKINST),包括流程参与者的信息(ACT_HI_IDENTITYLINK)等都不会记录下来。 如果我们将历史日志记录的级别改为 audit,则上面提到的这几种日志就都会记录下来。但是ACT_HI_DETAIL表还是空的,详细一个流程变量的变化过程不会被记录下来。 如果我们将日志记录级别改为 full,那么将会记录下更多的信息。ACT_HI_DETAIL表中会记录下流程变量的详细信息。 整个过程我就不给小伙伴们演示了大家可以自行尝试。 好啦,关于历史数据的查询,松哥先和小伙伴们聊这么多~下篇文章我们继续~ 阅读原文

上一篇:2025-07-01_前端权限系统怎么做才不会写吐?我们项目踩过的 3 套失败方案总结 下一篇:2024-05-27_投稿上精选活动!本周主题「来自520的浪漫」

TAG标签:

18
网站开发网络凭借多年的网站建设经验,坚持以“帮助中小企业实现网络营销化”为宗旨,累计为4000多家客户提供品质建站服务,得到了客户的一致好评。如果您有网站建设网站改版域名注册主机空间手机网站建设网站备案等方面的需求...
请立即点击咨询我们或拨打咨询热线:13245491521 13245491521 ,我们会详细为你一一解答你心中的疑难。
项目经理在线

相关阅读 更多>>

猜您喜欢更多>>

我们已经准备好了,你呢?
2022我们与您携手共赢,为您的企业营销保驾护航!

不达标就退款

高性价比建站

免费网站代备案

1对1原创设计服务

7×24小时售后支持

 

全国免费咨询:

13245491521

业务咨询:13245491521 / 13245491521

节假值班:13245491521()

联系地址:

Copyright © 2019-2025      ICP备案:沪ICP备19027192号-6 法律顾问:律师XXX支持

在线
客服

技术在线服务时间:9:00-20:00

在网站开发,您对接的直接是技术员,而非客服传话!

电话
咨询

13245491521
7*24小时客服热线

13245491521
项目经理手机

微信
咨询

加微信获取报价