Camunda流程驳回至上一节点

2023-11-14


前言

Camunda驳回至上一节点,网上大多都是回退至开始节点,这样逻辑比较简单清晰。但实际使用中,往往需要驳回至上一节点,甚至需要连续驳回多次。
流程驳回关键的一步就是获取到要回退到的节点。其中,需要理解的是(假设流程节点是1,2,3,4,5这样顺序的流程),如果当前已办历史节点是{1,2,3},如果4节点执行了驳回操作,那么历史节点会变成{1,2,3,4},而当前待办节点是3, 节点4执行了驳回操作,4同样会出现在历史执行节点中。
也就是说,当历史节点是{1,2,3,4}时,有两种可能性:一种是常规的顺序执行,节点3执行通过到达4节点,当前待办节点是4;另外一种是节点4执行了驳回操作,当前待办节点是3。


  • 思路一(实现代码见getLastNode):
    分两种情况:
    1、当前节点不在历史节点里
    2、当前节点在历史节点里
    假设,已办历史节点 resultList={1,2,3}
    (1)当前节点是4,表示3是完成节点,4驳回需要回退到3
    (2)当前节点是2,表示3是驳回节点,3驳回到当前2节点,2驳回需要回退到1
    其他驳回过的情况也都包含在情况2中。

  • 思路二:
    假设,已办历史节点 resultList={1,2,3}
    无论当前节点在不在历史节点里,一律将当前节点追加到已办历史节点列表中,再调用currentNodeInHis方法获取上一节点。
    (1)当前节点是4,追加后 resultList={1,2,3,4}
    (2)当前节点是2,追加后 resultList={1,2,3,2}

一、版本

camunda : 7.15.0
spring-boot : 2.4.3
spring-cloud :2020.0.1

二、实现

1、回退至上一节点

代码如下:
引入的jar包

import io.swagger.annotations.ApiOperation;
import org.camunda.bpm.engine.HistoryService;
import org.camunda.bpm.engine.RuntimeService;
import org.camunda.bpm.engine.TaskService;
import org.camunda.bpm.engine.history.HistoricActivityInstance;
import org.camunda.bpm.engine.runtime.ActivityInstance;
import org.camunda.bpm.engine.task.Task;
import org.springframework.web.bind.annotation.*;

import javax.annotation.Resource;
import java.util.*;

实现方法
其中RejectBean 实体类有三个属性:processInstanceId, currentUserId, rejectComment

