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

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

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

    主要是增加两个逻辑:

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

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

/*** 驳回任务 for自定义业务** @param flowTaskVo*/@Overridepublic void taskRejectForDataId(FlowTaskVo flowTaskVo) {if (taskService.createTaskQuery().taskId(flowTaskVo.getTaskId()).singleResult().isSuspended()) {throw new CustomException("任务处于挂起状态");}// 当前任务 taskTask 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 即节点 KeyList<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();}/*======================驳回  回调以及关键数据保存======================*///业务数据idString 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转换成realnameList<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;	}

     

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/65553.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

【前端demo】CSS border-radius可视化 原生实现

文章目录 效果原理代码 前端demo系列目录&#xff1a;https://blog.csdn.net/karshey/article/details/132585901 效果 效果预览&#xff1a;https://codepen.io/karshey/pen/zYyBPBR 参考&#xff1a; Fancy Border Radius Generator (9elements.github.io) https://borde…

测试开发【Mock平台】09开发:项目管理(五)搜索、删除和Table优化

【Mock平台】为系列测试开发教程&#xff0c;从0到1编码带你一步步使用Spring Boot 和 Antd React框架完成搭建一个测试工具平台&#xff0c;希望作为一个实战项目对各位的测试开发学习之路有帮助&#xff0c;大奇一个专注测试技术干货原创与分享的家伙。 Mock平台系统项目基本…

【C++】一文解析std::binary_function、std::bind1st、std::bind2nd、std::bind

STL中有一个叫做“适配器”的概念&#xff0c;它指的是某些函数可能定义了两个形参&#xff0c;但是某些算法需要的函数却有时候需要一个形参&#xff0c;那么就需要对其进行适配&#xff0c;将原本只需要两个参数的函数转变成需要1和参数就能正常运行的函数。就像你为你的笔记…

数据集学习笔记(六):目标检测和图像分割标注软件介绍和使用,并转换成YOLO系列可使用的数据集格式

文章目录 一、目标检测1.1 labelImg1.2 介绍1.3 安装1.4 使用1.5 转换1.6 验证 二、图像分割2.1 labelme2.2 介绍2.3 安装2.4 使用2.5 转换2.6 验证 一、目标检测 1.1 labelImg 1.2 介绍 labelImg是一个开源的图像标注工具&#xff0c;用于创建图像标注数据集。它提供了一个…

iOS 设置下载部分文件,如何获取完整文件的大小

在视频的需求中&#xff0c;遇到这样一个需求&#xff0c;播放一视频的时候&#xff0c;要预下载 后面10条视频&#xff0c;但是只下载后面十条视频的前面1M 实现方法 1 创建请求时设置cacheLength resource [[IdiotResource alloc] init];resource.requestURL task.request…

不同路径 II【动态规划】

不同路径 II 一个机器人位于一个 m x n 网格的左上角 &#xff08;起始点在下图中标记为 “Start” &#xff09;。 机器人每次只能向下或者向右移动一步。机器人试图达到网格的右下角&#xff08;在下图中标记为 “Finish”&#xff09;。 现在考虑网格中有障碍物。那么从左上…

深入解析即时通讯App开发中的关键技术

即时通讯App开发在现代社交和通信领域中扮演着重要的角色。随着移动设备的普及和网络的高速发展&#xff0c;人们对即时通讯工具的需求不断增加。本篇文章将深入探讨即时通讯App开发中的关键技术&#xff0c;帮助读者了解该领域的最新动态和技术趋势。 基础架构和通信协议 现…

【Python】Python 利用模块实现单例模式

Python 利用模块实现单例模式 在GOF的23种设计模式中&#xff0c;单例是最常使用的模式&#xff0c;通过单例模式可以保证系统中 一个类只有一个实例而且该实例易于被外界访问&#xff0c;从而方便对实例个数的控制并节约系统资 源。每当大家想要实现一个名为XxxMangcr的类时&…

阻塞/非阻塞、同步/异步(网络IO)

1.阻塞/非阻塞、同步/异步(网络IO) 【思考】典型的一次 IO 的两个阶段是什么&#xff1f; 数据就绪 和 数据读写 数据就绪 &#xff1a;根据系统 IO 操作的就绪状态 阻塞 非阻塞 数据读写 &#xff1a;根据应用程序和内核的交互方式 同步 异步 陈硕&#xff1a;在处理 IO …

【JS】公共鼠标滚动事件(从下进入,从上进入),可vue做指令使用

unilateralScroll 方法是动画主事件 enterFromBelow 方法是鼠标从上向下滚动事件 enterFromAbove 方法是鼠标从下向上滚动事件 Action[“事件名”] 是使用自定事件 // index.js 文件/** 从下进入事件 -- FromLeftToRight 默认动画 */ const enterFromBelow (el, event) > A…

ISP——3A算法

目录 前沿一. 自动曝光AE1.1. 自动曝光1.2. 18%灰1.3. 测光区域1.4. 摄影曝光加法系统1.5. AE算法1.5.1. 考虑事项1.5.2. AE实现过程 1.6. AE算法 二. 自动对焦AF2.1. 什么是自动对焦2.2. 图像清晰度评价方法2.2.1. Brenner 梯度函数2.2.2. Tenengrad 梯度函数2.2.3. Laplacian…

1.12 进程注入ShellCode套接字

在笔者前几篇文章中我们一直在探讨如何利用Metasploit这个渗透工具生成ShellCode以及如何将ShellCode注入到特定进程内&#xff0c;本章我们将自己实现一个正向ShellCodeShell&#xff0c;当进程被注入后&#xff0c;则我们可以通过利用NC等工具连接到被注入进程内&#xff0c;…

【go】异步任务解决方案Asynq实战

文章目录 一.Asynq介绍二.所需工具三.代码示例四.Reference 一.Asynq介绍 Asynq 是一个 Go 库&#xff0c;一个高效的分布式任务队列。 Asynq 工作原理&#xff1a; 客户端&#xff08;生产者&#xff09;将任务放入队列服务器&#xff08;消费者&#xff09;从队列中拉出任…

【vue】this.$nextTick解决this.$refs undefined的问题

说明 1、发邮件页面分成两个部分&#xff1a;模态框页面&#xff08;头部和底部&#xff09;和form页面&#xff08;操作按钮&#xff09; 2、点击回复按钮&#xff0c;要将发件人信息带到模态框页面&#xff0c;给定默认值且禁止收件人下拉选择&#xff08;多个邮箱&#xff…

Go用两个协程交替打印100以内的奇偶数

方式1&#xff08;使用无缓冲的channel&#xff09; package mainimport ( "fmt" "time")var flagChan make(chan int)func wokr1() { for i : 1; i < 100; i { flagChan <- 666 // 塞入 if i%2 1 { fmt.Println("协程1打印:", i) …

Windows Update Blocker,windows系统关闭自动更新工具

今天打开电脑发现系统又自动更新了 这一天天更新真的太烦了 然后我从网上找到一个工具 可以自由开启和关闭系统自动更新 这里分享一下网址&#xff1a;https://www.filehorse.com/download-windows-update-blocker/ 若网址失效&#xff0c;蓝奏云盘链接 https://wwgw.lanzouc.c…

leecode 数据库:1164. 指定日期的产品价格

导入数据&#xff1a; Create table If Not Exists Products (product_id int, new_price int, change_date date); Truncate table Products; insert into Products (product_id, new_price, change_date) values (1, 20, 2019-08-14); insert into Products (product_id, new…

安全计算环境技术测评要求项

1.身份鉴别-在应用系统及各类型设备中确认操作者身份的过程&#xff08;身份鉴别和数据保密&#xff09; 1-2/2-3/3-4/4-4 a&#xff09;应对登录的用户进行身份标识和鉴别&#xff0c;身份标识具有唯一性&#xff0c;身份鉴别信息具有复杂度要求并定期更换 b&#xff09;应具有…

归并排序的详解!

本文旨在讲解归并排序的实现&#xff08;递归及非递归&#xff09;搬好小板凳&#xff0c;干货来了&#xff01; 前序&#xff1a; 在介绍归并排序之前&#xff0c;需要给大家介绍的是什么是归并&#xff0c;归并操作&#xff0c;也叫归并算法&#xff0c;指的是将两个顺序序列…

lv3 嵌入式开发-3 linux shell命令(文件搜索、文件处理、压缩)

目录 1 查看文件相关命令 1.1 常用命令 1.2 硬链接和软链接 2 文件搜索相关命令 2.1 查找文件命令 2.2 查找文件内容命令 2.3 其他相关命令 3 文件处理相关命令 3.1 cut 3.2 sed 过滤 3.3 awk 匹配 4 解压缩相关命令 4.1 解压缩文件的意义 4.2 解压缩相关命令 1 …