学习记录556@flowable多人会签

前言

实际业务中的会签,代表一个任务节点由多个人进行审批,可能会衍生出多种情况:

  • 只要一个人审批后,就到下一个节点
  • 全部审批后,才能到下一个节点
  • 审批人数占比多少,就到下一个节点,本案例就选择这个最麻烦的。

在Flowable BPMN 用户手册中,”会签“的介绍在”多实例“的章节,也就是说,会签,其实就是多实例。

多实例活动(multi-instance activity)是在业务流程中,为特定步骤定义重复的方式。在编程概念中,多实例类似for each结构:可以为给定集合中的每一条目,顺序或并行地,执行特定步骤,甚至是整个子流程。多实例是一个普通活动,加上定义(被称作“多实例特性的”)额外参数,会使得活动在运行时被多次执行。

具体的可参考:https://tkjohn.github.io/flowable-userguide/#bpmnBusinessRuleTask
特别注意其描述的xml部分:
要将活动变成多实例,该活动的XML元素必须有multiInstanceLoopCharacteristics子元素

<multiInstanceLoopCharacteristics isSequential="false|true">
 ...
</multiInstanceLoopCharacteristics>

isSequential属性代表了活动的实例为顺序还是并行执行。
实例的数量在进入活动时,计算一次。有几种不同方法可以配置数量。一个方法是通过loopCardinality子元素,直接指定数字。

<multiInstanceLoopCharacteristics isSequential="false|true">
  <loopCardinality>5</loopCardinality>
</multiInstanceLoopCharacteristics>

也可以使用解析为正整数的表达式:

<multiInstanceLoopCharacteristics isSequential="false|true">
  <loopCardinality>${nrOfOrders-nrOfCancellations}</loopCardinality>
</multiInstanceLoopCharacteristics>

另一个定义实例数量的方法,是使用loopDataInputRef子元素,指定一个集合型流程变量的名字。对集合中的每一项,都会创建一个实例。可以使用inputDataItem子元素,将该项设置给该实例的局部变量。在下面的XML示例中展示:

<userTask id="miTasks" name="My Task ${loopCounter}" flowable:assignee="${assignee}">
  <multiInstanceLoopCharacteristics isSequential="false">
    <loopDataInputRef>assigneeList</loopDataInputRef>
    <inputDataItem name="assignee" />
  </multiInstanceLoopCharacteristics>
</userTask>

假设变量assigneeList包含[kermit, gonzo, fozzie]。上面的代码会创建三个并行的用户任务。每一个执行都有一个名为assignee的(局部)流程变量,含有集合中的一项,并在这个例子中被用于指派用户任务。

loopDataInputRef与inputDataItem的缺点是名字很难记,并且由于BPMN 2.0概要的限制,不能使用表达式。Flowable通过在multiInstanceCharacteristics上提供collection与elementVariable属性解决了这些问题:

<userTask id="miTasks" name="My Task" flowable:assignee="${assignee}">
  <multiInstanceLoopCharacteristics isSequential="true"
     flowable:collection="${myService.resolveUsersForTask()}" flowable:elementVariable="assignee" >
  </multiInstanceLoopCharacteristics>
</userTask>

请注意collection属性会作为表达式进行解析。如果表达式解析为字符串而不是一个集合,不论是因为本身配置的就是静态字符串值,还是表达式计算结果为字符串,这个字符串都会被当做变量名,在流程变量中用于获取实际的集合。

例如,下面的代码片段会要求集合存储在assigneeList流程变量中:

<userTask id="miTasks" name="My Task" flowable:assignee="${assignee}">
  <multiInstanceLoopCharacteristics isSequential="true"
     flowable:collection="assigneeList" flowable:elementVariable="assignee" >
  </multiInstanceLoopCharacteristics>
</userTask>

假如myService.getCollectionVariableName()返回字符串值,引擎就会用这个值作为变量名,获取流程变量保存的集合。

<userTask id="miTasks" name="My Task" flowable:assignee="${assignee}">
  <multiInstanceLoopCharacteristics isSequential="true"
     flowable:collection="${myService.getCollectionVariableName()}" flowable:elementVariable="assignee" >
  </multiInstanceLoopCharacteristics>
</userTask>

多实例活动在所有实例都完成时结束。然而,也可以指定一个表达式,在每个实例结束时进行计算。当表达式计算为true时,将销毁所有剩余的实例,并结束多实例活动,继续执行流程。这个表达式必须通过completionCondition子元素定义。

<userTask id="miTasks" name="My Task" flowable:assignee="${assignee}">
  <multiInstanceLoopCharacteristics isSequential="false"
     flowable:collection="assigneeList" flowable:elementVariable="assignee" >
    <completionCondition>${nrOfCompletedInstances/nrOfInstances >= 0.6 }</completionCondition>
  </multiInstanceLoopCharacteristics>
</userTask>

在这个例子里,会为assigneeList集合中的每个元素创建并行实例。当60%的任务完成时,其他的任务将被删除,流程继续运行。

接下来具体介绍本文的案例

案例中,只有一个风控审批的节点,审核人员是风控人员组的成员,审核人数占比超过50%就结束会签。

