Flowable做会签和或签

考虑如下场景:

  • 某一流程进行到下一步需要指派给多人,当每个被指派的人都选择完成任务后,流程继续
  • 某一流程进行到下一步需要指派给多人,但是当某一个人选择完成任务后,流程继续
  • 指派任务时,其人数是不固定的,可能是2-5个人,在流程创建时是未知的;只有在启动流程实例时,指派人的具体信息可以确认

其中需求1就是常说的会签,需求2就是常说的或签;在AI和互联网的帮助下搜到了一些解决方案:

  • 多实例
  • 多实例 + 任务监听器 + 执行监听器
  • 网关 + 子流程
  • 使用candidateGroup的(或签)

这篇文章记录一下用最简单的多实例完成以上需求的过程,FLOWABLE版本7.0.0.M1
具体主要是参考官方代码中的单元测试类MultiInstanceTest中的testParallelUserTasksBasedOnCollection方法。

先看下具体代码:

BPMN

<?xml version="1.0" encoding="UTF-8"?>
<definitions id="definition" 
  xmlns="http://www.omg.org/spec/BPMN/20100524/MODEL"
  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xmlns:activiti="http://activiti.org/bpmn"
  targetNamespace="Examples">
  
  <process id="miParallelUserTasksBasedOnCollection">
  
    <startEvent id="theStart" />
    <sequenceFlow id="flow1" sourceRef="theStart" targetRef="miTasks" />
    
    <userTask id="miTasks" name="My Task ${loopCounter}" activiti:assignee="${assignee}">
      <multiInstanceLoopCharacteristics isSequential="false">
        <loopDataInputRef>assigneeList</loopDataInputRef>
        <inputDataItem name="assignee" />
        <completionCondition>${nrOfCompletedInstances/nrOfInstances >= 0.6 }</completionCondition>
      </multiInstanceLoopCharacteristics>
    </userTask>
    
    <sequenceFlow id="flow3" sourceRef="miTasks" targetRef="theEnd" />
    <endEvent id="theEnd" />
    
  </process>

</definitions>

单元测试方法

@Test
@Deployment
public void testParallelUserTasksBasedOnCollection() {
    List<String> assigneeList = Arrays.asList("kermit", "gonzo", "mispiggy", "fozzie", "bubba");
    String procId = runtimeService.startProcessInstanceByKey("miParallelUserTasksBasedOnCollection", CollectionUtil.singletonMap("assigneeList", assigneeList)).getId();

    List<org.flowable.task.api.Task> tasks = taskService.createTaskQuery().orderByTaskAssignee().asc().list();
    assertThat(tasks)
            .extracting(Task::getAssignee)
            .containsExactly("bubba", "fozzie", "gonzo", "kermit", "mispiggy");

    // Completing 3 tasks will trigger completioncondition
    taskService.complete(tasks.get(0).getId());
    taskService.complete(tasks.get(1).getId());
    taskService.complete(tasks.get(2).getId());
    assertThat(taskService.createTaskQuery().count()).isZero();
    assertProcessEnded(procId);
}

打开AI,解释下此段代码,

总结

这段代码测试了一个基于集合的并行用户任务的完成条件。具体步骤如下:

  1. 创建一个包含多个分配者的列表 assigneeList
  2. 启动一个流程实例,并传递 assigneeList 作为变量。
  3. 查询并断言任务的分配者顺序。
  4. 依次完成前3个任务。
  5. 验证所有任务都已完成,任务数量为0。
  6. 断言流程实例已经结束。

解释下工作流:

工作流解释

定义

  • 该BPMN文件定义了一个名为 miParallelUserTasksBasedOnCollection 的流程。

起始事件

  • 流程从一个起始事件 theStart 开始。

并行用户任务

  • 从起始事件通过一个顺序流 flow1 连接到一个并行用户任务 miTasks
  • 该用户任务 miTasks 的名称为 My Task ${loopCounter},其中 ${loopCounter} 是一个循环计数器,表示当前任务的索引。
  • 用户任务的分配者通过表达式 ${assignee} 动态设置。
  • 该用户任务配置了多实例循环特性:
    • isSequential="false" 表示这些任务是并行执行的。
    • loopDataInputRef="assigneeList" 指定了一个集合变量 assigneeList,用于生成多个任务实例。
    • inputDataItem name="assignee" 表示每个任务实例的分配者从 assigneeList 中获取。
    • completionCondition="${nrOfCompletedInstances/nrOfInstances >= 0.6 }" 表示当已完成的任务实例数占总任务实例数的比例达到或超过60%时,整个多实例任务将完成。

