深入探究Camunda加签问题

963 阅读4分钟

前言

这里我们先抛出两个大问题,整篇文章针对这两个大问题再详细解析。

首先我们在设计流程定义时,流程节点可能是或签也可能是会签

会签:指同一个审批节点设置多个人,如ABC三人,三人会同时收到审批,需全部同意之后,审批才可到下一审批节点。
或签:指同一个审批节点设置多个人,如ABC三人,三人会同时收到审批,只要其中任意一人审批即可到下一审批节点。

当前会签也可以是比例签,比如节点上的审批人同意的超过60%就算通过。此时我们需要考虑的问题如下:

问题一:被加签人如何加到节点上

问题二:加签后是否会影响节点的通过【不同通过规则的节点】

如何加签

这里还需要区分设置审批人的方式:

  1. 在发起流程实例时,一次性将所有节点的审批人全部设置完成
  2. 通过监听器来设置审批人

在设计流程定义时,节点上主要设置这几个参数

  • 1是分配到待办任务的人,变量名和3保持一致;
  • 2是我们要传入的参数名,
  • 3是用来遍历2中集合的变量名,
  • 4是节点通过的条件。

关于怎么发布流程定义可以查看我之前的文章,不赘述了。

一次性设置审批人

    @GetMapping("/startProcessInstance")
    public String startProcessInstance(@RequestParam("processDefineKey")String processDefineKey,
                                     @RequestParam("businessKey")String businessKey) {
        Map<String, Object> map = new HashMap<>();
        List<String> userOneList = new ArrayList<>();
        userOneList.add("zhangsan");
        userOneList.add("lisi");
        List<String> userTwoList = new ArrayList<>();
        userTwoList.add("wangwu");
        userTwoList.add("maliu");
        map.put("initiator","lonewalker");
        map.put("userOneList", userOneList);
        map.put("userTwoList", userTwoList);
        ProcessInstance processInstance = runtimeService.startProcessInstanceByKey(processDefineKey, businessKey, map);
        //默认把发起人节点审批通过
        Task task = taskService.createTaskQuery().processInstanceId(processInstance.getProcessInstanceId()).singleResult();
        String taskId = task.getId();
        taskService.complete(taskId);
        return processInstance.getProcessInstanceId();
    }

请求接口

来看一下流程图,生成两个用户任务

我们开始加签,这里流程跑到第二个节点时会创建和userOneList.size 的Task,想在这个节点加签,就得想办法让这个事件再触发一次。

    @GetMapping("/addSign")
    public void addSign(@RequestParam("processInstanceId")String processInstanceId,
                        @RequestParam("nodeId")String nodeId,
                        @RequestParam("userId")String userId){
        runtimeService.createProcessInstanceModification(processInstanceId)
                .startBeforeActivity(nodeId)
                .setVariable("userOne",userId)
                .setAnnotation("加签")
                .execute();
    }

请求接口后查看已经成功加签

按节点设置审批人

此时我们需要通过执行监听器来完成审批人的设置,两种监听器的区别可查看此篇

修改流程定义,在或签节点之前的连线上设置执行监听器。

自定义一个执行监听器

@Component
public class CustomExecutionListener implements ExecutionListener {
    @Override
    public void notify(DelegateExecution execution) throws Exception {
        System.out.println("触发了执行监听器");
        List<String> userOneList = new ArrayList<>();
        userOneList.add("zhangsan");
        userOneList.add("lisi");
        execution.setVariable("userOneList",userOneList);
    }
}

发起流程实例的接口中把或签节点审批人去掉

然后部署,发起流程实例

