基于jeecg-boot的flowable流程自定义业务驳回到发起人的一种处理方式

有些粉丝,希望对自定义业务中,驳回到发起人进行处理,比如可以重新进行发起流程,下面就给出一种方式,当然不一定是最好的方式,只是提供一种参考而已,以后可以考虑动态根据流程状态或节点信息进行更加好的处理。

这种方式目前前端不做修改,只做后端的一种处理。

主要是增加两个逻辑:

1、增加一个判断是发起人节点,isFirstInitiator ,以后可以考虑增加驳回与退回的处理

2、对于驳回里对于驳回到发起人后进行流程删除与关联删除,以便进行重新发起流程

java 复制代码
/**
     * 驳回任务 for自定义业务
     *
     * @param flowTaskVo
     */
    @Override
    public void taskRejectForDataId(FlowTaskVo flowTaskVo) {
        if (taskService.createTaskQuery().taskId(flowTaskVo.getTaskId()).singleResult().isSuspended()) {
            throw new CustomException("任务处于挂起状态");
        }
        // 当前任务 task
        Task task = taskService.createTaskQuery().taskId(flowTaskVo.getTaskId()).singleResult();
        // 获取流程定义信息
        ProcessDefinition processDefinition = repositoryService.createProcessDefinitionQuery().processDefinitionId(task.getProcessDefinitionId()).singleResult();
        // 获取所有节点信息
        Process process = repositoryService.getBpmnModel(processDefinition.getId()).getProcesses().get(0);
        // 获取全部节点列表,包含子节点
        Collection<FlowElement> allElements = FlowableUtils.getAllElements(process.getFlowElements(), null);
        // 获取当前任务节点元素
        FlowElement source = null;
        if (allElements != null) {
            for (FlowElement flowElement : allElements) {
                // 类型为用户节点
                if (flowElement.getId().equals(task.getTaskDefinitionKey())) {
                    // 获取节点信息
                    source = flowElement;
                }
            }
        }

        // 目的获取所有跳转到的节点 targetIds
        // 获取当前节点的所有父级用户任务节点
        // 深度优先算法思想:延边迭代深入
        List<UserTask> parentUserTaskList = FlowableUtils.iteratorFindParentUserTasks(source, null, null);
        if (parentUserTaskList == null || parentUserTaskList.size() == 0) {
            throw new CustomException("当前节点为初始任务节点,不能驳回");
        }
        // 获取活动 ID 即节点 Key
        List<String> parentUserTaskKeyList = new ArrayList<>();
        parentUserTaskList.forEach(item -> parentUserTaskKeyList.add(item.getId()));
        // 获取全部历史节点活动实例,即已经走过的节点历史,数据采用开始时间升序
        List<HistoricTaskInstance> historicTaskInstanceList = historyService.createHistoricTaskInstanceQuery().processInstanceId(task.getProcessInstanceId()).orderByHistoricTaskInstanceStartTime().asc().list();
        // 数据清洗,将回滚导致的脏数据清洗掉
        List<String> lastHistoricTaskInstanceList = FlowableUtils.historicTaskInstanceClean(allElements, historicTaskInstanceList);
        // 此时历史任务实例为倒序,获取最后走的节点
        List<String> targetIds = new ArrayList<>();
        // 循环结束标识,遇到当前目标节点的次数
        int number = 0;
        StringBuilder parentHistoricTaskKey = new StringBuilder();
        for (String historicTaskInstanceKey : lastHistoricTaskInstanceList) {
            // 当会签时候会出现特殊的,连续都是同一个节点历史数据的情况,这种时候跳过
            if (parentHistoricTaskKey.toString().equals(historicTaskInstanceKey)) {
                continue;
            }
            parentHistoricTaskKey = new StringBuilder(historicTaskInstanceKey);
            if (historicTaskInstanceKey.equals(task.getTaskDefinitionKey())) {
                number++;
            }
            // 在数据清洗后,历史节点就是唯一一条从起始到当前节点的历史记录,理论上每个点只会出现一次
            // 在流程中如果出现循环,那么每次循环中间的点也只会出现一次,再出现就是下次循环
            // number == 1,第一次遇到当前节点
            // number == 2,第二次遇到,代表最后一次的循环范围
            if (number == 2) {
                break;
            }
            // 如果当前历史节点,属于父级的节点,说明最后一次经过了这个点,需要退回这个点
            if (parentUserTaskKeyList.contains(historicTaskInstanceKey)) {
                targetIds.add(historicTaskInstanceKey);
            }
        }


        // 目的获取所有需要被跳转的节点 currentIds
        // 取其中一个父级任务,因为后续要么存在公共网关,要么就是串行公共线路
        UserTask oneUserTask = parentUserTaskList.get(0);
        // 获取所有正常进行的任务节点 Key,这些任务不能直接使用,需要找出其中需要撤回的任务
        List<Task> runTaskList = taskService.createTaskQuery().processInstanceId(task.getProcessInstanceId()).list();
        List<String> runTaskKeyList = new ArrayList<>();
        runTaskList.forEach(item -> runTaskKeyList.add(item.getTaskDefinitionKey()));
        // 需驳回任务列表
        List<String> currentIds = new ArrayList<>();
        // 通过父级网关的出口连线,结合 runTaskList 比对,获取需要撤回的任务
        List<UserTask> currentUserTaskList = FlowableUtils.iteratorFindChildUserTasks(oneUserTask, runTaskKeyList, null, null);
        currentUserTaskList.forEach(item -> currentIds.add(item.getId()));


        // 规定:并行网关之前节点必须需存在唯一用户任务节点,如果出现多个任务节点,则并行网关节点默认为结束节点,原因为不考虑多对多情况
        if (targetIds.size() > 1 && currentIds.size() > 1) {
            throw new CustomException("任务出现多对多情况,无法撤回");
        }

        // 循环获取那些需要被撤回的节点的ID,用来设置驳回原因
        List<String> currentTaskIds = new ArrayList<>();
        currentIds.forEach(currentId -> runTaskList.forEach(runTask -> {
            if (currentId.equals(runTask.getTaskDefinitionKey())) {
                currentTaskIds.add(runTask.getId());
            }
        }));
        // 设置驳回意见
        currentTaskIds.forEach(item -> taskService.addComment(item, task.getProcessInstanceId(), FlowComment.REJECT.getType(), flowTaskVo.getComment()));
        SysUser loginUser = iFlowThirdService.getLoginUser();
        try {
            // 设置处理人
            taskService.setAssignee(task.getId(), loginUser.getUsername());
            // 如果父级任务多于 1 个,说明当前节点不是并行节点,原因为不考虑多对多情况
            if (targetIds.size() > 1) {
                // 1 对 多任务跳转,currentIds 当前节点(1),targetIds 跳转到的节点(多)
                runtimeService.createChangeActivityStateBuilder()
                        .processInstanceId(task.getProcessInstanceId()).
                        moveSingleActivityIdToActivityIds(currentIds.get(0), targetIds).changeState();
            }
            // 如果父级任务只有一个,因此当前任务可能为网关中的任务
            if (targetIds.size() == 1) {
                // 1 对 1 或 多 对 1 情况,currentIds 当前要跳转的节点列表(1或多),targetIds.get(0) 跳转到的节点(1)
                runtimeService.createChangeActivityStateBuilder()
                        .processInstanceId(task.getProcessInstanceId())
                        .moveActivityIdsToSingleActivityId(currentIds, targetIds.get(0)).changeState();
            }
            /*======================驳回  回调以及关键数据保存======================*/
            //业务数据id
            String dataId = flowTaskVo.getDataId();
            if (dataId==null) return;
            //如果保存数据前未调用必调的FlowCommonService.initActBusiness方法,就会有问题
            FlowMyBusiness business = flowMyBusinessService.getByDataId(dataId);
            // 驳回到了上一个节点等待处理
            Task targetTask = taskService.createTaskQuery().processInstanceId(business.getProcessInstanceId()).active().singleResult();
            //spring容器类名
            String serviceImplName = business.getServiceImplName();
            FlowCallBackServiceI flowCallBackService = (FlowCallBackServiceI) SpringContextUtils.getBean(serviceImplName);
            Map<String, Object> values = flowTaskVo.getValues();
            if (values ==null){
                values = MapUtil.newHashMap();
                values.put("dataId",dataId);
            } else {
                values.put("dataId",dataId);
            }
            List<String> beforeParamsCandidateUsernames = flowCallBackService.flowCandidateUsernamesOfTask(targetTask.getTaskDefinitionKey(), values);
            //设置数据
            String doneUsers = business.getDoneUsers();
            // 处理过流程的人
            JSONArray doneUserList = new JSONArray();
            if (StrUtil.isNotBlank(doneUsers)){
                doneUserList = JSON.parseArray(doneUsers);
            }
            if (!doneUserList.contains(loginUser.getUsername())){
                doneUserList.add(loginUser.getUsername());
            }
            business.setActStatus(ActStatus.reject)
                    .setTaskId(targetTask.getId())
                    .setTaskNameId(targetTask.getTaskDefinitionKey())
                    .setTaskName(targetTask.getName())
                    .setDoneUsers(doneUserList.toJSONString())
            ;
            FlowElement targetElement = null;
            if (allElements != null) {
                for (FlowElement flowElement : allElements) {
                    // 类型为用户节点
                    if (flowElement.getId().equals(targetTask.getTaskDefinitionKey())) {
                        // 获取节点信息
                        targetElement = flowElement;
                    }
                }
            }

            ProcessInstance processInstance = runtimeService.createProcessInstanceQuery().processInstanceId(targetTask.getProcessInstanceId()).singleResult();
            String startUserId = processInstance.getStartUserId();
            
            if (targetElement!=null){
                UserTask targetUserTask = (UserTask) targetElement;
                business.setPriority(targetUserTask.getPriority());

                if (StrUtil.equals(targetUserTask.getIncomingFlows().get(0).getSourceRef(),"startNode1")) {//是否为发起人节点
                    //    开始节点。设置处理人为申请人
                    business.setTodoUsers(JSON.toJSONString(Lists.newArrayList(business.getProposer())));
                    taskService.setAssignee(business.getTaskId(),business.getProposer());
                } else {
                    List<SysUser> sysUserFromTask = getSysUserFromTask(targetUserTask,startUserId);
                    List<String> collect_username = sysUserFromTask.stream().map(SysUser::getUsername).collect(Collectors.toList());
                    //collect_username转换成realname
                    List<String> newusername = new ArrayList<String>();
                    for (String oldUser : collect_username) {
                    	if(StringUtils.equalsAnyIgnoreCase(oldUser, "${INITIATOR}")) {//对发起人做特殊处理
                    		SysUser sysUser = iFlowThirdService.getUserByUsername(startUserId);
                            newusername.add(sysUser.getRealname());
                    	}
                    	else {
                    	  SysUser sysUser = iFlowThirdService.getUserByUsername(oldUser);
                          newusername.add(sysUser.getRealname());
                    	}
                    	
                    }
                    business.setTodoUsers(JSON.toJSONString(newusername));
                    // 删除后重写
                    for (String oldUser : collect_username) {
                        taskService.deleteCandidateUser(targetTask.getId(),oldUser);
                    }
                    if (CollUtil.isNotEmpty(beforeParamsCandidateUsernames)){
                        // 业务层有指定候选人,覆盖
                        for (String newUser : beforeParamsCandidateUsernames) {
                            taskService.addCandidateUser(targetTask.getId(),newUser);
                        }
                        business.setTodoUsers(JSON.toJSONString(beforeParamsCandidateUsernames));
                    } else {
                        for (String oldUser : collect_username) {
                            taskService.addCandidateUser(targetTask.getId(),oldUser);
                        }
                    }
                    if(collect_username.size() ==1) {
                        targetTask.setAssignee(newusername.get(0).toString());
                        taskService.addUserIdentityLink(targetTask.getId(), collect_username.get(0).toString(), IdentityLinkType.ASSIGNEE);
                    }else if(collect_username.size() > 1){
                        List<HistoricActivityInstance> list = historyService
                                .createHistoricActivityInstanceQuery()
                                .activityId(targetTask.getTaskDefinitionKey())
                                .orderByHistoricActivityInstanceStartTime()
                                .desc().list();
                        for (HistoricActivityInstance historicActivityInstance : list) {
                            if (StrUtil.isNotBlank(historicActivityInstance.getAssignee())) {
                                targetTask.setAssignee(historicActivityInstance.getAssignee());
                                taskService.addUserIdentityLink(targetTask.getId(), historicActivityInstance.getAssignee(), IdentityLinkType.ASSIGNEE);
                                break;
                            }
                        }
                    }
                }
            }
            
            //  重新查询当前任务
            Task currentTask = taskService.createTaskQuery().processInstanceId(targetTask.getProcessInstanceId()).singleResult();
            //判断是否是发起人节点,恢复自定义业务表单重新提交
            if(isFirstInitiator(currentTask)) {
                //删除自定义业务任务关联表与流程历史表,以便可以重新发起流程。
            	//(要是需要重新进行提交的话,那就要保留第一个发起人历史信息,自定义业务表单最好增加一个再次发起按钮来处理这种情况
                if (business != null) {
                	flowMyBusinessService.removeById(business);
                	// 对自定义业务,删除运行和历史的节点信息 
                    this.deleteActivity(targetTask.getTaskDefinitionKey(), targetTask.getProcessInstanceId(), dataId);
                }
            }
            else {
            	flowMyBusinessService.updateById(business);
                // 流程处理完后,进行回调业务层
                business.setValues(values);
                if (flowCallBackService!=null) flowCallBackService.afterFlowHandle(business);
            }
        } catch (FlowableObjectNotFoundException e) {
            throw new CustomException("未找到流程实例,流程可能已发生变化");
        } catch (FlowableException e) {
            throw new CustomException("无法取消或开始活动");
        }

    }
    
    /**
    * 判断当前节点是否是第一个发起人节点
    *
    * @param flowTaskVo 请求实体参数
    */
    boolean isFirstInitiator(Task task) {
        BpmnModel bpmnModel = repositoryService.getBpmnModel(task.getProcessDefinitionId());
       //  获取当前活动节点
        FlowNode currentFlowNode = (FlowNode) bpmnModel.getMainProcess().getFlowElement(task.getTaskDefinitionKey());
        // 输入连线
        List<SequenceFlow> inFlows = currentFlowNode.getIncomingFlows();
        for (SequenceFlow sequenceFlow : inFlows) {
        	FlowElement sourceFlowElement = sequenceFlow.getSourceFlowElement();
        	// 如果上个节点为开始节点
            if (sourceFlowElement instanceof StartEvent) {
            	log.info("当前节点为发起人节点,上个节点为开始节点:id=" + sourceFlowElement.getId() + ",name=" + sourceFlowElement.getName());
                return true;
            }
        }
		return false;	
    }