public Result rejectToLastNode(@RequestBody RejectBean rejectBean) {
        //获取当前task
        Task task = taskService.createTaskQuery()
                .taskAssignee(rejectBean.getCurrentUserId()) //当前登录用户的id
                .processInstanceId(rejectBean.getProcessInstanceId())
                .singleResult();
        ActivityInstance tree = runtimeService.getActivityInstance(rejectBean.getProcessInstanceId());
        //获取所有已办用户任务节点
        List<HistoricActivityInstance> resultList = historyService
                .createHistoricActivityInstanceQuery()
                .processInstanceId(rejectBean.getProcessInstanceId())
                .activityType("userTask")
                .finished()
                .orderByHistoricActivityInstanceEndTime()
                .asc()
                .list();
        if(null == resultList || resultList.size() == 0){
            return ResultFactory.buildFailResult("当前任务无法驳回!");
        }
        //得到第一个任务节点的id
        HistoricActivityInstance historicActivityInstance = resultList.get(0);
        String startActId = historicActivityInstance.getActivityId();
        if(startActId.equals(task.getTaskDefinitionKey())){
            return ResultFactory.buildFailResult("开始节点无法驳回!");
        }
        //得到上一个任务节点的ActivityId和待办人
        Map<String,String> lastNode = getLastNode(resultList,task.getTaskDefinitionKey());
        if(null == lastNode){
            return ResultFactory.buildFailResult("回退节点异常!");
        }
        String toActId = lastNode.get("toActId");
        String assignee = lastNode.get("assignee");
        //设置流程中的可变参数
        Map<String, Object> taskVariable = new HashMap<>(2);
        taskVariable.put("user", assignee);
        //taskVariable.put("formName", "流程驳回");
        taskService.createComment(task.getId(), rejectBean.getProcessInstanceId(), "驳回原因:" + rejectBean.getRejectComment());
        runtimeService.createProcessInstanceModification(rejectBean.getProcessInstanceId())
                .cancelActivityInstance(getInstanceIdForActivity(tree, task.getTaskDefinitionKey()))//关闭相关任务
                .setAnnotation("进行了驳回到上一个任务节点操作")
                .startBeforeActivity(toActId)//启动目标活动节点
                .setVariables(taskVariable)//流程的可变参数赋值
                .execute();
        return ResultFactory.buildSuccessResult(null);
    }

    private String getInstanceIdForActivity(ActivityInstance activityInstance, String activityId) {
        ActivityInstance instance = getChildInstanceForActivity(activityInstance, activityId);
        if (instance != null) {
            return instance.getId();
        }
        return null;
    }

    private ActivityInstance getChildInstanceForActivity(ActivityInstance activityInstance, String activityId) {
        if (activityId.equals(activityInstance.getActivityId())) {
            return activityInstance;
        }
        for (ActivityInstance childInstance : activityInstance.getChildActivityInstances()) {
            ActivityInstance instance = getChildInstanceForActivity(childInstance, activityId);
            if (instance != null) {
                return instance;
            }
        }
        return null;
    }

    /**
     * 获取上一节点信息
     * 分两种情况:
     * 1、当前节点不在历史节点里
     * 2、当前节点在历史节点里
     * 比如,resultList={1,2,3}
     *     (1)当前节点是4,表示3是完成节点,4驳回需要回退到3
     *     (2)当前节点是2,表示3是驳回节点,3驳回到当前2节点,2驳回需要回退到1
     * 其他驳回过的情况也都包含在情况2中。
     *
     * @param resultList 历史节点列表
     * @param currentActivityId 当前待办节点ActivityId
     * @return 返回值:上一节点的ActivityId和待办人(toActId, assignee)
     */
    private static Map<String,String> getLastNode(List<HistoricActivityInstance> resultList, String currentActivityId){
        Map<String,String> backNode = new HashMap<>();
        //新建一个有序不重复集合
        LinkedHashMap<String,String> linkedHashMap = new LinkedHashMap();
        for(HistoricActivityInstance hai : resultList){
            linkedHashMap.put(hai.getActivityId(),hai.getAssignee());
        }
        //分两种情况:当前节点在不在历史节点里面,当前节点在历史节点里
        //情况1、当前节点不在历史节点里
        int originSize = resultList.size();
        int duplicateRemovalSize = linkedHashMap.size();
        //判断历史节点中是否有重复节点
        //if(originSize == duplicateRemovalSize){
            boolean flag = false;
            for(Map.Entry entry: linkedHashMap.entrySet()){
                if(currentActivityId.equals(entry.getKey())){
                    flag = true;
                    break;
                }
            }
//            if(flag){
//                //当前节点在历史节点里:最后一个节点是回退节点
//                return currentNodeInHis(linkedHashMap, currentActivityId);
//            }
            if(!flag) {
                //当前节点不在历史节点里:最后一个节点是完成节点
                HistoricActivityInstance historicActivityInstance = resultList.get(originSize - 1);
                backNode.put("toActId", historicActivityInstance.getActivityId());
                backNode.put("assignee", historicActivityInstance.getAssignee());
                return backNode;
            }
        //}
        //情况2、当前节点在历史节点里(已回退过的)
        return currentNodeInHis(linkedHashMap, currentActivityId);
    }

    private static Map<String,String> currentNodeInHis(LinkedHashMap<String,String> linkedHashMap,String currentActivityId){
        //情况2、当前节点在历史节点里(已回退过的)
        Map<String,String> backNode = new HashMap<>();
        ListIterator<Map.Entry<String,String>> li = new ArrayList<>(linkedHashMap.entrySet()).listIterator();
        //System.out.println("已回退过的");
        while (li.hasNext()){
            Map.Entry<String,String> entry = li.next();
            if(currentActivityId.equals(entry.getKey())){
                li.previous();
                Map.Entry<String,String> previousEntry = li.previous();
                backNode.put("toActId",previousEntry.getKey());
                backNode.put("assignee",previousEntry.getValue());
                return backNode;
            }
        }
        return null;
    }

