flowable工作流-多人会签
突然要用到工作流了,参考大家的博客做了一个小测试,都是自己简单的理解,小记一下。
需求
需求描述:
要求多个人顺序会签同一个任务,一旦有任意一个人审批不通过,则任务视为不通过;直到所有人全部审批通过,则任务才算通过。
需求的流程图设计如下:
要求3个人张三、李四、王五按照顺序审批【员工投票】任务,如果其中任意一个人不同意则流转到【人事审批】任务,如果3个人全部审批通过,则流转到【经理审批】任务。
实现思路分析:
会签任务是否完成主要在于【完成条件】,首先它是boolean
值,也就是表达式结果为true
时,会签任务结束;表达式结果为false
时,会签任务继续(也就是让其他人继续审批)。那么最终网关的走向就是【完成条件】表达式的值再加上【流条件】表达式的值。
实现方式举例:
方式一:
比如【完成条件】表达式的值为${employeeVoteTaskCompleteConfig.completeTaskTo(execution)}
,该表达式调用了一个类中的方法,如果要结束会签任务,则需要方法返回结果为true
;如果要继续会签任务,则需要方法返回结果为false
。
方式二:
比如【完成条件】表达式的值为${approvalStatus == 'finished'}
,如果要结束会签任务,则需要设置一个流程变量approvalStatus
并且设置为finished
;如果要继续会签任务,则设置流程变量approvalStatus
不要为finished
,任意字符串xxx
都可以,让表达式为false
即可。
其他方式
略。
流程变量:
无论是哪种配置,核心思想就是控制【完成条件】的结果是true(结束会签任务)还是false(继续会签任务),需要根据实际的业务去写代码做判断。
关于本文档中描述的需求,需要对审批通过的人数进行计数,每有一个人审批通过则计数加1,而对审批通过的人数则不需要计数,因为只要有一个人审批不通过则直接结束会签任务,直接对审批的流条件的值进行判断即可。
方式一:【完成条件(多实例) 】条件表达式实现
1. 流程图
(1)【员工投票】任务配置:
其中【完成条件(多实例) 】的表达式为:${employeeVoteTaskCompleteConfig.completeTaskTo(execution)}
(2) SequentialCountersigningDemo2.bpmn20.xml
<?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" exporter="Flowable Open Source Modeler" exporterVersion="6.8.0">
<process id="SequentialCountersigningDemo2" name="SequentialCountersigningDemo2" isExecutable="true">
<documentation>串行会签,任务都审批通过了才算会签结束</documentation>
<startEvent id="startEvent1" flowable:formFieldValidation="true"></startEvent>
<userTask id="sid-E50087DC-EEE6-463D-82CF-D1768B68AF5C" name="员工投票" flowable:assignee="${assignee}" flowable:formFieldValidation="true">
<extensionElements>
<modeler:initiator-can-complete xmlns:modeler="http://flowable.org/modeler"><![CDATA[false]]></modeler:initiator-can-complete>
</extensionElements>
<multiInstanceLoopCharacteristics isSequential="true" flowable:collection="userList" flowable:elementVariable="assignee">
<extensionElements></extensionElements>
<loopCardinality>3</loopCardinality>
<completionCondition>${employeeVoteTaskCompleteConfig.completeTaskTo(execution)}</completionCondition>
</multiInstanceLoopCharacteristics>
</userTask>
<sequenceFlow id="sid-505C40DD-5976-4251-BE19-9405E3E48A0E" sourceRef="startEvent1" targetRef="sid-E50087DC-EEE6-463D-82CF-D1768B68AF5C"></sequenceFlow>
<userTask id="sid-4CD59B65-B6ED-476D-8C69-C9633A85972D" name="经理审批" flowable:assignee="经理1" flowable:formFieldValidation="true">
<extensionElements>
<modeler:initiator-can-complete xmlns:modeler="http://flowable.org/modeler"><![CDATA[false]]></modeler:initiator-can-complete>
</extensionElements>
</userTask>
<endEvent id="sid-6AFF5EB8-36BC-4AAC-BAAF-046B630B1EAF"></endEvent>
<sequenceFlow id="sid-1A2B4CC7-8440-4603-8FE9-5262B7ECE063" sourceRef="sid-4CD59B65-B6ED-476D-8C69-C9633A85972D" targetRef="sid-6AFF5EB8-36BC-4AAC-BAAF-046B630B1EAF"></sequenceFlow>
<userTask id="sid-92B878AB-F3CF-4545-99F5-35A2E61454C3" name="人事审批" flowable:assignee="人事1" flowable:formFieldValidation="true">
<extensionElements>
<modeler:initiator-can-complete xmlns:modeler="http://flowable.org/modeler"><![CDATA[false]]></modeler:initiator-can-complete>
</extensionElements>
</userTask>
<exclusiveGateway id="sid-4CBF8677-102B-4FED-9881-BAA7256E7560"></exclusiveGateway>
<sequenceFlow id="sid-E3E81E5C-001A-4362-8C87-BFA9EEE2753F" sourceRef="sid-E50087DC-EEE6-463D-82CF-D1768B68AF5C" targetRef="sid-4CBF8677-102B-4FED-9881-BAA7256E7560"></sequenceFlow>
<sequenceFlow id="sid-8BB95FD3-F90A-463A-A70C-A3C98A60EDB8" sourceRef="sid-92B878AB-F3CF-4545-99F5-35A2E61454C3" targetRef="sid-6AFF5EB8-36BC-4AAC-BAAF-046B630B1EAF"></sequenceFlow>
<sequenceFlow id="sid-06478FB7-4527-4C10-8A78-1DA552A27898" name="通过" sourceRef="sid-4CBF8677-102B-4FED-9881-BAA7256E7560" targetRef="sid-4CD59B65-B6ED-476D-8C69-C9633A85972D">
<conditionExpression xsi:type="tFormalExpression"><![CDATA[${result == 1}]]></conditionExpression>
</sequenceFlow>
<sequenceFlow id="sid-49858C03-F037-447E-A085-0ED4047996F4" name="不通过" sourceRef="sid-4CBF8677-102B-4FED-9881-BAA7256E7560" targetRef="sid-92B878AB-F3CF-4545-99F5-35A2E61454C3">
<conditionExpression xsi:type="tFormalExpression"><![CDATA[${result == 0}]]></conditionExpression>
</sequenceFlow>
</process>
<bpmndi:BPMNDiagram id="BPMNDiagram_SequentialCountersigningDemo2">
<bpmndi:BPMNPlane bpmnElement="SequentialCountersigningDemo2" id="BPMNPlane_SequentialCountersigningDemo2">
<bpmndi:BPMNShape bpmnElement="startEvent1" id="BPMNShape_startEvent1">
<omgdc:Bounds height="30.0" width="30.0" x="100.0" y="163.0"></omgdc:Bounds>
</bpmndi:BPMNShape>
<bpmndi:BPMNShape bpmnElement="sid-E50087DC-EEE6-463D-82CF-D1768B68AF5C" id="BPMNShape_sid-E50087DC-EEE6-463D-82CF-D1768B68AF5C">
<omgdc:Bounds height="80.0" width="100.0" x="180.0" y="138.0"></omgdc:Bounds>
</bpmndi:BPMNShape>
<bpmndi:BPMNShape bpmnElement="sid-4CD59B65-B6ED-476D-8C69-C9633A85972D" id="BPMNShape_sid-4CD59B65-B6ED-476D-8C69-C9633A85972D">
<omgdc:Bounds height="80.0" width="100.0" x="540.0" y="138.0"></omgdc:Bounds>
</bpmndi:BPMNShape>
<bpmndi:BPMNShape bpmnElement="sid-6AFF5EB8-36BC-4AAC-BAAF-046B630B1EAF" id="BPMNShape_sid-6AFF5EB8-36BC-4AAC-BAAF-046B630B1EAF">
<omgdc:Bounds height="28.0" width="28.0" x="707.5" y="164.0"></omgdc:Bounds>
</bpmndi:BPMNShape>
<bpmndi:BPMNShape bpmnElement="sid-92B878AB-F3CF-4545-99F5-35A2E61454C3" id="BPMNShape_sid-92B878AB-F3CF-4545-99F5-35A2E61454C3">
<omgdc:Bounds height="80.0" width="100.0" x="334.5" y="298.0"></omgdc:Bounds>
</bpmndi:BPMNShape>
<bpmndi:BPMNShape bpmnElement="sid-4CBF8677-102B-4FED-9881-BAA7256E7560" id="BPMNShape_sid-4CBF8677-102B-4FED-9881-BAA7256E7560">
<omgdc:Bounds height="40.0" width="40.0" x="364.5" y="158.0"></omgdc:Bounds>
</bpmndi:BPMNShape>
<bpmndi:BPMNEdge bpmnElement="sid-505C40DD-5976-4251-BE19-9405E3E48A0E" id="BPMNEdge_sid-505C40DD-5976-4251-BE19-9405E3E48A0E" flowable:sourceDockerX="15.0" flowable:sourceDockerY="15.0" flowable:targetDockerX="50.0" flowable:targetDockerY="40.0">
<omgdi:waypoint x="129.9499986183554" y="178.0"></omgdi:waypoint>
<omgdi:waypoint x="180.0" y="178.0"></omgdi:waypoint>
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge bpmnElement="sid-E3E81E5C-001A-4362-8C87-BFA9EEE2753F" id="BPMNEdge_sid-E3E81E5C-001A-4362-8C87-BFA9EEE2753F" flowable:sourceDockerX="50.0" flowable:sourceDockerY="40.0" flowable:targetDockerX="20.5" flowable:targetDockerY="20.5">
<omgdi:waypoint x="279.9499999999986" y="178.16112903225806"></omgdi:waypoint>
<omgdi:waypoint x="364.93527508090267" y="178.43527508090614"></omgdi:waypoint>
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge bpmnElement="sid-8BB95FD3-F90A-463A-A70C-A3C98A60EDB8" id="BPMNEdge_sid-8BB95FD3-F90A-463A-A70C-A3C98A60EDB8" flowable:sourceDockerX="50.0" flowable:sourceDockerY="40.0" flowable:targetDockerX="14.0" flowable:targetDockerY="14.0">
<omgdi:waypoint x="434.4499999998777" y="338.0"></omgdi:waypoint>
<omgdi:waypoint x="721.5" y="338.0"></omgdi:waypoint>
<omgdi:waypoint x="721.5" y="191.94992687591196"></omgdi:waypoint>
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge bpmnElement="sid-49858C03-F037-447E-A085-0ED4047996F4" id="BPMNEdge_sid-49858C03-F037-447E-A085-0ED4047996F4" flowable:sourceDockerX="20.5" flowable:sourceDockerY="20.5" flowable:targetDockerX="50.0" flowable:targetDockerY="40.0">
<omgdi:waypoint x="384.94025157232704" y="197.5035983658077"></omgdi:waypoint>
<omgdi:waypoint x="384.6252351097179" y="298.0"></omgdi:waypoint>
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge bpmnElement="sid-06478FB7-4527-4C10-8A78-1DA552A27898" id="BPMNEdge_sid-06478FB7-4527-4C10-8A78-1DA552A27898" flowable:sourceDockerX="20.5" flowable:sourceDockerY="20.5" flowable:targetDockerX="50.0" flowable:targetDockerY="40.0">
<omgdi:waypoint x="403.99167888562664" y="178.45354523227383"></omgdi:waypoint>
<omgdi:waypoint x="539.9999999999948" y="178.12182926829266"></omgdi:waypoint>
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge bpmnElement="sid-1A2B4CC7-8440-4603-8FE9-5262B7ECE063" id="BPMNEdge_sid-1A2B4CC7-8440-4603-8FE9-5262B7ECE063" flowable:sourceDockerX="50.0" flowable:sourceDockerY="40.0" flowable:targetDockerX="14.0" flowable:targetDockerY="14.0">
<omgdi:waypoint x="639.9499999999077" y="178.0"></omgdi:waypoint>
<omgdi:waypoint x="707.5" y="178.0"></omgdi:waypoint>
</bpmndi:BPMNEdge>
</bpmndi:BPMNPlane>
</bpmndi:BPMNDiagram>
</definitions>
2. 【完成条件(多实例) 】的表达式中的类
package com.example.flowabledemo.config;
import org.flowable.engine.delegate.DelegateExecution;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;
/**
* 完成任务
*/
@Component
public class EmployeeVoteTaskCompleteConfig {
private static final Logger logger = LoggerFactory.getLogger(EmployeeVoteTaskCompleteConfig.class);
/**
* 任务的执行实例监听器
* 任务配置:【员工投票】-完成条件(多实例) : ${employeeVoteTaskCompleteConfig.completeTaskTo(execution)}
*
* @param execution 执行实例
* @return {true: 结束会签任务;false: 继续会签任务}
*/
public boolean completeTaskTo(DelegateExecution execution) {
Object result = execution.getVariable("result");
if (result.equals(0)) {
logger.info("当前任务({})有人审批不通过,结束会签任务", execution.getCurrentFlowElement().getName());
//设置会签结果 排他网关使用
return true;
}
if (!result.equals(1)) {
throw new IllegalArgumentException("不识别的流程变量取值: " + result);
}
Integer nrOfInstances = (Integer) execution.getVariable("nrOfInstances");
logger.info("任务会签总数:" + nrOfInstances);
logger.info("待会签任务数量:" + execution.getVariable("nrOfActiveInstances"));
logger.info("已完成会签任务数量:" + execution.getVariable("nrOfCompletedInstances"));
// 获取同意人数
Object agreeObj = execution.getVariable("agree");
if (agreeObj == null) {
execution.setVariable("agree", 1);
logger.info("当前同意人数为{}, 增加同意人数为:{}", 0, 1);
} else {
execution.setVariable("agree", (Integer) agreeObj + 1);
logger.info("当前同意人数为{}, 增加同意人数为:{}", agreeObj, (Integer) agreeObj + 1);
}
// 最终的同意人数
Integer agree = (Integer) execution.getVariable("agree");
logger.info("当前审批通过的人数: {}", execution.getVariable("agree"));
if (agree.equals(nrOfInstances)) {
logger.info("审批通过的人数({})为达到任务会签总数({}),结束会签任务({})", agree, nrOfInstances, execution.getCurrentFlowElement().getName());
//设置会签结果 排他网关使用wanm
return true;
}
logger.info("当前审批通过的人数({}),还未达到任务会签总数({}),继续会签任务({})", agree, nrOfInstances, execution.getCurrentFlowElement().getName());
//设置会签结果 排他网关使用
return false;
}
}
3. 测试类
package com.example.flowabledemo;
import org.flowable.engine.*;
import org.flowable.engine.repository.Deployment;
import org.flowable.engine.repository.DeploymentBuilder;
import org.flowable.engine.repository.ProcessDefinition;
import org.flowable.engine.runtime.ProcessInstance;
import org.flowable.task.api.Task;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import java.io.IOException;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
@SpringBootTest
public class SequentialCountersigningTests2 {
@Autowired
ProcessEngine processEngine;
@Autowired
RepositoryService repositoryService;
@Autowired
RuntimeService runtimeService;
@Autowired
TaskService taskService;
@Autowired
HistoryService historyService;
private static final String processDefinitionKey = "SequentialCountersigningDemo2";
/**
* 部署
*
* @throws IOException
*/
@Test
void deployFlow() throws IOException {
// 部署流程
DeploymentBuilder deploymentBuilder = repositoryService.createDeployment();
deploymentBuilder.addClasspathResource("processes/SequentialCountersigningDemo2.bpmn20.xml");
deploymentBuilder.key(processDefinitionKey);
Deployment deploy = deploymentBuilder.deploy();
System.out.println("deployId = " + deploy.getId());
}
@Test
public void statrProcess() {
//先查到流程
ProcessDefinition processDefinition = repositoryService.createProcessDefinitionQuery().processDefinitionKey(processDefinitionKey).singleResult();
//启动流程并分配
Map<String, Object> map = new HashMap<>();
//注意这里的值必须大于 设置的基数
map.put("userList", Arrays.asList("张三", "李四", "王五", "赵六"));
ProcessInstance processInstance = runtimeService.startProcessInstanceById(processDefinition.getId(), map);
System.out.println("processInstanceId = " + processInstance.getId());
}
@Test
public void completeTask1() {
ProcessInstance processInstance = runtimeService.createProcessInstanceQuery().processDefinitionKey(processDefinitionKey).singleResult();
if (processInstance != null) {
Task task = taskService.createTaskQuery()
.processInstanceId(processInstance.getProcessInstanceId())
.taskAssignee("张三")
.singleResult();
if (task != null) {
Map<String, Object> map = new HashMap<>();
map.put("result", 1);
taskService.complete(task.getId(), map);
System.out.println("完成任务");
}
}
}
@Test
public void completeTask2() {
ProcessInstance processInstance = runtimeService.createProcessInstanceQuery().processDefinitionKey(processDefinitionKey).singleResult();
if (processInstance != null) {
Task task = taskService.createTaskQuery()
.processInstanceId(processInstance.getProcessInstanceId())
.taskAssignee("李四")
.singleResult();
if (task != null) {
Map<String, Object> map = new HashMap<>();
map.put("result", 1);
taskService.complete(task.getId(), map);
System.out.println("完成任务");
}
}
}
@Test
public void completeTask3() {
ProcessInstance processInstance = runtimeService.createProcessInstanceQuery().processDefinitionKey(processDefinitionKey).singleResult();
if (processInstance != null) {
Task task = taskService.createTaskQuery()
.processInstanceId(processInstance.getProcessInstanceId())
.taskAssignee("王五")
.singleResult();
if (task != null) {
Map<String, Object> map = new HashMap<>();
map.put("result", 1);
taskService.complete(task.getId(), map);
System.out.println("完成任务");
}
}
}
@Test
public void completeTask() {
taskService.complete("5c3c12e5-d6a6-11ee-bd6d-2cdb0717c71c");
System.out.println("完成任务");
}
@Test
void deleteDeployFlow() {
List<ProcessDefinition> list = repositoryService.createProcessDefinitionQuery().processDefinitionKey(processDefinitionKey).list();
for (int i = 0; i < list.size(); i++) {
repositoryService.deleteDeployment(list.get(i).getDeploymentId(), true);
System.out.println((i + 1) + "-删除流程成功: " + list.get(i).getId());
}
}
}
方式二:任务监听器实现
1. 流程图
完整流程图与方式一相同。
(1)【员工投票】任务配置:
其中【完成条件(多实例) 】的表达式为:${approvalStatus == 'finished'}
.
其中任务监听器配置:
表达式为:${approvalStatus == 'finished'}
.
(2) SequentialCountersigningDemo3.bpmn20.xml
<?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" exporter="Flowable Open Source Modeler" exporterVersion="6.8.0">
<process id="SequentialCountersigningDemo3" name="SequentialCountersigningDemo3" isExecutable="true">
<documentation>测试3,串行会签,任务都审批通过了才算会签结束</documentation>
<startEvent id="startEvent1" flowable:formFieldValidation="true"></startEvent>
<userTask id="sid-E50087DC-EEE6-463D-82CF-D1768B68AF5C" name="员工投票" flowable:assignee="${assignee}" flowable:formFieldValidation="true">
<extensionElements>
<flowable:taskListener event="complete" class="com.example.flowabledemo.listener.CompleteTaskListener"></flowable:taskListener>
<modeler:initiator-can-complete xmlns:modeler="http://flowable.org/modeler"><![CDATA[false]]></modeler:initiator-can-complete>
</extensionElements>
<multiInstanceLoopCharacteristics isSequential="true" flowable:collection="userList" flowable:elementVariable="assignee">
<extensionElements></extensionElements>
<loopCardinality>3</loopCardinality>
<completionCondition>${approvalStatus == 'finished'}</completionCondition>
</multiInstanceLoopCharacteristics>
</userTask>
<sequenceFlow id="sid-505C40DD-5976-4251-BE19-9405E3E48A0E" sourceRef="startEvent1" targetRef="sid-E50087DC-EEE6-463D-82CF-D1768B68AF5C"></sequenceFlow>
<userTask id="sid-4CD59B65-B6ED-476D-8C69-C9633A85972D" name="经理审批" flowable:assignee="经理1" flowable:formFieldValidation="true">
<extensionElements>
<modeler:initiator-can-complete xmlns:modeler="http://flowable.org/modeler"><![CDATA[false]]></modeler:initiator-can-complete>
</extensionElements>
</userTask>
<endEvent id="sid-6AFF5EB8-36BC-4AAC-BAAF-046B630B1EAF"></endEvent>
<sequenceFlow id="sid-1A2B4CC7-8440-4603-8FE9-5262B7ECE063" sourceRef="sid-4CD59B65-B6ED-476D-8C69-C9633A85972D" targetRef="sid-6AFF5EB8-36BC-4AAC-BAAF-046B630B1EAF"></sequenceFlow>
<userTask id="sid-92B878AB-F3CF-4545-99F5-35A2E61454C3" name="人事审批" flowable:assignee="人事1" flowable:formFieldValidation="true">
<extensionElements>
<modeler:initiator-can-complete xmlns:modeler="http://flowable.org/modeler"><![CDATA[false]]></modeler:initiator-can-complete>
</extensionElements>
</userTask>
<exclusiveGateway id="sid-4CBF8677-102B-4FED-9881-BAA7256E7560"></exclusiveGateway>
<sequenceFlow id="sid-E3E81E5C-001A-4362-8C87-BFA9EEE2753F" sourceRef="sid-E50087DC-EEE6-463D-82CF-D1768B68AF5C" targetRef="sid-4CBF8677-102B-4FED-9881-BAA7256E7560"></sequenceFlow>
<sequenceFlow id="sid-8BB95FD3-F90A-463A-A70C-A3C98A60EDB8" sourceRef="sid-92B878AB-F3CF-4545-99F5-35A2E61454C3" targetRef="sid-6AFF5EB8-36BC-4AAC-BAAF-046B630B1EAF"></sequenceFlow>
<sequenceFlow id="sid-06478FB7-4527-4C10-8A78-1DA552A27898" name="通过" sourceRef="sid-4CBF8677-102B-4FED-9881-BAA7256E7560" targetRef="sid-4CD59B65-B6ED-476D-8C69-C9633A85972D">
<conditionExpression xsi:type="tFormalExpression"><![CDATA[${result == 1}]]></conditionExpression>
</sequenceFlow>
<sequenceFlow id="sid-49858C03-F037-447E-A085-0ED4047996F4" name="不通过" sourceRef="sid-4CBF8677-102B-4FED-9881-BAA7256E7560" targetRef="sid-92B878AB-F3CF-4545-99F5-35A2E61454C3">
<conditionExpression xsi:type="tFormalExpression"><![CDATA[${result == 0}]]></conditionExpression>
</sequenceFlow>
</process>
<bpmndi:BPMNDiagram id="BPMNDiagram_SequentialCountersigningDemo3">
<bpmndi:BPMNPlane bpmnElement="SequentialCountersigningDemo3" id="BPMNPlane_SequentialCountersigningDemo3">
<bpmndi:BPMNShape bpmnElement="startEvent1" id="BPMNShape_startEvent1">
<omgdc:Bounds height="30.0" width="30.0" x="100.0" y="163.0"></omgdc:Bounds>
</bpmndi:BPMNShape>
<bpmndi:BPMNShape bpmnElement="sid-E50087DC-EEE6-463D-82CF-D1768B68AF5C" id="BPMNShape_sid-E50087DC-EEE6-463D-82CF-D1768B68AF5C">
<omgdc:Bounds height="80.0" width="100.0" x="180.0" y="138.0"></omgdc:Bounds>
</bpmndi:BPMNShape>
<bpmndi:BPMNShape bpmnElement="sid-4CD59B65-B6ED-476D-8C69-C9633A85972D" id="BPMNShape_sid-4CD59B65-B6ED-476D-8C69-C9633A85972D">
<omgdc:Bounds height="80.0" width="100.0" x="540.0" y="138.0"></omgdc:Bounds>
</bpmndi:BPMNShape>
<bpmndi:BPMNShape bpmnElement="sid-6AFF5EB8-36BC-4AAC-BAAF-046B630B1EAF" id="BPMNShape_sid-6AFF5EB8-36BC-4AAC-BAAF-046B630B1EAF">
<omgdc:Bounds height="28.0" width="28.0" x="707.5" y="164.0"></omgdc:Bounds>
</bpmndi:BPMNShape>
<bpmndi:BPMNShape bpmnElement="sid-92B878AB-F3CF-4545-99F5-35A2E61454C3" id="BPMNShape_sid-92B878AB-F3CF-4545-99F5-35A2E61454C3">
<omgdc:Bounds height="80.0" width="100.0" x="334.5" y="298.0"></omgdc:Bounds>
</bpmndi:BPMNShape>
<bpmndi:BPMNShape bpmnElement="sid-4CBF8677-102B-4FED-9881-BAA7256E7560" id="BPMNShape_sid-4CBF8677-102B-4FED-9881-BAA7256E7560">
<omgdc:Bounds height="40.0" width="40.0" x="364.5" y="158.0"></omgdc:Bounds>
</bpmndi:BPMNShape>
<bpmndi:BPMNEdge bpmnElement="sid-505C40DD-5976-4251-BE19-9405E3E48A0E" id="BPMNEdge_sid-505C40DD-5976-4251-BE19-9405E3E48A0E" flowable:sourceDockerX="15.0" flowable:sourceDockerY="15.0" flowable:targetDockerX="50.0" flowable:targetDockerY="40.0">
<omgdi:waypoint x="129.9499986183554" y="178.0"></omgdi:waypoint>
<omgdi:waypoint x="180.0" y="178.0"></omgdi:waypoint>
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge bpmnElement="sid-E3E81E5C-001A-4362-8C87-BFA9EEE2753F" id="BPMNEdge_sid-E3E81E5C-001A-4362-8C87-BFA9EEE2753F" flowable:sourceDockerX="50.0" flowable:sourceDockerY="40.0" flowable:targetDockerX="20.5" flowable:targetDockerY="20.5">
<omgdi:waypoint x="279.9499999999986" y="178.16112903225806"></omgdi:waypoint>
<omgdi:waypoint x="364.93527508090267" y="178.43527508090614"></omgdi:waypoint>
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge bpmnElement="sid-8BB95FD3-F90A-463A-A70C-A3C98A60EDB8" id="BPMNEdge_sid-8BB95FD3-F90A-463A-A70C-A3C98A60EDB8" flowable:sourceDockerX="50.0" flowable:sourceDockerY="40.0" flowable:targetDockerX="14.0" flowable:targetDockerY="14.0">
<omgdi:waypoint x="434.4499999998777" y="338.0"></omgdi:waypoint>
<omgdi:waypoint x="721.5" y="338.0"></omgdi:waypoint>
<omgdi:waypoint x="721.5" y="191.94992687591196"></omgdi:waypoint>
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge bpmnElement="sid-49858C03-F037-447E-A085-0ED4047996F4" id="BPMNEdge_sid-49858C03-F037-447E-A085-0ED4047996F4" flowable:sourceDockerX="20.5" flowable:sourceDockerY="20.5" flowable:targetDockerX="50.0" flowable:targetDockerY="40.0">
<omgdi:waypoint x="384.94025157232704" y="197.5035983658077"></omgdi:waypoint>
<omgdi:waypoint x="384.6252351097179" y="298.0"></omgdi:waypoint>
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge bpmnElement="sid-06478FB7-4527-4C10-8A78-1DA552A27898" id="BPMNEdge_sid-06478FB7-4527-4C10-8A78-1DA552A27898" flowable:sourceDockerX="20.5" flowable:sourceDockerY="20.5" flowable:targetDockerX="50.0" flowable:targetDockerY="40.0">
<omgdi:waypoint x="403.99167888562664" y="178.45354523227383"></omgdi:waypoint>
<omgdi:waypoint x="539.9999999999948" y="178.12182926829266"></omgdi:waypoint>
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge bpmnElement="sid-1A2B4CC7-8440-4603-8FE9-5262B7ECE063" id="BPMNEdge_sid-1A2B4CC7-8440-4603-8FE9-5262B7ECE063" flowable:sourceDockerX="50.0" flowable:sourceDockerY="40.0" flowable:targetDockerX="14.0" flowable:targetDockerY="14.0">
<omgdi:waypoint x="639.9499999999077" y="178.0"></omgdi:waypoint>
<omgdi:waypoint x="707.5" y="178.0"></omgdi:waypoint>
</bpmndi:BPMNEdge>
</bpmndi:BPMNPlane>
</bpmndi:BPMNDiagram>
</definitions>
2. 【任务监听器 】中配置的类
package com.example.flowabledemo.listener;
import org.flowable.task.service.delegate.DelegateTask;
import org.flowable.task.service.delegate.TaskListener;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class CompleteTaskListener implements TaskListener {
private static final Logger logger = LoggerFactory.getLogger(CompleteTaskListener.class);
/**
* 监听器触发的回调方式
*
* @param delegateTask 委派任务
*/
@Override
public void notify(DelegateTask delegateTask) {
System.out.println("自定义监听器执行了,任务名称为:" + delegateTask.getName());
if (EVENTNAME_COMPLETE.equals(delegateTask.getEventName())) {
System.out.println("任务完成了");
}
Object result = delegateTask.getVariable("result");
if (result.equals(0)) {
logger.info("当前任务({})有人审批不通过,结束会签任务", delegateTask.getName());
delegateTask.setVariable("approvalStatus", "finished");
return;
}
if (!result.equals(1)) {
throw new IllegalArgumentException("不识别的流程变量取值: " + result);
}
Integer nrOfInstances = (Integer) delegateTask.getVariable("nrOfInstances");
logger.info("任务会签总数:" + nrOfInstances);
logger.info("待会签任务数量:" + delegateTask.getVariable("nrOfActiveInstances"));
logger.info("已完成会签任务数量:" + delegateTask.getVariable("nrOfCompletedInstances"));
//获取同意人数
Object agreeObj = delegateTask.getVariable("agree");
if (agreeObj == null) {
delegateTask.setVariable("agree", 1);
logger.info("当前同意人数为{}, 增加同意人数为:{}", 0, 1);
} else {
delegateTask.setVariable("agree", (Integer) agreeObj + 1);
logger.info("当前同意人数为{}, 增加同意人数为:{}", agreeObj, (Integer) agreeObj + 1);
}
// 最终的同意人数
Integer agree = (Integer) delegateTask.getVariable("agree");
logger.info("当前审批通过的人数: {}", delegateTask.getVariable("agree"));
if (agree.equals(nrOfInstances)) {
logger.info("审批通过的人数({})为达到任务会签总数({}),结束会签任务({})", agree, nrOfInstances, delegateTask.getName());
delegateTask.setVariable("approvalStatus", "finished");
return;
}
logger.info("当前审批通过的人数({}),还未达到任务会签总数({}),继续会签任务({})", agree, nrOfInstances, delegateTask.getName());
delegateTask.setVariable("approvalStatus", "unfinished");
}
}
3. 测试类
package com.example.flowabledemo;
import org.flowable.engine.*;
import org.flowable.engine.repository.Deployment;
import org.flowable.engine.repository.DeploymentBuilder;
import org.flowable.engine.repository.ProcessDefinition;
import org.flowable.engine.runtime.ProcessInstance;
import org.flowable.task.api.Task;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import java.io.IOException;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
@SpringBootTest
public class SequentialCountersigningTests3 {
@Autowired
ProcessEngine processEngine;
@Autowired
RepositoryService repositoryService;
@Autowired
RuntimeService runtimeService;
@Autowired
TaskService taskService;
@Autowired
HistoryService historyService;
private static final String processDefinitionKey = "SequentialCountersigningDemo3";
private static final String ClasspathResource = "processes/SequentialCountersigningDemo3.bpmn20.xml";
/**
* 部署
*
* @throws IOException
*/
@Test
void deployFlow() throws IOException {
// 部署流程
DeploymentBuilder deploymentBuilder = repositoryService.createDeployment();
deploymentBuilder.addClasspathResource(ClasspathResource);
deploymentBuilder.key(processDefinitionKey);
Deployment deploy = deploymentBuilder.deploy();
System.out.println("deployId = " + deploy.getId());
}
@Test
public void statrProcess() {
//先查到流程
ProcessDefinition processDefinition = repositoryService.createProcessDefinitionQuery().processDefinitionKey(processDefinitionKey).singleResult();
//启动流程并分配
Map<String, Object> map = new HashMap<>();
//注意这里的值必须大于 设置的基数
map.put("userList", Arrays.asList("张三", "李四", "王五", "赵六"));
ProcessInstance processInstance = runtimeService.startProcessInstanceById(processDefinition.getId(), map);
System.out.println("processInstanceId = " + processInstance.getId());
}
@Test
public void completeTask1() {
ProcessInstance processInstance = runtimeService.createProcessInstanceQuery().processDefinitionKey(processDefinitionKey).singleResult();
if (processInstance != null) {
Task task = taskService.createTaskQuery()
.processInstanceId(processInstance.getProcessInstanceId())
.taskAssignee("张三")
.singleResult();
if (task != null) {
Map<String, Object> map = new HashMap<>();
map.put("result", 1);
taskService.complete(task.getId(), map);
System.out.println("完成任务");
}
}
}
@Test
public void completeTask2() {
ProcessInstance processInstance = runtimeService.createProcessInstanceQuery().processDefinitionKey(processDefinitionKey).singleResult();
if (processInstance != null) {
Task task = taskService.createTaskQuery()
.processInstanceId(processInstance.getProcessInstanceId())
.taskAssignee("李四")
.singleResult();
if (task != null) {
Map<String, Object> map = new HashMap<>();
map.put("result", 1);
taskService.complete(task.getId(), map);
System.out.println("完成任务");
}
}
}
@Test
public void completeTask3() {
ProcessInstance processInstance = runtimeService.createProcessInstanceQuery().processDefinitionKey(processDefinitionKey).singleResult();
if (processInstance != null) {
Task task = taskService.createTaskQuery()
.processInstanceId(processInstance.getProcessInstanceId())
.taskAssignee("王五")
.singleResult();
if (task != null) {
Map<String, Object> map = new HashMap<>();
map.put("result", 1);
taskService.complete(task.getId(), map);
System.out.println("完成任务");
}
}
}
@Test
public void completeTask() {
taskService.complete("28362991-d6ca-11ee-9bf9-2cdb0717c71c");
System.out.println("完成任务");
}
@Test
void deleteDeployFlow() {
List<ProcessDefinition> list = repositoryService.createProcessDefinitionQuery().processDefinitionKey(processDefinitionKey).list();
for (int i = 0; i < list.size(); i++) {
repositoryService.deleteDeployment(list.get(i).getDeploymentId(), true);
System.out.println((i + 1) + "-删除流程成功: " + list.get(i).getId());
}
}
}
【员工投票】任务审批测试结果
测试用例如下:
- 张三不同意 -> 【人事审批】
- 张三同意,李四不同意 -> 【人事审批】
- 张三同意,李四同意,王五不同意 -> 【人事审批】
- 张三同意,李四同意,王五同意 -> 【经理审批】
同意:审批人完成任务时,设置流程变量result
为1
;
不同意:审批人完成任务时,设置流程变量result
为0
;
测试的maven项目配置
(1) pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.example</groupId>
<artifactId>flowable-demo</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>flowable-demo</name>
<description>flowable-demo</description>
<properties>
<java.version>1.8</java.version>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<spring-boot.version>2.5.14</spring-boot.version>
<flowable.version>6.7.2</flowable.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- JPA -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.flowable</groupId>
<artifactId>flowable-spring-boot-starter</artifactId>
<version>${flowable.version}</version>
<exclusions>
<exclusion>
<groupId>org.flowable</groupId>
<artifactId>flowable-cmmn-spring</artifactId>
</exclusion>
<exclusion>
<groupId>org.flowable</groupId>
<artifactId>flowable-dmn-spring</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
<!-- 阿里数据库连接池 -->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid-spring-boot-starter</artifactId>
<version>1.2.20</version>
</dependency>
<!-- 日志相关 -->
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-log4j12</artifactId>
</dependency>
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-all</artifactId>
<version>5.8.22</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-configuration-processor</artifactId>
<optional>true</optional>
</dependency>
</dependencies>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-dependencies</artifactId>
<version>${spring-boot.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.8.1</version>
<configuration>
<source>1.8</source>
<target>1.8</target>
<encoding>UTF-8</encoding>
</configuration>
</plugin>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<version>${spring-boot.version}</version>
<configuration>
<mainClass>com.example.flowabledemo.FlowableDemoApplication</mainClass>
<skip>true</skip>
</configuration>
<executions>
<execution>
<id>repackage</id>
<goals>
<goal>repackage</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>
(2) application.yml中的flowable相关配置
logging:
level:
org:
flowable: debug
flowable:
# 是否激活异步执行器
asyncExecutorActivate: false
# 将databaseSchemaUpdate没置为true,当flowable发现库与数据库表结构不一致时,会自动将数据库表结构升级至新版本。
databaseSchemaUpdate: true
# 在启动时是否检查流程定义,将其设置为false可以关闭自动部署功能
checkProcessDefinitions: false
form:
# 是否开启自动部署表单
deployResources: true
deploymentName: "formDeployment"
resourceLocation: "classpath*:/forms/"
app:
enabled: false
(3)流程图文件存放目录
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 全程不用写代码,我用AI程序员写了一个飞机大战
· DeepSeek 开源周回顾「GitHub 热点速览」
· 记一次.NET内存居高不下排查解决与启示
· 物流快递公司核心技术能力-地址解析分单基础技术分享
· .NET 10首个预览版发布:重大改进与新特性概览!