相关推荐
武子康1 分钟前
大数据-258 离线数仓 - Griffin架构 配置安装 Livy 架构设计 解压配置 Hadoop Hive
java·大数据·数据仓库·hive·hadoop·架构
豪宇刘1 小时前
MyBatis的面试题以及详细解答二
java·servlet·tomcat
秋恬意1 小时前
Mybatis能执行一对一、一对多的关联查询吗?都有哪些实现方式,以及它们之间的区别
java·数据库·mybatis
FF在路上2 小时前
Knife4j调试实体类传参扁平化模式修改:default-flat-param-object: true
java·开发语言
真的很上进2 小时前
如何借助 Babel+TS+ESLint 构建现代 JS 工程环境?
java·前端·javascript·css·react.js·vue·html
众拾达人3 小时前
Android自动化测试实战 Java篇 主流工具 框架 脚本
android·java·开发语言
皓木.3 小时前
Mybatis-Plus
java·开发语言
不良人天码星3 小时前
lombok插件不生效
java·开发语言·intellij-idea
守护者1703 小时前
JAVA学习-练习试用Java实现“使用Arrays.toString方法将数组转换为字符串并打印出来”
java·学习
源码哥_博纳软云3 小时前
JAVA同城服务场馆门店预约系统支持H5小程序APP源码
java·开发语言·微信小程序·小程序·微信公众平台