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);
    }
}
相关推荐
xlsw_3 小时前
java全栈day20--Web后端实战(Mybatis基础2)
java·开发语言·mybatis
神仙别闹4 小时前
基于java的改良版超级玛丽小游戏
java
黄油饼卷咖喱鸡就味增汤拌孜然羊肉炒饭4 小时前
SpringBoot如何实现缓存预热?
java·spring boot·spring·缓存·程序员
暮湫4 小时前
泛型(2)
java
超爱吃士力架4 小时前
邀请逻辑
java·linux·后端
南宫生4 小时前
力扣-图论-17【算法学习day.67】
java·学习·算法·leetcode·图论
转码的小石5 小时前
12/21java基础
java
李小白665 小时前
Spring MVC(上)
java·spring·mvc
GoodStudyAndDayDayUp5 小时前
IDEA能够从mapper跳转到xml的插件
xml·java·intellij-idea
装不满的克莱因瓶5 小时前
【Redis经典面试题六】Redis的持久化机制是怎样的?
java·数据库·redis·持久化·aof·rdb