流程

在这里插入图片描述

<?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.7.2">
  <process id="shenpi1" name="审批1" isExecutable="true">
    <startEvent id="startEvent1" flowable:formFieldValidation="true"></startEvent>
    <userTask id="sid-B0A762DE-D600-4998-96D6-49F7BE676159" name="风控审批" flowable:candidateGroups="${风控人员组}" flowable:formFieldValidation="true">
      <multiInstanceLoopCharacteristics isSequential="false" flowable:collection="persons" flowable:elementVariable="person">
        <extensionElements></extensionElements>
        <completionCondition>${compCondition.getComCondition(execution)}</completionCondition>
      </multiInstanceLoopCharacteristics>
    </userTask>
    <sequenceFlow id="sid-0129BF6A-C57C-4383-A14C-879FD48EED4E" sourceRef="startEvent1" targetRef="sid-B0A762DE-D600-4998-96D6-49F7BE676159"></sequenceFlow>
    <endEvent id="sid-80CB6AF8-5DD5-4AC1-A1B2-05A5569B402D"></endEvent>
    <sequenceFlow id="sid-3349C437-5FF2-4565-B2CD-75CCA54FAF03" sourceRef="sid-B0A762DE-D600-4998-96D6-49F7BE676159" targetRef="sid-80CB6AF8-5DD5-4AC1-A1B2-05A5569B402D"></sequenceFlow>
  </process>
  <bpmndi:BPMNDiagram id="BPMNDiagram_shenpi1">
    <bpmndi:BPMNPlane bpmnElement="shenpi1" id="BPMNPlane_shenpi1">
      <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-B0A762DE-D600-4998-96D6-49F7BE676159" id="BPMNShape_sid-B0A762DE-D600-4998-96D6-49F7BE676159">
        <omgdc:Bounds height="80.0" width="100.0" x="225.0" y="135.0"></omgdc:Bounds>
      </bpmndi:BPMNShape>
      <bpmndi:BPMNShape bpmnElement="sid-80CB6AF8-5DD5-4AC1-A1B2-05A5569B402D" id="BPMNShape_sid-80CB6AF8-5DD5-4AC1-A1B2-05A5569B402D">
        <omgdc:Bounds height="28.0" width="28.0" x="435.0" y="161.00000216744158"></omgdc:Bounds>
      </bpmndi:BPMNShape>
      <bpmndi:BPMNEdge bpmnElement="sid-3349C437-5FF2-4565-B2CD-75CCA54FAF03" id="BPMNEdge_sid-3349C437-5FF2-4565-B2CD-75CCA54FAF03" flowable:sourceDockerX="50.0" flowable:sourceDockerY="40.0" flowable:targetDockerX="2.0" flowable:targetDockerY="14.0">
        <omgdi:waypoint x="324.9499999999799" y="175.0000006682945"></omgdi:waypoint>
        <omgdi:waypoint x="434.99999863624566" y="175.00000214068302"></omgdi:waypoint>
      </bpmndi:BPMNEdge>
      <bpmndi:BPMNEdge bpmnElement="sid-0129BF6A-C57C-4383-A14C-879FD48EED4E" id="BPMNEdge_sid-0129BF6A-C57C-4383-A14C-879FD48EED4E" flowable:sourceDockerX="15.0" flowable:sourceDockerY="15.0" flowable:targetDockerX="50.0" flowable:targetDockerY="40.0">
        <omgdi:waypoint x="129.9474255708386" y="177.71879843198602"></omgdi:waypoint>
        <omgdi:waypoint x="225.0" y="175.9365625"></omgdi:waypoint>
      </bpmndi:BPMNEdge>
    </bpmndi:BPMNPlane>
  </bpmndi:BPMNDiagram>
</definitions>

注意
在这里插入图片描述

这里有必要说一下设置的重点,我采用的是flowable-ui绘制的
在这里插入图片描述

  • 多实例类型:并行或者串行,案例中是并行的
  • 基数:创造几个实例,除非特别固定,否则不会用这个
  • 集合:很常用,比如案例中风控审批人员组有多少个人,就根据这个集合创造多少实例,也就是多少人来会签
  • 元素:对应集合中的元素,也就是每个风控人员,会使用变量保存下来
  • 完成条件:什么条件下会签结束,我这里传递了一个类的方法${compCondition.getComCondition(execution)},当有人会签后,就调用这个方法,计算会签完成比例是否大于0.5,如果是,就会签结束,没有完成的task自动结束。

