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());
        }
    }

}


【员工投票】任务审批测试结果

测试用例如下:

  1. 张三不同意 -> 【人事审批】
  2. 张三同意,李四不同意 -> 【人事审批】
  3. 张三同意,李四同意,王五不同意 -> 【人事审批】
  4. 张三同意,李四同意,王五同意 -> 【经理审批】

同意:审批人完成任务时,设置流程变量result1
不同意:审批人完成任务时,设置流程变量result0

测试的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)流程图文件存放目录

posted @   cymin  阅读(5122)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 全程不用写代码,我用AI程序员写了一个飞机大战
· DeepSeek 开源周回顾「GitHub 热点速览」
· 记一次.NET内存居高不下排查解决与启示
· 物流快递公司核心技术能力-地址解析分单基础技术分享
· .NET 10首个预览版发布:重大改进与新特性概览!
点击右上角即可分享
微信分享提示