基于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; }
相关文章:

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

【大数据知识】大数据平台和数据中台的定义、区别以及联系
数据行业有太多数据名词,例如大数据、大数据平台、数据中台、数据仓库等等。但大家很容易混淆,也很容易产生疑问,今天我们就来简单聊聊大数据平台和数据中台的定义、区别以及联系。 大数据平台和数据中台的定义 大数据平台:一个…...

华为OD:IPv4地址转换成整数
题目描述: 存在一种虚拟IPv4地址,由4小节组成,每节的范围为0-255,以#号间隔,虚拟IPv4地址可以转换为一个32位的整数,例如: 128#0#255#255,转换为32位整数的结果为2147549183&#…...

2023.9 - java - 浅拷贝
与 js的浅拷贝不同: 在 JavaScript 中, Object.assign() 或 spread 运算符等方法可以实现浅拷贝,但只针对对象的第一层属性进行复制。如果一个对象只包含基本数据类型的属性,那么对浅拷贝出来的对象进行修改不会影响原始对象&…...

STM32f103入门(10)ADC模数转换器
ADC模数转换器 ADC简介AD单通道初始化代码编写第一步开启时钟第二步 RCCCLK分频 6分频 72M/612M第三步 配置GPIO 配置为AIN状态第四步,选择规则组的输入通道第五步 用结构体 初始化ADC第六步 对ADC进行校准编写获取电压函数初始化代码如下 Main函数编写 ADC简介 ADC…...

实训笔记8.28
实训笔记8.28 8.28笔记一、大数据计算场景主要分为两种1.1 离线计算场景1.2 实时计算场景 二、一般情况下大数据项目的开发流程2.1 数据采集存储阶段2.2 数据清洗预处理阶段2.3 数据统计分析阶段2.4 数据挖掘预测阶段2.5 数据迁移阶段2.6 数据可视化阶段 三、纯大数据离线计算项…...

机器学习笔记之最优化理论与方法(五)凸优化问题(上)
机器学习笔记之最优化理论与方法——凸优化问题[上] 引言凸优化问题的基本定义凸优化定义:示例 凸优化与非凸优化问题的区分局部最优解即全局最优解凸优化问题的最优性条件几种特殊凸问题的最优性条件无约束凸优化等式约束凸优化非负约束凸优化 引言 本节将介绍凸优…...

在Windows10上编译grpc工程,得到protoc.exe和grpc_cpp_plugin.exe
grpc是google于2015年发布的一款跨进程、跨语言、开源的RPC(远程过程调用)技术。使用C/S模式,在客户端、服务端共享一个protobuf二进制数据。在点对点通信、微服务、跨语言通信等领域应用很广,下面介绍grpc在windows10上编译,这里以编译grpc …...

一些测试知识
希望能起到帮助,博主主页: https://blog.csdn.net/qq_57785602/category_12023254.html?spm1001.2014.3001.5482 软件测试理论 测试的依据: 需求,规格说明,模型,用户需求等 什么是软件测试 描述一种来…...

Socket交互的基本流程?
TCP socket通信过程图 什么是网络编程,网络编程就是编写程序使两台连联网的计算机相互交换数据。怎么交换数据呢?操作系统提供了“套接字”(socket)的组件我们基于这个组件进行网络通信开发。tcp套接字工作流程都以“打电话”来生…...