代码核心

 @Transactional
    public void startProcess() {
        Map<String, Object> variables = new HashMap<String, Object>();
        //根据当前用户获取所在的用户组
        Group group = identityService.createGroupQuery().groupMember("三井寿1").singleResult();
        variables.put("风控人员组",group.getId());
        //获取当前组的人员集合
        List<String> persons = identityService.createUserQuery().memberOfGroup(group.getId()).list().stream().map((li) -> {
            return li.getId();
        }).collect(Collectors.toList());
        variables.put("persons",persons);
        runtimeService.startProcessInstanceByKey("shenpi1",variables);
    }

    //领取任务,act_ru_task表中ASSIGNEE_字段之前为空,谁领取就变成谁。
    @Transactional
    public void claimTask() {

        //根据当前用户获取所在的组
        Group group = identityService.createGroupQuery().groupMember("三井寿2").singleResult();
        //根据组查询任务,查询到没有分配处理人的任务就申领这个任务,然后结束循环
        List<Task> list = taskService.createTaskQuery().processInstanceId("a4bdf5bb-b57b-11ec-85bf-3c9c0f202230").taskCandidateGroup(group.getId()).list();
        if (list != null){
            for (int i = 0; i < list.size(); i++) {
                Task task = list.get(i);
                if (task.getAssignee() == null){
                    taskService.claim(task.getId(),"三井寿2");//领取任务
                    System.out.println(task.getAssignee());
                    break;
                }
            }
        }
    }
    //    完成任务
    @Transactional
    public void completeTask() {
        Task task = taskService.createTaskQuery().processInstanceId("a4bdf5bb-b57b-11ec-85bf-3c9c0f202230").taskAssignee("三井寿2").singleResult();
        if (task != null){
            taskService.complete(task.getId());
        }
    }

    @Transactional
    public void addGroupAndUser() {

        //实际开发中,可将本身的用户表、角色表、部门表以及之间的关系同步到flowable的act_id_user、act_id_group、act_id_membership表中
        //其中角色和部门可当作组

        User user3 =identityService.newUser("三井寿1");
        user3.setFirstName("三井");
        user3.setLastName("寿1");
        identityService.saveUser(user3);//注意必须这样保存

        User user4 =identityService.newUser("三井寿2");
        user4.setFirstName("三井");
        user4.setLastName("寿2");
        identityService.saveUser(user4);//注意必须这样保存

        User user5 =identityService.newUser("三井寿3");
        user5.setFirstName("三井");
        user5.setLastName("寿3");
        identityService.saveUser(user5);//注意必须这样保存

        Group group2 = identityService.newGroup("风控人员组");
        group2.setName("风控员");
        identityService.saveGroup(group2);//注意必须这样保存

        //创建关联关系
        identityService.createMembership("三井寿1","风控人员组");
        identityService.createMembership("三井寿2","风控人员组");
        identityService.createMembership("三井寿3","风控人员组");
完成条件代码
package com.example.flowable.service;

import org.flowable.engine.delegate.DelegateExecution;
import org.springframework.stereotype.Component;

import java.io.Serializable;

@Component("compCondition")
public class CompCondition implements Serializable {
//    多实例活动在所有实例都完成时结束。
//    然而,也可以指定一个表达式(或类的方法),在每个实例结束时进行计算。
//    当表达式计算为true时,将销毁所有剩余的实例,并结束多实例活动,继续执行流程。
//    这个表达式必须通过completionCondition子元素定义。
    public boolean getComCondition(DelegateExecution execution){
        Object nrOfInstances = execution.getVariable("nrOfInstances");//实例总数
        Object nrOfActiveInstances = execution.getVariable("nrOfActiveInstances");//未完成的实例
        Object nrOfCompletedInstances = execution.getVariable("nrOfCompletedInstances");//已完成实例
        System.out.println("总实例数量"+Integer.parseInt(nrOfCompletedInstances.toString()));
        System.out.println("未完成的实例"+Integer.parseInt(nrOfActiveInstances.toString()));
        System.out.println("已完成实例"+Integer.parseInt(nrOfCompletedInstances.toString()));
        if (Float.parseFloat(nrOfCompletedInstances.toString())/Float.parseFloat(nrOfInstances.toString()) > 0.5){
            return true;//如果完成的比例高于50%就返回ture,代表会签结束,没有完成的任务就自动结束了
        }else {
            return false;//如果完成的比例不高于50%就返回false,还需要继续会签
        }
    }
}

测试与内部机制思考

当部署后开启实例,在风控审批节点创造三个任务,因为会签组有三个人。
在这里插入图片描述
我们分别用会签组成员去领取任务并完成,当第一个完成后,还剩两个任务。
在这里插入图片描述
然后再完成一个任务,任务列表就没有了,原因是,根据”完成条件代码“,超过了50%的成员会签了,其余任务就自动结束。
在这里插入图片描述
可以查看act_hi_taskinst 历史任务表,确实有个任务没有申领,就结束了。
在这里插入图片描述

对我而言,我必须用自己的钱支持自己的观点。我的亏损已经教会我,在无法确定自己必须撤退前,我根本不应该前进。但如果我不前进,我就不会有任何行动。讲这一点,我意思并不是说,当一个人犯错的时候他不应该去止损。他应该这样做,但那不能让他变得优柔寡断。我一辈子都在犯错,但是在亏损中我收获了经验,积累了诸多宝贵的教训。我破产过好多次,但我的亏损从来都不是彻底的失败,否则我现在也不会在这里了。我一直相信我有其他机会,而且犯过的错误我不会再犯第二次。只有一件事情可以让我相信自己犯错误了,那就是赔钱。只有赚到了钱,我才是正确的,这就是投机

  • 4
    点赞
  • 14
    收藏
    觉得还不错? 一键收藏
  • 18
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 18
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值