结束事件

  • 多实例用户任务完成后,通过一个顺序流 flow3 连接到结束事件 theEnd,表示流程结束。

总结

该BPMN工作流定义了一个基于集合的并行用户任务流程。流程从起始事件开始,生成多个并行用户任务,每个任务的分配者从 assigneeList 中获取。当已完成的任务实例数达到总任务实例数的60%时,多实例任务完成,流程结束。

在看下FLOWABLE官方文档对多实例的说明。

至此,掌握的事实如下,已基本可以满足需求,总结如下:

  • 可以使用loopDataInputRef和inputDataItem指定的变量创建多实例以及使用的参数名
  • 可以使用completionCondition以及内置变量nrOfCompletedInstances,nrOfInstances等来判断何时终结多实例。比如nrOfCompletedInstances == nrOfInstances即所有多实例都完成,即为会签;nrOfCompletedInstances >= 1即多实例有一个以上完成了,即为或签;当然也可以和例子中类似使用nrOfCompletedInstances/nrOfInstances >= 0.6来表示60%以上的人完成后,流程继续。
  • 如果loopDataInputRef和inputDataItem比较难以记住,可以使用标签flowable:collection="assigneeList" flowable:elementVariable="assignee"来达到同等效果

我们开始自己的流程,期望建立一个流程,第一个任务做会签,然后进行一步Service Task,第二个任务做或签,

流程代码如下:

<?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="simpleProcess" isExecutable="true">
        <startEvent id="startEvent1"/>
        <sequenceFlow id="flow1" sourceRef="startEvent1" targetRef="userTask1"/>
        <userTask id="userTask1" name="My User Task ${loopCounter}" flowable:assignee="${userId}">
            <multiInstanceLoopCharacteristics isSequential="false"
                                              flowable:collection="${userIds}"
                                              flowable:elementVariable="userId">
                <loopCardinality>${userIds.size()}</loopCardinality>
                <completionCondition>${nrOfCompletedInstances == nrOfInstances}</completionCondition>
            </multiInstanceLoopCharacteristics>
        </userTask>
        <sequenceFlow id="flow2" sourceRef="userTask1" targetRef="serviceTask1"/>
        <serviceTask id="serviceTask1" name="Print Variables" flowable:class="com.suoshi.astralstream.PrintVariablesDelegate"/>
        <sequenceFlow id="flow4" sourceRef="serviceTask1" targetRef="approvalTask"/>
        <userTask id="approvalTask" name="Approval Task ${loopCounter}" flowable:assignee="${approver}">
            <multiInstanceLoopCharacteristics isSequential="false"
                                              flowable:collection="${approvers}"
                                              flowable:elementVariable="approver">
                <loopCardinality>${approvers.size()}</loopCardinality>
                <completionCondition>${nrOfCompletedInstances >= 1}</completionCondition>
            </multiInstanceLoopCharacteristics>
        </userTask>
        <sequenceFlow id="flow5" sourceRef="approvalTask" targetRef="endEvent1"/>
        <endEvent id="endEvent1"/>
    </process>
    <bpmndi:BPMNDiagram id="BPMNDiagram_a">
        <bpmndi:BPMNPlane bpmnElement="simpleProcess" id="BPMNPlane_a">
            <bpmndi:BPMNShape bpmnElement="startEvent1" id="BPMNShape_startEvent1">
                <omgdc:Bounds height="36.0" width="36.0" x="100.0" y="100.0"/>
            </bpmndi:BPMNShape>
            <bpmndi:BPMNShape bpmnElement="userTask1" id="BPMNShape_userTask1">
                <omgdc:Bounds height="80.0" width="100.0" x="200.0" y="80.0"/>
            </bpmndi:BPMNShape>
            <bpmndi:BPMNShape bpmnElement="serviceTask1" id="BPMNShape_serviceTask1">
                <omgdc:Bounds height="80.0" width="100.0" x="350.0" y="80.0"/>
            </bpmndi:BPMNShape>
            <bpmndi:BPMNShape bpmnElement="approvalTask" id="BPMNShape_approvalTask">
                <omgdc:Bounds height="80.0" width="100.0" x="500.0" y="80.0"/>
            </bpmndi:BPMNShape>
            <bpmndi:BPMNShape bpmnElement="endEvent1" id="BPMNShape_endEvent1">
                <omgdc:Bounds height="36.0" width="36.0" x="700.0" y="100.0"/>
            </bpmndi:BPMNShape>
            <bpmndi:BPMNEdge bpmnElement="flow1" id="BPMNEdge_flow1">
                <omgdi:waypoint x="136.0" y="118.0"/>
                <omgdi:waypoint x="200.0" y="120.0"/>
            </bpmndi:BPMNEdge>
            <bpmndi:BPMNEdge bpmnElement="flow2" id="BPMNEdge_flow2">
                <omgdi:waypoint x="300.0" y="120.0"/>
                <omgdi:waypoint x="350.0" y="120.0"/>
            </bpmndi:BPMNEdge>
            <bpmndi:BPMNEdge bpmnElement="flow4" id="BPMNEdge_flow4">
                <omgdi:waypoint x="450.0" y="120.0"/>
                <omgdi:waypoint x="500.0" y="120.0"/>
            </bpmndi:BPMNEdge>
            <bpmndi:BPMNEdge bpmnElement="flow5" id="BPMNEdge_flow5">
                <omgdi:waypoint x="600.0" y="120.0"/>
                <omgdi:waypoint x="700.0" y="118.0"/>
            </bpmndi:BPMNEdge>
        </bpmndi:BPMNPlane>
    </bpmndi:BPMNDiagram>