2、回退至开始节点

public Result rejectToFirstNode(@RequestBody RejectBean rejectBean) {
        //String rejectMessage="项目的金额款项结算不正确";
        Task task = taskService.createTaskQuery()
                .taskAssignee(rejectBean.getCurrentUserId()) //当前登录用户的id
                .processInstanceId(rejectBean.getProcessInstanceId())
                .singleResult();
        ActivityInstance tree = runtimeService.getActivityInstance(rejectBean.getProcessInstanceId());
        List<HistoricActivityInstance> resultList = historyService
                .createHistoricActivityInstanceQuery()
                .processInstanceId(rejectBean.getProcessInstanceId())
                .activityType("userTask")
                .finished()
                .orderByHistoricActivityInstanceEndTime()
                .asc()
                .list();
        if(null == resultList || resultList.size()<2){
            return ResultFactory.buildFailResult("第一个用户节点无法驳回!");
        }
        //得到第一个任务节点的id
        HistoricActivityInstance historicActivityInstance = resultList.get(0);
        String toActId = historicActivityInstance.getActivityId();
        String assignee = historicActivityInstance.getAssignee();
        //设置流程中的可变参数
        Map<String, Object> taskVariable = new HashMap<>(2);
        taskVariable.put("user", assignee);
        //taskVariable.put("formName", "流程驳回");
        taskService.createComment(task.getId(), rejectBean.getProcessInstanceId(), "驳回原因:" + rejectBean.getRejectComment());
        runtimeService.createProcessInstanceModification(rejectBean.getProcessInstanceId())
                .cancelActivityInstance(getInstanceIdForActivity(tree, task.getTaskDefinitionKey()))//关闭相关任务
                .setAnnotation("进行了驳回到第一个任务节点操作")
                .startBeforeActivity(toActId)//启动目标活动节点
                .setVariables(taskVariable)//流程的可变参数赋值
                .execute();
        return ResultFactory.buildSuccessResult(null);
    }

3、测试方法

测试时只需给getAcitvityId和getAssingee返回值赋值即可,本身也就只需要这两个属性