此时我们加签的逻辑和之前一样,但是实现不一样了:

    @GetMapping("/addSign")
    public void addSign(@RequestParam("processInstanceId")String processInstanceId,
                        @RequestParam("nodeId")String nodeId){
        //获取实例的流程定义 这里主要是为了拿到节点前的那条线的Id
        ProcessInstance processInstance = runtimeService.createProcessInstanceQuery().processInstanceId(processInstanceId).singleResult();
        BpmnModelInstance bpmnModel = repositoryService.getBpmnModelInstance(processInstance.getProcessDefinitionId());
        ModelElementInstance modelElement = bpmnModel.getModelElementById(nodeId);
        UserTask userTask = (UserTask) modelElement;
        Collection<SequenceFlow> incoming = userTask.getIncoming();
        String transitionId = "";
        if (incoming.stream().findFirst().isPresent()) {
            transitionId = incoming.stream().findFirst().get().getId();
        } else {
            throw new ProcessException(ErrorCodeEnum.PROC_TASK_REJECT_FAILURE);
        }
        runtimeService.createProcessInstanceModification(processInstanceId)
                .setAnnotation("加签")
                .startTransition(transitionId)
                .execute();
    }

重新触发或签节点前连线上的执行监听器,然后加了一个sunjiu

调用加签接口后:

问题:

此时我模拟sunjiu审批,因为是或签节点按正常情况,流程会到会签节点,或签节点结束。

​刚刚加签的那个人确实审批通过了,也到会签节点了,但是原先的两个人还停留在或签,看一下原因

因为重新解析了userOneList,导致加签的任务和原来的任务它们的父环节实例不一致。

​这是不是意味着,这两个人的其中一人审批都会再次触发会签节点前连线上的执行监听器。这明显不是我们想要的。改一下审批接口

    @GetMapping("/agreeTask")
    public  void agreeTask(@RequestParam("taskId")String taskId){
        Task task = taskService.createTaskQuery().taskId(taskId).singleResult();
        String processInstanceId = task.getProcessInstanceId();
        //完成任务
        taskService.complete(taskId);
        List<Task> activeTaskList = taskService.createTaskQuery().processInstanceId(processInstanceId).active().list();
        if (CollUtil.isNotEmpty(activeTaskList)){
            //如果nodeIdSet大于1 就说明下一节点开启待办任务了,而当前节点还有待办任务  说明刚刚处理的这个任务是或签操作
            Set<String> nodeIdSet = activeTaskList.stream().map(Task::getTaskDefinitionKey).collect(Collectors.toSet());
            if (nodeIdSet.size() > 1){
                //查询当前节点未完成的实例
                List<HistoricActivityInstance> unfinishedInstanceList = historyService.createHistoricActivityInstanceQuery().
                        processInstanceId(processInstanceId).activityId(task.getTaskDefinitionKey()).unfinished().list();

                if (CollUtil.isNotEmpty(unfinishedInstanceList)){
                    //说明或签节点有加签
                    for (HistoricActivityInstance instance : unfinishedInstanceList) {
                        runtimeService.createProcessInstanceModification(processInstanceId).cancelActivityInstance(instance.getId()).execute();
                    }
                }
            }
        }
    }

重新发起一条流程实例,将上述操作再走一遍:

注意:这种情况下可以一次性加多个人,但是会签就不行了,因为这种方式加签会产生多个multiInstanceBody 。 比如会签节点现在有两个审批人a、b,那它们俩的multiInstanceBody 是同一个,但是如果我用上述加签方式加签 c 后会又产生一个multiInstanceBody,当c审批通过时会触发下一节点连线上的执行监听器,a、b审批通过后又会触发一次

此时就要利用好这个Assignee变量

  @GetMapping("/addSign")    
public void addSign(@RequestParam("processInstanceId")String processInstanceId,
                        @RequestParam("nodeId")String nodeId,
                        @RequestParam("userId")String userId){
          runtimeService.createProcessInstanceModification(processInstanceId)
                  .startBeforeActivity(nodeId)
                  .setVariable("userTwo",userId)
                  .execute();
    }

个人觉得最好的方式还是这种,而上述的也是为了提供给大家更好的思路,毕竟需求都是不一样的

扩展

有朋友会疑问为什么不用这个参数,这个相当于事先知道这个节点上有几个审批人,比如我设置5,那到这个节点就会创建5个待办任务,那如果是角色呢?所以才用上述的监听器去查询角色对应的人数,从而实现动态设置审批人。