css 分割线中间带文字
效果图 代码块(自适应) <div class"line"><span class"text">我是文字</span></div>.line{height:0;border-top:1px solid #000;text-align:center;}.text{position:relative;top:-14px;background-color:#…...

会不会激发对modern c++的新兴趣
可变参数好像很厉害的样子,会节省很多手写代码,让编译器自动帮我们生成代码 template<typename Fun, typename...Args> void invoke(Fun&& fun, Args&&...args) { fun(std::forward<Args>(args)...); } 任意函数包装器…...

Nginx服务器如何配合Java开发项目
Nginx服务器如何才能配合好相关的编程语言进行服务器搭建呢?下面我们就来看看有关的技术如何融合。希望大家有所收获。 在进行Nginx服务器建设的时候有很多语言的应用,其中Java 开发的web项目就是很常见的。下面我们就看看Nginx服务器如何才能与Java编程…...

【LeetCode-中等题】994. 腐烂的橘子
文章目录 题目方法一:bfs层序遍历 题目 该题值推荐用bfs,因为是一层一层的感染,而不是一条线走到底的那种,所以深度优先搜索不适合 方法一:bfs层序遍历 广度优先搜索,就是从起点出发,每次都尝…...

K8s部署单机mysql
文章目录 一、K8s部署单机mysql1.1 说明1.2 不足 二、部署三、检查 一、K8s部署单机mysql 1.1 说明 定制配置数据存放在configMapmysql数据放在/opt/mysql目录下(/opt/mysql目录需要事先创建)root账号密码使用环境变量env服务暴露方式为nodePort,端口30336 1.2 不…...

Midjourney学习(二)参数的基础
prompt的组成 prompt 可以由三部分组成, 第一部分是垫图部分,也就是一张网络图片 第二部分是文本描述内容 第三部分则是参数 参数列表 --aspect <value> 或者 --ar <value> 控制画面的比例,横竖比例 --version <value> -…...

Ubuntu安装Protobuf,指定版本
参考:https://github.com/protocolbuffers/protobuf#readme https://github.com/protocolbuffers/protobuf/blob/v3.20.3/src/README.md 其实官网的readme给的步骤很详细。 1.安装相关依赖 sudo apt-get install autoconf automake libtool curl make g unzip …...

没有使用sniffer dongle在windows抓包蓝牙方法分享
网上很多文章都是介绍买一个sniffer dongle来抓蓝牙数据,嫌麻烦又费钱,目前找到一个好方法,不需要sniffer就可以抓蓝牙数据过程,现分享如下: (1)在我资源附件找到相关安装包或者查看如下链接 https://learn.microsoft.com/zh-cn/windows-hardware/drivers/bluetooth/testing-bt…...

解决Debian系统通过cifs挂载smb后,中文目录乱码问题
解决Debian系统通过cifs挂载smb后,中文目录乱码问题 //$smb_server/share /mnt/nas_share cifs credentials/root/.smbcredentials,iocharsetutf8 0 0默认通过以上命令挂载smb,但是在查看文件目录时,中文乱码 解决问题方式: de…...

springboot整合jquery实现前后端数据交互
一 实施逻辑 1.1 前端 <!doctype html> <html lang"en"><head><meta charset"UTF-8"><meta name"Generator" content"EditPlus"><meta name"Author" content""><meta n…...

TypeScript 中的类型检查实用函数
TypeScript 中的类型检查实用函数 文章目录 TypeScript 中的类型检查实用函数一、概述二、代码实现 一、概述 在前端开发中,我们经常需要判断变量的类型以进行相应的操作或处理。TypeScript 提供了基础的类型检查,但有时我们需要更复杂或更灵活的类型检…...

JavaScript中的事件委托(event delegation)
聚沙成塔每天进步一点点 ⭐ 专栏简介⭐ JavaScript事件委托⭐ 事件冒泡(Event Bubbling)⭐ 事件委托的优点⭐ 如何使用事件委托⭐ 写在最后 ⭐ 专栏简介 前端入门之旅:探索Web开发的奇妙世界 记得点击上方或者右侧链接订阅本专栏哦 几何带你启…...

ubuntu OCR 脚本
1. 百度 PaddleOCR 介绍 2. 环境安装 pip install paddlepaddle -i https://pypi.tuna.tsinghua.edu.cn/simple # 进入 https://github.com/PaddlePaddle/PaddleOCR # 这里有个 requirements.txt pip install paddleocr -i https://mirror.baidu.com/pypi/simple pip instal…...

Go死码消除
概念: 死码消除(dead code elimination, DCE) 是一种编译器优化技术, 作用是在编译阶段去掉对程序运行结果没有任何影响的代码 和 逃逸分析[1],内联优化[2]并称为 Go编译器执行的三个重要优化 效果: 对于 const.go代码如下: package mainimport "fmt"func max(a, b i…...

基于改进莱维飞行和混沌映射的粒子群优化BP神经网络分类研究(Matlab代码实现)
💥💥💞💞欢迎来到本博客❤️❤️💥💥 🏆博主优势:🌞🌞🌞博客内容尽量做到思维缜密,逻辑清晰,为了方便读者。 ⛳️座右铭&a…...

12. 自动化项目实战
目录 1. 登录测试 2. 测试首页的帖子列表数不为0 3. 帖子详情页校验 4. 发布帖子 5. 退出登录 自动化项目实施的基本流程如下图所示: 手工测试用例、自动化测试用例。 1. 登录测试 校验登录后主页显示的用户名称和登录时输入的用户名是否相等。 public class…...

Window11下载安装jdk8-jdk11与环境变量的配置
目录 一、下载jdk 二、安装jdk 三、配置环境变量 四、检查JDK是否配置成功 一、下载jdk jdk8下载链接:请点击网址 jdk11下载链接:请点击网址 二、安装jdk 按照提示一步一步安装即可。 默认安装位置:C:\Program Files\Java 三、配置…...

Vector Search with OpenAI Embeddings: Lucene Is All You Need
本文是LLM系列文章,针对《Vector Search with OpenAI Embeddings: Lucene Is All You Need》的翻译。 使用OpenAI嵌入的向量搜索:Lucence是你所需的一切 摘要1 引言2 从架构到实现3 实验4 讨论5 结论 摘要 我们在流行的MS MARCO文章排名测试集上使用Lu…...

JS算法与树(二)
前言 二叉搜索树(BST)存在一个问题:当你添加的节点数够多的时候,树的一边可能会非常的深。而其他的分支却只有几层。 AVL树 为了解决上面的问题,我们提出一种自平衡二叉搜索树。意思是任何一个节点左右两侧子树的高度之…...

composer 扩展库。助手库文档
composer helpers packagist 简介 death_satan/composer 作用于在有composer管理工具的项目中。封装了上层由 composer V2 提供的 ClassLoader 和 InstallVersion 轻量级的封装,无任何第三方包集成。便捷式的使用composer V2 API 安装要求 php > 7.4composer &g…...