public static void main(String[] args) {
        HistoricActivityInstance hai1 = new HistoricActivityInstance() {
            @Override
            public String getId() {
                return "act-1";
            }

            @Override
            public String getParentActivityInstanceId() {
                return null;
            }

            @Override
            public String getActivityId() {
                return "act-1";
            }

            @Override
            public String getActivityName() {
                return null;
            }

            @Override
            public String getActivityType() {
                return null;
            }

            @Override
            public String getProcessDefinitionKey() {
                return null;
            }

            @Override
            public String getProcessDefinitionId() {
                return null;
            }

            @Override
            public String getRootProcessInstanceId() {
                return null;
            }

            @Override
            public String getProcessInstanceId() {
                return null;
            }

            @Override
            public String getExecutionId() {
                return null;
            }

            @Override
            public String getTaskId() {
                return null;
            }

            @Override
            public String getCalledProcessInstanceId() {
                return null;
            }

            @Override
            public String getCalledCaseInstanceId() {
                return null;
            }

            @Override
            public String getAssignee() {
                return "user-1";
            }

            @Override
            public Date getStartTime() {
                return null;
            }

            @Override
            public Date getEndTime() {
                return null;
            }

            @Override
            public Long getDurationInMillis() {
                return null;
            }

            @Override
            public boolean isCompleteScope() {
                return false;
            }

            @Override
            public boolean isCanceled() {
                return false;
            }

            @Override
            public String getTenantId() {
                return null;
            }

            @Override
            public Date getRemovalTime() {
                return null;
            }
        };
        HistoricActivityInstance hai2 = new HistoricActivityInstance() {
            @Override
            public String getId() {
                return "act-2";
            }

            @Override
            public String getParentActivityInstanceId() {
                return null;
            }

            @Override
            public String getActivityId() {
                return "act-2";
            }

            @Override
            public String getActivityName() {
                return null;
            }

            @Override
            public String getActivityType() {
                return null;
            }

            @Override
            public String getProcessDefinitionKey() {
                return null;
            }

            @Override
            public String getProcessDefinitionId() {
                return null;
            }

            @Override
            public String getRootProcessInstanceId() {
                return null;
            }

            @Override
            public String getProcessInstanceId() {
                return null;
            }

            @Override
            public String getExecutionId() {
                return null;
            }

            @Override
            public String getTaskId() {
                return null;
            }

            @Override
            public String getCalledProcessInstanceId() {
                return null;
            }

            @Override
            public String getCalledCaseInstanceId() {
                return null;
            }

            @Override
            public String getAssignee() {
                return "user-2";
            }

            @Override
            public Date getStartTime() {
                return null;
            }

            @Override
            public Date getEndTime() {
                return null;
            }

            @Override
            public Long getDurationInMillis() {
                return null;
            }

            @Override
            public boolean isCompleteScope() {
                return false;
            }

            @Override
            public boolean isCanceled() {
                return false;
            }

            @Override
            public String getTenantId() {
                return null;
            }

            @Override
            public Date getRemovalTime() {
                return null;
            }
        };
        HistoricActivityInstance hai3 = new HistoricActivityInstance() {
            @Override
            public String getId() {
                return "act-3";
            }

            @Override
            public String getParentActivityInstanceId() {
                return null;
            }

            @Override
            public String getActivityId() {
                return "act-3";
            }

            @Override
            public String getActivityName() {
                return null;
            }

            @Override
            public String getActivityType() {
                return null;
            }

            @Override
            public String getProcessDefinitionKey() {
                return null;
            }

            @Override
            public String getProcessDefinitionId() {
                return null;
            }

            @Override
            public String getRootProcessInstanceId() {
                return null;
            }

            @Override
            public String getProcessInstanceId() {
                return null;
            }

            @Override
            public String getExecutionId() {
                return null;
            }

            @Override
            public String getTaskId() {
                return null;
            }

            @Override
            public String getCalledProcessInstanceId() {
                return null;
            }

            @Override
            public String getCalledCaseInstanceId() {
                return null;
            }

            @Override
            public String getAssignee() {
                return "user-3";
            }

            @Override
            public Date getStartTime() {
                return null;
            }

            @Override
            public Date getEndTime() {
                return null;
            }

            @Override
            public Long getDurationInMillis() {
                return null;
            }

            @Override
            public boolean isCompleteScope() {
                return false;
            }

            @Override
            public boolean isCanceled() {
                return false;
            }

            @Override
            public String getTenantId() {
                return null;
            }

            @Override
            public Date getRemovalTime() {
                return null;
            }
        };
        HistoricActivityInstance hai4 = new HistoricActivityInstance() {
            @Override
            public String getId() {
                return "act-4";
            }

            @Override
            public String getParentActivityInstanceId() {
                return null;
            }

            @Override
            public String getActivityId() {
                return "act-4";
            }

            @Override
            public String getActivityName() {
                return null;
            }

            @Override
            public String getActivityType() {
                return null;
            }

            @Override
            public String getProcessDefinitionKey() {
                return null;
            }

            @Override
            public String getProcessDefinitionId() {
                return null;
            }

            @Override
            public String getRootProcessInstanceId() {
                return null;
            }

            @Override
            public String getProcessInstanceId() {
                return null;
            }

            @Override
            public String getExecutionId() {
                return null;
            }

            @Override
            public String getTaskId() {
                return null;
            }

            @Override
            public String getCalledProcessInstanceId() {
                return null;
            }

            @Override
            public String getCalledCaseInstanceId() {
                return null;
            }

            @Override
            public String getAssignee() {
                return "user-4";
            }

            @Override
            public Date getStartTime() {
                return null;
            }

            @Override
            public Date getEndTime() {
                return null;
            }

            @Override
            public Long getDurationInMillis() {
                return null;
            }

            @Override
            public boolean isCompleteScope() {
                return false;
            }

            @Override
            public boolean isCanceled() {
                return false;
            }

            @Override
            public String getTenantId() {
                return null;
            }

            @Override
            public Date getRemovalTime() {
                return null;
            }
        };
        List<HistoricActivityInstance> resultList = new ArrayList<>();
        resultList.add(hai1);
        resultList.add(hai2);
        resultList.add(hai3);
//        resultList.add(hai4);
//        resultList.add(hai3);
        Map<String,String> map = getLastNode(resultList,"act-2");
        System.out.println("toActId ==> "+map.get("toActId"));
        System.out.println("assignee ==> "+map.get("assignee"));

    }
 
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

