Flowable
1、什么是Flowable
Flowable是一个流行的轻量级的采用Java开发的业务流程引擎。通过Flowable流程引擎,我们可以部署BPMN2.0的流程定义(一般为XML文件),通过流程定义创建流程实例,查询和访问流程相关的实例与数据,等等。(BPMN,是指业务流程建模与标注,包括这些图元如何组合成一个业务流程图(Business Process Diagram) )
2、Flowable-ui的使用
将flowable-ui.war(首先修改默认数据库为MySQL)部署至Tomcat下,并启动。访问localhost:8080/flowable-ui并输入账号密码admin/test进行流程图的操作。
在Web页面画好流程图后,可将其导出为BPMN2,XML格式文件,放到resource/processes文件夹中(SpringBoot会自动加载)
3、Flowable与SpringBoot项目整合
pom.xml
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.flowable</groupId>
<artifactId>flowable-spring-boot-starter</artifactId>
<version>${flowable.version}</version>
</dependency>
<dependency>
<groupId>org.flowable</groupId>
<artifactId>flowable-spring</artifactId>
<version>${flowable.version}</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
application.properties
# 数据库驱动:
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
# 数据源名称
spring.datasource.name=defaultDataSource
# 数据库连接地址
spring.datasource.url=jdbc:mysql://localhost:3306/flowable?nullCatalogMeansCurrent=true&characterEncoding=utf-8&serverTimezone=UTC
# 数据库用户名&密码:
spring.datasource.username=root
spring.datasource.password=root
在启动项目时,flowable会自动在指定的数据库中生成完成流程所定义的表。
此处为单线并行审批流程图
在审批流程中定义多实例,设置isSequential="true" ,为true时,多实例串行执行,反之则为并行执行。
多实例是在业务流程中,为特定步骤定义重复的方式;类似于for each,为给定集合中的每一项顺序或并行的执行特定步骤。在多实例中默认会提供以下变量:
- nrOfInstances:实例总数。
- nrOfActiveInstances:当前活动的(即未完成的),实例数量。对于顺序多实例,这个值总为1。
- nrOfCompletedInstances:已完成的实例数量。
- loopCounter:给定实例在for-each循环中的index。可以通过Flowable的elementIndexVariable属性为loopCounter变量重命名。
<userTask id="sid-417AF694-DF86-4C94-BC81-66F7DAA7FF02" name="审批"
flowable:formFieldValidation="true"
flowable:assignee="${user}">
<multiInstanceLoopCharacteristics isSequential="true" flowable:collection="list" flowable:elementVariable="user">
<!--完成条件的配置-->
<completionCondition>${nrOfCompletedInstances/nrOfInstances == 1.0}</completionCondition>
</multiInstanceLoopCharacteristics>
</userTask>
在审批通过节点中,使用JavaDelegate指定调用Java逻辑,要实现可以在流程执行中调用的类,需要实现org.flowable.engine.delegate.JavaDelegate接口,并在execute方法中提供所需逻辑。当流程执行到达该活动时,会执行方法中定义的逻辑,并按照BPMN 2.0的默认方法离开活动。
在流程定义中使用的类,不会在容器启动时创建,只会在流程到达时创建,且仅创建一个实例,需要是线程安全的。
<serviceTask id="sid-4C6B1693-6A81-4B21-A496-ADA6D12964FF" name="审批通过"
flowable:class="com.csii.flowable.delegate.TaskThreeSuccessDelegate"></serviceTask>
示例仅为简单打印日志
@Slf4j
public class TaskSuccessDelegate implements JavaDelegate {
@Override
public void execute(DelegateExecution delegateExecution) {
log.info("OneTask处理结果: 审批成功,流程结束");
log.info("执行审批成功的业务逻辑~");
}
}
Processes
<?xml version="1.0" encoding="UTF-8"?>
<definitions xmlns="http://www.omg.org/spec/BPMN/20100524/MODEL" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:flowable="http://flowable.org/bpmn" xmlns:bpmndi="http://www.omg.org/spec/BPMN/20100524/DI" xmlns:omgdc="http://www.omg.org/spec/DD/20100524/DC" xmlns:omgdi="http://www.omg.org/spec/DD/20100524/DI" typeLanguage="http://www.w3.org/2001/XMLSchema" expressionLanguage="http://www.w3.org/1999/XPath" targetNamespace="http://www.flowable.org/processdef">
<process id="taskThree" name="flowable_task_three" isExecutable="true">
<startEvent id="sid-862A8722-9753-4628-8F57-4CB63CB76CAD" flowable:formFieldValidation="true"></startEvent>
<userTask id="sid-417AF694-DF86-4C94-BC81-66F7DAA7FF02" name="审批"
flowable:formFieldValidation="true"
flowable:assignee="${user}">
<multiInstanceLoopCharacteristics isSequential="true" flowable:collection="list" flowable:elementVariable="user">
<!--完成条件的配置-->
<completionCondition>${nrOfCompletedInstances/nrOfInstances == 1.0}</completionCondition>
</multiInstanceLoopCharacteristics>
</userTask>
<sequenceFlow id="sid-91634D47-5ECF-4347-9D97-BB416FFADBF1" sourceRef="sid-862A8722-9753-4628-8F57-4CB63CB76CAD" targetRef="sid-417AF694-DF86-4C94-BC81-66F7DAA7FF02"></sequenceFlow>
<userTask id="sid-24D76057-EF0A-41E1-A895-144F1C0321AA" name="复核"
flowable:formFieldValidation="true"
flowable:candidateGroups="managers">
</userTask>
<endEvent id="sid-F89E280B-2936-46F1-BDCA-3A03D3B53CFE"></endEvent>
<serviceTask id="sid-4C6B1693-6A81-4B21-A496-ADA6D12964FF" name="审批通过"
flowable:class="com.csii.flowable.delegate.TaskThreeSuccessDelegate"></serviceTask>
<sequenceFlow id="sid-1D678C50-5F71-42C8-986F-F636DD5A1AB2" sourceRef="sid-24D76057-EF0A-41E1-A895-144F1C0321AA" targetRef="sid-4C6B1693-6A81-4B21-A496-ADA6D12964FF">
<conditionExpression xsi:type="tFormalExpression"><![CDATA[${check}]]></conditionExpression>
</sequenceFlow>
<sequenceFlow id="sid-819E7813-8C7F-4E28-8E78-AED2BC7415EF" sourceRef="sid-4C6B1693-6A81-4B21-A496-ADA6D12964FF" targetRef="sid-F89E280B-2936-46F1-BDCA-3A03D3B53CFE"></sequenceFlow>
<sequenceFlow id="sid-2F18E8E3-0E74-4182-B2AD-85DEB2D49A2F" sourceRef="sid-417AF694-DF86-4C94-BC81-66F7DAA7FF02" targetRef="sid-24D76057-EF0A-41E1-A895-144F1C0321AA">
<conditionExpression xsi:type="tFormalExpression"><![CDATA[${approve}]]></conditionExpression>
</sequenceFlow>
</process>
<bpmndi:BPMNDiagram id="BPMNDiagram_taskThree">
<bpmndi:BPMNPlane bpmnElement="taskThree" id="BPMNPlane_taskThree">
<bpmndi:BPMNShape bpmnElement="sid-862A8722-9753-4628-8F57-4CB63CB76CAD" id="BPMNShape_sid-862A8722-9753-4628-8F57-4CB63CB76CAD">
<omgdc:Bounds height="30.0" width="30.0" x="105.0" y="143.0"></omgdc:Bounds>
</bpmndi:BPMNShape>
<bpmndi:BPMNShape bpmnElement="sid-417AF694-DF86-4C94-BC81-66F7DAA7FF02" id="BPMNShape_sid-417AF694-DF86-4C94-BC81-66F7DAA7FF02">
<omgdc:Bounds height="80.0" width="100.0" x="180.0" y="118.0"></omgdc:Bounds>
</bpmndi:BPMNShape>
<bpmndi:BPMNShape bpmnElement="sid-24D76057-EF0A-41E1-A895-144F1C0321AA" id="BPMNShape_sid-24D76057-EF0A-41E1-A895-144F1C0321AA">
<omgdc:Bounds height="80.0" width="100.0" x="360.0" y="118.0"></omgdc:Bounds>
</bpmndi:BPMNShape>
<bpmndi:BPMNShape bpmnElement="sid-F89E280B-2936-46F1-BDCA-3A03D3B53CFE" id="BPMNShape_sid-F89E280B-2936-46F1-BDCA-3A03D3B53CFE">
<omgdc:Bounds height="28.0" width="28.0" x="750.0" y="144.0"></omgdc:Bounds>
</bpmndi:BPMNShape>
<bpmndi:BPMNShape bpmnElement="sid-4C6B1693-6A81-4B21-A496-ADA6D12964FF" id="BPMNShape_sid-4C6B1693-6A81-4B21-A496-ADA6D12964FF">
<omgdc:Bounds height="80.0" width="100.0" x="555.0" y="118.0"></omgdc:Bounds>
</bpmndi:BPMNShape>
<bpmndi:BPMNEdge bpmnElement="sid-91634D47-5ECF-4347-9D97-BB416FFADBF1" id="BPMNEdge_sid-91634D47-5ECF-4347-9D97-BB416FFADBF1">
<omgdi:waypoint x="134.9499984899576" y="158.0"></omgdi:waypoint>
<omgdi:waypoint x="179.9999999999917" y="158.0"></omgdi:waypoint>
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge bpmnElement="sid-819E7813-8C7F-4E28-8E78-AED2BC7415EF" id="BPMNEdge_sid-819E7813-8C7F-4E28-8E78-AED2BC7415EF">
<omgdi:waypoint x="654.9499999999999" y="158.0"></omgdi:waypoint>
<omgdi:waypoint x="750.0" y="158.0"></omgdi:waypoint>
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge bpmnElement="sid-2F18E8E3-0E74-4182-B2AD-85DEB2D49A2F" id="BPMNEdge_sid-2F18E8E3-0E74-4182-B2AD-85DEB2D49A2F">
<omgdi:waypoint x="279.9499999999431" y="158.0"></omgdi:waypoint>
<omgdi:waypoint x="359.99999999997226" y="158.0"></omgdi:waypoint>
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge bpmnElement="sid-1D678C50-5F71-42C8-986F-F636DD5A1AB2" id="BPMNEdge_sid-1D678C50-5F71-42C8-986F-F636DD5A1AB2">
<omgdi:waypoint x="459.9499999999802" y="158.0"></omgdi:waypoint>
<omgdi:waypoint x="555.0" y="158.0"></omgdi:waypoint>
</bpmndi:BPMNEdge>
</bpmndi:BPMNPlane>
</bpmndi:BPMNDiagram>
</definitions>
4、如何与Flowable交互
API
所有的服务都是无状态的。这意味着你可以很容易的在集群环境的多个节点上运行Flowable,使用同一个数据库,而不用担心上一次调用实际在哪台机器上执行。不论在哪个节点执行,对任何服务的任何调用都是幂等(idempotent)的。(from Flowable文档)
4.1 启动流程实例
传入流程Id与需要传入的参数Map<String, Object>
@Autowired
private RuntimeService runtimeService;
ProcessInstance processInstance = runtimeService.startProcessInstanceByKey(processId, variableMap);
在启动后,Flowable将从开始节点流动到审批节点,并为传入的UserList中的每个用户创建一个任务
4.2 查看任务
通过传入UserId作为Key去查询该用户下的全部任务
@Autowired
private TaskService taskService;
List<String> taskIds = taskService.createTaskQuery().taskAssignee(userId).list().stream().map(TaskInfo::getId).collect(Collectors.toList());
在实际应用中出现数据库中task表数据量巨大,导致该处查询缓慢的情况
使用自定义SQL
List<String> taskIds = taskService.createNativeTaskQuery().sql("SELECT art.ID_ FROM act_ru_task art WHERE art.ASSIGNEE_ = #{assignee}")
.parameter("assignee", "userId")
.list()
.stream()
.map(Task::getId)
.collect(Collectors.toList());
对act_ru_task表中的ASSIGNEE_字段建立索引 在查询时可以达到覆盖索引 不需要回表操作 可以极大地提升查询效率
4.3 完成任务
这里如果传入的taskId不存在或者已经结束会抛错 可以在complete前先判断该任务是否存在
@Autowired
private TaskService taskService;
// 先判断任务是否存在
Task task = taskService.createTaskQuery().taskId("taskId").singleResult();
if (ObjectUtils.isEmpty(task)) {
// 此处抛出项目中的自定义异常进行统一异常处理
throw new RuntimeException("任务不存在");
}
// 可以在完成任务时传入参数与启动流程类似
taskService.complete(taskId,map);
在审批节点中由于设置了完成条件的判断,所以完成后 nrOfCompletedInstances——已完成的实例数量会自动加一,如果达到要求当前节点视为结束,当前节点剩下的所有任务被移动到历史表中,流程进入下一节点。
4.4 判断流程是否结束
可以根据结果进行一些业务处理
@Autowired
private TaskService taskService;
@Autowired
private HistoryService historyService;
// 判断流程是否结束
// 方法一
HistoricProcessInstance processInstance = historyService.createHistoricProcessInstanceQuery().processInstanceId("processInstanceId").singleResult();
if (ObjectUtils.isEmpty(processInstance)) {
// 此流程已经结束
} else {
// 流程尚未结束
}
// 方法二
List<Task> taskList = taskService.createTaskQuery().processInstanceId("processInstanceId").list();
if (taskList.isEmpty()) {
// 此流程已经结束
} else {
// 流程尚未结束
}