Flowable的入门使用

755 阅读4分钟

Flowable

1、什么是Flowable

Flowable是一个流行的轻量级的采用Java开发的业务流程引擎。通过Flowable流程引擎,我们可以部署BPMN2.0的流程定义(一般为XML文件),通过流程定义创建流程实例,查询和访问流程相关的实例与数据,等等。(BPMN,是指业务流程建模与标注,包括这些图元如何组合成一个业务流程图(Business Process Diagram)

Flowable用户手册

2、Flowable-ui的使用

将flowable-ui.war(首先修改默认数据库为MySQL)部署至Tomcat下,并启动。访问localhost:8080/flowable-ui并输入账号密码admin/test进行流程图的操作。

image-20220117083430057.png 在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会自动在指定的数据库中生成完成流程所定义的

此处为单线并行审批流程图

image-20220116145848326.png

在审批流程中定义多实例,设置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>

image-20220116164235839.png

image-20220116163840050.png

在审批通过节点中,使用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("执行审批成功的业务逻辑~");
    }
}

image-20220116164000870.png

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

api.services.png

所有的服务都是无状态的。这意味着你可以很容易的在集群环境的多个节点上运行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 {
     // 流程尚未结束
 }