Camunda流程驳回至上一节点 的相关文章

随机推荐

  • Unity脚本实现——触摸屏3D模型,随单根手指,无死角旋转(Input的GetTouch方法和touchCount属性)

    Unity脚本实现模型360度旋转 参考别人随手指绕Y轴转动 添加了绕X轴转动 using System Collections using System Collections Generic using UnityEngine publ
  • document 使用方法介绍

    document节点是文档的根节点 每张网页都有自己的document节点 属性 1 document doctype 它是一个对象 包含了当前文档类型 Document Type Declaration 简写DTD 信息 2 docume
  • Tessy — 嵌入式软件单元测试/ 集成测试工具

    Tessy 源自戴姆勒 奔驰公司的软件技术实验室 由德国Hitex 公司负责全球销售及技术支持服务 是一款针对嵌入式软件进行单元 集成测试的工具 它可以对C C 代码进行单元 集成测试 可以自动化搭建测试环境 执行测试 评估测试结果并生成测
  • SpringBoot 线程池的使用

    前言 最近在做订单模块 用户购买服务类产品之后 需要进行预约 预约成功之后分别给商家和用户发送提醒短信 考虑发短信耗时的情况所以我想用异步的方法去执行 于是就在网上看见了Spring的 Async了 但是遇到了许多问题 使得 Async无效
  • flutter Flex Wrap Stack Align布局

    1 flex布局 Flex direction Axis horizontal 水平反向 direction不能为空 direction Axis vertical 垂直反向 Expanded flex 1 实现代码如下 child Con
  • C语言-宏定义

    C语言 宏定义 1 宏定义是什么 2 宏定义怎么用 2 1 宏定义常量 2 1 1 预定义宏 2 1 2 自定义宏 2 2 带参数的宏 2 3 编译预处理 3 宏展开 4 编译预处理指令 1 宏定义是什么 宏是用来表示一段代码的标识符 宏也
  • how to unzip split file

    1 how to unzip split file cat zipfile tar gz tar zxv
  • springBoot整合log4j2

    文章目录 什么是log4j2 springBoot依赖的引入 接下来是log4j2的示例配置 首先在application yml制定采用哪个配置文件 在resources目录下新建log4j2 xml文件 什么是log4j2 Apache
  • Linux系统:stress-ng测压工具

    目录 一 理论 1 stress工具简介与安装 2 语法及参数 3 具体安装 二 实验 1 运行8 cpu 4 fork 5 hdd 4 io 50 vm 10小时 2 CPU测试 3 内存测试 4 IO测试 5 磁盘及I O测试 三 问题
  • Java同步代码块详解

    目录 一 什么是内置锁 二 什么是重入 三 活跃性与性能 四 对象的共享 1 可见性 2 非原子的64位操作 3 volatile变量 一 什么是内置锁 Java提供了一种内置的锁机制来支持原子性 同步代码块 同步代码块包含两部分 一个作为
  • SQL,如何更新表结构

    We can alter an existing table structure using the ALTER TABLE command followed by the alteration you want to make 我们可以使
  • 【Unity】[帮助文档] AddForce函数详解,参数ForceMode(Acceleration、Force、Impulse 和 VelocityChange)的选择

    背景 经常忘 经常查 倒不如我自己写一篇给自己方便参考 毕竟每次都在某N站查出来的都是不知道互抄到哪一年的机翻文章 本文涉及代码与测试参考unity版本为2021 3 AddForce 用于对rigidbody组件对象添加力的作用 其参数决
  • 编程题思路1

    1反转链表 2节点两两反转 3判断链表是否有环 1 0 5毫秒内是否出现Null 2 set中查重 3 快慢指针 4匹配左右括号 5实时判断第K大的元素 大顶堆 实时排序 6 乱序判断 法一 sort NlogN return sorted
  • 手机屏幕的DPI和PPI有什么区别?

    为什么有的手机厂商在屏幕参数一栏标注PPI 而有的手机却标注DPI 这其中又有什么猫腻呢 不同的标注方法会对手机产生那些影响 PPI和DPI的区别是什么 分别都是如何计算的 对你的视觉体验会产生多大的影响呢 DPI即dot per inch
  • 抽取式文档摘要方法(一)

    1 抽取式 从文档中抽取已有句子形成摘要 实现简单 能保证句子的可读性 可看作一个组合优化问题 可与语句压缩一并进行 可看作混合式方法 2 抽取式文档摘要的关键技术 重要信息评估 冗余信息的过滤 碎片化信息的聚合 多源信息的篇章组织 其中单
  • JavaScript——操作浏览器窗口

    学习内容 今天学习了alert提示框 提示框中的内容 就是alert后边小括号中的内容 例如 alert 我要学JavaScript alert 我要学习 学习总结 日常小总结 例如 后面的分号 可以随便去掉 不影响运行效果 不能去掉小括号
  • 在“信创”大背景下 美信时代的业务思路

    数据量的飞速增长 并不是单纯对云端进行扩容就可以完全应对的 大量的数据汇聚到云端进行处理 带来的延迟逐渐让人无法忍受 在移动智能设备终端 延迟往往令实时互动滞后 严重影响体验 在制造业领域 监控瞬时数据量巨大 留给异常数据的处理窗口很小 传
  • lvgl小部件-基础对象学习篇(二)

    lvgl小部件 基础对象学习篇 二 学习材料 工具 QT Creator 5 12 3 lvgl 源代码 极客笔记 学习过程 内容 LVGL 基础对象 坐标 尺寸 位置 对齐 父子关系 屏幕 层次 事件处理 部件 状态 风格 属性 保护 组
  • FreeRTOS学习(三)开关中断

    声明及感谢 跟随正点原子资料学习 在此作为学习的记录和总结 环境 keil stm32f103 背景知识 Cotex M3的NVIC最多支持240个IRQ 中断请求 1个不可屏蔽 NMI 1个Systick 滴答定时器 Cortex M处理
  • Camunda流程驳回至上一节点

    文章目录 前言 一 版本 二 实现 1 回退至上一节点 2 回退至开始节点 3 测试方法 前言 Camunda驳回至上一节点 网上大多都是回退至开始节点 这样逻辑比较简单清晰 但实际使用中 往往需要驳回至上一节点 甚至需要连续驳回多次 流程