</definitions>

单元测试代码

@Test
    public void testStartSimpleProcess() {
        // 部署流程定义
        Deployment deployment = repositoryService.createDeployment()
                .addClasspathResource("simple-process.bpmn20.xml")
                .deploy();

        // 设置任务变量,包括 assignee 列表
        List<String> userIds = new ArrayList<>();
        userIds.add("johnDoe");
        userIds.add("janeDoe");
        userIds.add("alice");

        // 设置审批者列表
        List<String> approvers = new ArrayList<>();
        approvers.add("bob");
        approvers.add("charlie");

        Map<String, Object> variables = new HashMap<>();
        variables.put("userIds", userIds);
        variables.put("approvers", approvers);

        // 启动流程实例
        ProcessInstance processInstance = runtimeService.startProcessInstanceByKey("simpleProcess", variables);

        // 断言流程实例是否成功启动
        assertNotNull(processInstance);
        System.out.println("流程实例ID: " + processInstance.getId());

        // 查询用户任务
        List<Task> tasks = taskService.createTaskQuery().processInstanceId(processInstance.getId()).list();
        assertEquals(userIds.size(), tasks.size());

        // 验证每个任务是否正确分配给指定的用户
        for (Task task : tasks) {
            System.out.println("用户任务ID: " + task.getId());
            System.out.println("任务名称: " + task.getName());
            System.out.println("任务分配给的执行人员: " + task.getAssignee());

            // 验证任务分配给的执行人员
            assertTrue(userIds.contains(task.getAssignee()));
        }

        // 模拟两个用户完成任务
        for (int i = 0; i < 2; i++) {
            Task task = tasks.get(i);
            taskService.complete(task.getId());
        }

        // 检查流程实例是否仍然处于活动状态
        boolean isEnded = runtimeService.createProcessInstanceQuery()
                .processInstanceId(processInstance.getId())
                .singleResult() == null;
        assertFalse(isEnded);

        // 处理最后一个人
        Task lastTask = tasks.get(tasks.size() - 1);
        taskService.complete(lastTask.getId());

        List<Task> approvalTasks = taskService.createTaskQuery().processInstanceId(processInstance.getId()).list();
        // 模拟只有一个人完成任务
        Task approvalTask = approvalTasks.get(0);
        taskService.complete(approvalTask.getId());

        // 检查流程实例是否已经结束
        boolean isEnded2 = runtimeService.createProcessInstanceQuery()
                .processInstanceId(processInstance.getId())
                .singleResult() == null;
        assertTrue(isEnded2);
    }

输出日志

流程实例ID: 3b57f0ad-b76a-11ef-bc19-2ea2d97adec2
用户任务ID: 3b5e5963-b76a-11ef-bc19-2ea2d97adec2
任务名称: My User Task 0
任务分配给的执行人员: johnDoe
用户任务ID: 3b5ea788-b76a-11ef-bc19-2ea2d97adec2
任务名称: My User Task 1
任务分配给的执行人员: janeDoe
用户任务ID: 3b5ece9d-b76a-11ef-bc19-2ea2d97adec2
任务名称: My User Task 2
任务分配给的执行人员: alice
Service Task 'Print Variables' triggered.
Variable 'userIds' = [johnDoe, janeDoe, alice]
Variable 'approvers' = [bob, charlie]

目前看已经可以完成第一个任务会签,第二个任务或签的需求。

FLOWABLE还有很多其他实现方式,待后续慢慢研究。Enjoy Coding!

©著作权归作者所有,转载或内容合作请联系作者
【社区内容提示】社区部分内容疑似由AI辅助生成,浏览时请结合常识与多方信息审慎甄别。
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容