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);
    }
}
相关推荐
2401_857610038 分钟前
Spring Boot框架:电商系统的技术优势
java·spring boot·后端
希忘auto24 分钟前
详解MySQL安装
java·mysql
冰淇淋烤布蕾35 分钟前
EasyExcel使用
java·开发语言·excel
拾荒的小海螺41 分钟前
JAVA:探索 EasyExcel 的技术指南
java·开发语言
Jakarta EE1 小时前
正确使用primefaces的process和update
java·primefaces·jakarta ee
马剑威(威哥爱编程)1 小时前
哇喔!20种单例模式的实现与变异总结
java·开发语言·单例模式
java—大象1 小时前
基于java+springboot+layui的流浪动物交流信息平台设计实现
java·开发语言·spring boot·layui·课程设计
杨哥带你写代码2 小时前
网上商城系统:Spring Boot框架的实现
java·spring boot·后端
camellias_2 小时前
SpringBoot(二十一)SpringBoot自定义CURL请求类
java·spring boot·后端
布川ku子2 小时前
[2024最新] java八股文实用版(附带原理)---Mysql篇
java·mysql·面试