flowable执行监听器动态指定审批人在退回时产生的bug

场景: 退回产生的bug,有一个结点,本身是通过执行监听器判断上一个结点的审批人来得到这个结点的审批人。之前是通过直接的获取最新task来拿到,但是在退回场景下,最新task为退回结点,故产生错误。

解决: 遍历原始bpmn模型找到正确的前驱结点,再从HistoricTask中拿到历史任务信息进行比对

首先:

java 复制代码
//历史任务结点信息
List<HistoricTaskInstance> historicTaskInstanceList = historyService.createHistoricTaskInstanceQuery().processInstanceId(processInstance.getId()).list()
        .stream().sorted(Comparator.comparing(HistoricTaskInstance::getCreateTime)).collect(Collectors.toList());
//这里按照时间排序得到正确的流程流转顺序

然后:

java 复制代码
//在BpmnModel中找到当前任务结点的前驱节点集合
// 加载BPMN模型
BpmnModel bpmnModel = repositoryService.getBpmnModel(processDefinition.getId());
Map<String, FlowElement> flowElementMap = bpmnModel.getMainProcess().getFlowElementMap();

//前驱结点集合
List<String> preNodeList = new ArrayList<>();
// 遍历FlowElementMap

Map<String, SequenceFlow> sequenceFlowMap = new HashMap<>();
//找到当前结点的前去结点集合
for (Map.Entry<String, FlowElement> entry : flowElementMap.entrySet()) {
    FlowElement flowElement = entry.getValue();

    // 检查FlowElement是否是SequenceFlow的实例
    if (flowElement instanceof SequenceFlow) {
        // 如果是,就将其转换为SequenceFlow并添加到新的映射中
        SequenceFlow sequenceFlow = (SequenceFlow) flowElement;
        sequenceFlowMap.put(entry.getKey(), sequenceFlow);
    }
}

//遍历sequenceFlowMap找前驱结点
for (Map.Entry<String, SequenceFlow> entry : sequenceFlowMap.entrySet()) {
    SequenceFlow sequenceFlow = entry.getValue();
    String targetRef = sequenceFlow.getTargetRef(); // 需要找到目标为当前结点的
    String sourceRef = sequenceFlow.getSourceRef(); // 保存指向当前结点的

    if (Objects.equals(targetRef, curTaskDefinitionKey)) {
        if (flowElementMap.get(sourceRef) instanceof ExclusiveGateway) {
            //是网关
            //去找指向该网关的 也就是deepTarget=sourceRef  不会出现网关指向网关
            //遍历
            for (Map.Entry<String, SequenceFlow> deepEntry : sequenceFlowMap.entrySet()) {
                SequenceFlow deepSequenceFlow = deepEntry.getValue();
                String deepTargetRef = deepSequenceFlow.getTargetRef(); //目标都是网关
                String deepSourceRef = deepSequenceFlow.getSourceRef(); //指向网关的源结点
                if (Objects.equals(sourceRef, deepTargetRef)) {
                    if (targetRef.equals(deepSourceRef)) {
                        continue; //循环部分不认为是前驱结点
                    }
                    preNodeList.add(deepSourceRef);
                }
            }
        } else {
            if (targetRef.equals(sourceRef)) {
                continue; //循环部分不认为是前驱结点
            }
            preNodeList.add(sourceRef);
        }
    }
}
for (int i = historicTaskInsListLen - 1; i >= 0; i--) {
    if (preNodeList.contains(historicTaskInstanceList.get(i).getTaskDefinitionKey())) {
        //找到第一个包含的
        //不是循环审批,找到上一个审批人设定为初始结点
        lastHistoricTaskInstance = historicTaskInstanceList.get(i);
        break;
    }
}

lastHistoricTaskInstance即为正确的前驱结点

给出我自己业务中的完整代码,有需要可按自己情况进行更改,核心代码如上

java 复制代码
@Component
public class LastHandlerLeaderCycleTaskListener implements ExecutionListener {
    private final SysUserMapper userMapper = SpringUtil.getBean(SysUserMapper.class);
    private final SysDeptMapper sysDeptMapper = SpringUtil.getBean(SysDeptMapper.class);

    private final HistoryService historyService = SpringUtil.getBean(HistoryService.class);
    private final RepositoryService repositoryService = SpringUtil.getBean(RepositoryService.class);
    private final RuntimeService runtimeService = SpringUtil.getBean(RuntimeService.class);

    @Override
    public void notify(DelegateExecution execution) {
        ProcessInstance processInstance = runtimeService
                .createProcessInstanceQuery()
                .processInstanceId(execution.getProcessInstanceId())
                .singleResult();

        ProcessDefinition processDefinition = repositoryService.createProcessDefinitionQuery()
                .processDefinitionId(execution.getProcessDefinitionId())
                .singleResult();


        //针对回退:
        //1.在BpmnModel中找到当前任务结点的前驱节点集合
        //2.从HistoricTaskInstance中从后向前找到第一个出现在前驱节点集合的元素
        //3.把该元素对饮的审批人设定为前一个节点的审批人,然后重新进入循环审批环节中

        //历史任务结点信息
        List<HistoricTaskInstance> historicTaskInstanceList = historyService.createHistoricTaskInstanceQuery().processInstanceId(processInstance.getId()).list()
                .stream().sorted(Comparator.comparing(HistoricTaskInstance::getCreateTime)).collect(Collectors.toList());
        int historicTaskInsListLen = historicTaskInstanceList.size();
        //接下来要进入的结点信息
        String curTaskDefinitionKey = execution.getCurrentActivityId();


        Long lastDeptId = null; //上一次的部门id
        HistoricTaskInstance lastHistoricTaskInstance = null;
//        2. 如果上一个任务结点刚好为自身结点,那么进入循环审批,指定上一个结点审批人领导为自身领导
        if (Objects.equals(historicTaskInstanceList.get(historicTaskInsListLen - 1).getTaskDefinitionKey(), curTaskDefinitionKey)) {
            //是循环审批,指定上一个结点审批人为初始结点
            lastHistoricTaskInstance = historicTaskInstanceList.get(historicTaskInsListLen - 1);

            //获取上一次的部门信息
            lastDeptId = (Long) execution.getVariable(FlowableStoreInfosEnum.lastLeaderCycleDeptId.name());
        }
//        3. 如果上一个结点与不是自身结点,那么开始向前找第一个出现的前向结点,把他的信息认为是前向审批人
        else {
            //在BpmnModel中找到当前任务结点的前驱节点集合
            // 加载BPMN模型
            BpmnModel bpmnModel = repositoryService.getBpmnModel(processDefinition.getId());
            Map<String, FlowElement> flowElementMap = bpmnModel.getMainProcess().getFlowElementMap();

            //前驱结点集合
            List<String> preNodeList = new ArrayList<>();
            // 遍历FlowElementMap

            Map<String, SequenceFlow> sequenceFlowMap = new HashMap<>();
            //找到当前结点的前去结点集合
            for (Map.Entry<String, FlowElement> entry : flowElementMap.entrySet()) {
                FlowElement flowElement = entry.getValue();

                // 检查FlowElement是否是SequenceFlow的实例
                if (flowElement instanceof SequenceFlow) {
                    // 如果是,就将其转换为SequenceFlow并添加到新的映射中
                    SequenceFlow sequenceFlow = (SequenceFlow) flowElement;
                    sequenceFlowMap.put(entry.getKey(), sequenceFlow);
                }
            }

            //遍历sequenceFlowMap找前驱结点
            for (Map.Entry<String, SequenceFlow> entry : sequenceFlowMap.entrySet()) {
                SequenceFlow sequenceFlow = entry.getValue();
                String targetRef = sequenceFlow.getTargetRef(); // 需要找到目标为当前结点的
                String sourceRef = sequenceFlow.getSourceRef(); // 保存指向当前结点的

                if (Objects.equals(targetRef, curTaskDefinitionKey)) {
                    if (flowElementMap.get(sourceRef) instanceof ExclusiveGateway) {
                        //是网关
                        //去找指向该网关的 也就是deepTarget=sourceRef  不会出现网关指向网关
                        //遍历
                        for (Map.Entry<String, SequenceFlow> deepEntry : sequenceFlowMap.entrySet()) {
                            SequenceFlow deepSequenceFlow = deepEntry.getValue();
                            String deepTargetRef = deepSequenceFlow.getTargetRef(); //目标都是网关
                            String deepSourceRef = deepSequenceFlow.getSourceRef(); //指向网关的源结点
                            if (Objects.equals(sourceRef, deepTargetRef)) {
                                if (targetRef.equals(deepSourceRef)) {
                                    continue; //循环部分不认为是前驱结点
                                }
                                preNodeList.add(deepSourceRef);
                            }
                        }
                    } else {
                        if (targetRef.equals(sourceRef)) {
                            continue; //循环部分不认为是前驱结点
                        }
                        preNodeList.add(sourceRef);
                    }
                }
            }
            for (int i = historicTaskInsListLen - 1; i >= 0; i--) {
                if (preNodeList.contains(historicTaskInstanceList.get(i).getTaskDefinitionKey())) {
                    //找到第一个包含的
                    //不是循环审批,找到上一个审批人设定为初始结点
                    lastHistoricTaskInstance = historicTaskInstanceList.get(i);
                    break;
                }
            }
        }


        //先找直属部门 再找直属部门对应leader 下一次来的话,需要找到上一次部门的父部门,也就是中途肯定需要某种方法保存当前次的部门id
        String lastAssignee = lastHistoricTaskInstance.getAssignee();
        SysUser leaderUser = null;
        if (lastDeptId == null) { //刚进入循环
            SysUser lastTaskUser = userMapper.selectUserById(Long.valueOf(lastAssignee)); //找自己
            lastDeptId = lastTaskUser.getDeptId();
            SysDept lastSysDept = sysDeptMapper.selectById(lastDeptId); //找上一次部门
            //找领导
            leaderUser = userMapper.selectUserByEmpCode(lastSysDept.getLeader()); //这次审批人是上传审批人的所在部门

            execution.setVariable(FlowableStoreInfosEnum.lastLeaderCycleDeptLevel.name(), lastSysDept.getDeptLevel()); //存储等级,以供流程跳出
            execution.setVariable(FlowableStoreInfosEnum.lastLeaderCycleDeptId.name(), lastSysDept.getDeptId()); //存储id,以供前端判断 这次对于下次来说就是上次
        } else {
            SysDept lastSysDept = sysDeptMapper.selectById(lastDeptId); //找上一次的部门
            SysDept nowSysDept = sysDeptMapper.selectById(lastSysDept.getParentId()); //找这次的部门
            leaderUser = userMapper.selectUserByEmpCode(nowSysDept.getLeader()); //这次审批人是上传审批人的所在部门

            execution.setVariable(FlowableStoreInfosEnum.lastLeaderCycleDeptLevel.name(), nowSysDept.getDeptLevel()); //存储等级,以供流程跳出
            execution.setVariable(FlowableStoreInfosEnum.lastLeaderCycleDeptId.name(), nowSysDept.getDeptId()); //存储id,以供前端判断 这次对于下次来说就是上次
        }

        String leaderIdStr = leaderUser == null ? lastAssignee : leaderUser.getUserId().toString();
        //存储审批人id到candidateUsers,框架自行使用该字段
        execution.setVariable("candidateUsers", leaderIdStr);
    }
}
相关推荐
架构文摘JGWZ44 分钟前
Java 23 的12 个新特性!!
java·开发语言·学习
拾光师2 小时前
spring获取当前request
java·后端·spring
aPurpleBerry2 小时前
neo4j安装启动教程+对应的jdk配置
java·neo4j
我是苏苏2 小时前
Web开发:ABP框架2——入门级别的增删改查Demo
java·开发语言
xujinwei_gingko2 小时前
Spring IOC容器Bean对象管理-Java Config方式
java·spring
2301_789985942 小时前
Java语言程序设计基础篇_编程练习题*18.29(某个目录下的文件数目)
java·开发语言·学习
IT学长编程2 小时前
计算机毕业设计 教师科研信息管理系统的设计与实现 Java实战项目 附源码+文档+视频讲解
java·毕业设计·springboot·毕业论文·计算机毕业设计选题·计算机毕业设计开题报告·教师科研管理系统
m0_571957582 小时前
Java | Leetcode Java题解之第406题根据身高重建队列
java·leetcode·题解
xuehaisj2 小时前
手写流程图元素检测系统源码分享
流程图
cleveryuoyuo2 小时前
二叉树的链式结构和递归程序的递归流程图
c语言·数据结构·流程图