SpringBoot整合Flowable【07】- 驳回节点任务

简介: 本文通过绩效流程的业务场景,详细介绍了如何在Flowable工作流引擎中实现任务驳回功能。具体步骤包括:获取目标任务节点和当前任务节点信息,进行必要的判空和逻辑校验,调用API完成节点回退,并清理相关脏数据(如历史任务和变量)。最后通过测试验证了驳回功能的正确性,确保流程能够成功回退到指定节点并清除中间产生的冗余数据。此功能在实际业务中非常有用,能够满足上级驳回自评等需求。

一、引子

在06篇中我以查询历史绩效结果这个业务场景,来帮助大家熟悉和掌握相关API的使用,通过查询历史流程和变量来实现这个目的。回到我们本篇的博客,我们依然从业务场景出发,还是这个绩效流程,如果你的上级对你的自评结果不满意,他想要驳回让你重评;或者你自评后发现有遗漏填写数据需要补充,也需要上级给你驳回重填。

通过以上的业务场景,我们可以发现其实驳回功能在实际业务中是一个很普遍且有用的功能。因此,在本篇中我们将学习如何对任务进行驳回。

二、问题分析

首先抛开技术,我们只是想想将当前任务节点驳回到上个任务节点这个操作都需要什么?答案就在问题上,需要当前所在处的任务节点信息以及要驳回到的目标节点信息。那么,回到Flowable引擎上,它支持将一个任务节点驳回到历史走过的任意一个节点。我们这里的需求很明确,因为是一个绩效流程,所以驳回要驳回到上一个任务节点,即一级一级往回驳,进而我们要知道驳回操作本身很简单,用引擎自带的API就可以实现,主要的工作其实是查和清除:查就是要查当前的节点信息和它的上一个节点信息,而清除则是需要清除节点的老数据,因为驳回之后需要重新提交的。明确了这一点后,我们开始今天的实操。

三、代码编写

1.获取目标任务节点信息

大家想想,在实际业务中,假如我们当前这个绩效流程走到了上级评环节,但在前端展示上通常会把已经走过的节点还是要展示出来,因此前端是有自评节点的任务id的,那么我们设计驳回接口时就可以将要驳回到的前一个任务id由前端传过来,如下:

@PostMapping("/rollback/targetTaskId/{targetTaskId}/userId/{userId}")
public ResponseEntity<Object> rollBackTask(@PathVariable String targetTaskId) {
   
    //1.查询目标任务数据
    HistoricTaskInstance targetTask = historyService
            .createHistoricTaskInstanceQuery()
            .taskId(targetTaskId)
            .singleResult();
    //2.判空
    if (Objects.isNull(targetTask)) {
   
        return new ResponseEntity<>(new BaseResponse<>("驳回目的任务不存在!",null),
                HttpStatus.BAD_REQUEST);
    }
}

通过historyService去查前一个节点的数据并且做相应的判空处理,这里还需要提一点,在前面的博客中我为了演示API,其实省略了很多判断,大家在实际业务中使用的时候,该做的判断可不能少。接下来,我们继续获取当前任务的节点信息。

2.获取当前任务节点信息

通过获取目标任务的数据,我们可以拿到当前流程的实例id,进而通过流程实例id获取当前执行到的任务节点,如下:

//3.获取流程实例id
String processInstanceId = targetTask.getProcessInstanceId();
//4.获取当前任务节点
Task sourceTask = taskService.createTaskQuery()
        .processInstanceId(processInstanceId)
        .active()
        .singleResult();
//5.判空
if (Objects.isNull(sourceTask)) {
   
    return new ResponseEntity<>(new BaseResponse<>("驳回当前任务不存在!",null),
            HttpStatus.BAD_REQUEST);
}
//6.不能驳回到自己
if (Objects.equals(targetTask, sourceTask)) {
   
    return new ResponseEntity<>(new BaseResponse<>("不能驳回到当前节点!",null),
            HttpStatus.BAD_REQUEST);
}

在业务需求下该有的判断都得有,当前节点都判断了,我们还需要对目标节点做下判断,这里可能有同学会有疑问,为什么通过查询历史节点就可以找到的目标节点,这里还要再回查流程定义呢?这里我们需要科普一下:其实这里的“回退”并不真的是回退到目标节点,因为目标节点实际上已经执行过了,我们要做的是根据流程定义创造出一个新的目标节点,然后回到它那一步继续执行,代码如下:

//7.获取流程定义id
String processDefinitionId = targetTask.getProcessDefinitionId();
//8.使用流程定义id查询流程定义,获取流程定义的所有节点信息
BpmnModel bpmnModel = repositoryService.getBpmnModel(processDefinitionId);
Process process = bpmnModel.getProcesses().get(0);
Collection<FlowElement> flowElements = process.getFlowElements();
//9.遍历流程定义的所有节点,找到目标节点的定义信息
FlowElement targetElement = null;
for (FlowElement flowElement : flowElements) {
   
    if (ObjectUtil.equal(flowElement.getId(), targetTask.getTaskDefinitionKey())) {
   
        targetElement = flowElement;
        break;
    }
}
//10.判空
if (Objects.isNull(targetElement)) {
   
    return new ResponseEntity<>(new BaseResponse<>("驳回目的任务定义不存在!",null),
            HttpStatus.BAD_REQUEST);
}

3.回退节点

该做的判断都做了,接下来就该调用相关API进行节点回退了,我把相关逻辑进行了封装,如下:

/**
 * 回退节点并清理数据.
 *
 * @param sourceTask 当前任务
 * @param targetTask 目标任务
 * @return 驳回后的任务
 */
public Task rollbackTask(Task sourceTask, HistoricTaskInstance targetTask) {
   
    //1.获取流程实例id
    String processInstanceId = sourceTask.getProcessInstanceId();
    //2.回退节点
    runtimeService.createChangeActivityStateBuilder()
            //指定流程实例ID
            .processInstanceId(processInstanceId)
            //指定源节点和目标节点
            .moveActivityIdTo(sourceTask.getTaskDefinitionKey(), targetTask.getTaskDefinitionKey())
            .changeState();
    //3.清理两个节点之间的脏数据
    clearDirtyData(processInstanceId, sourceTask, targetTask);
    //4.返回新目标节点
    return taskService.createTaskQuery()
            //指定流程实例ID
            .processInstanceId(processInstanceId)
            //指定目标节点定义
            .taskDefinitionKey(targetTask.getTaskDefinitionKey())
            .singleResult();
}

4.处理老节点的脏数据

但是仅仅是处理节点还是不够的,大家想想我们在这个流程里还有什么东西也需要处理一下呢?没错,就是流程变量,对老节点处理同时也要对老节点关联的变量进行处理,代码如下:

/**
 * 清除节点脏数据.
 *
 * @param processInstanceId 流程实例id
 * @param sourceTask      当前任务id
 * @param targetTask      目标任务id
 */
private void clearDirtyData(String processInstanceId, Task sourceTask,
                            HistoricTaskInstance targetTask) {
   
    //1.获取历史任务列表
    List<HistoricTaskInstance> historyTasks = historyService
            .createHistoricTaskInstanceQuery()
            .processInstanceId(processInstanceId)
            //按照任务开始时间降序排序
            .orderByHistoricTaskInstanceStartTime().desc()
            .list();
    //2.获取sourceTaskId和targetTaskId之间的所有任务节点,包括 targetTask 和 sourceTask,回滚之后删除。
    List<HistoricTaskInstance> betweenNodes =
            getBetweenNodes(historyTasks, sourceTask.getId(), targetTask.getId());
    //3.获取节点id列表
    List<String> taskIds = betweenNodes.stream()
            .map(HistoricTaskInstance::getId)
            .toList();
    //4.删除流程变量
    List<String> variableKeys = new ArrayList<>();
    for (String taskId : taskIds) {
   
        //分数key
        String scoreKey = "score";
        //所属人key
        String ownerKey = "ownerId";
        variableKeys.add(scoreKey);
        variableKeys.add(ownerKey);
        //删除历史节点
        historyService.deleteHistoricTaskInstance(taskId);
    }
    //5.删除流程实例中的指定变量
    runtimeService.removeVariables(processInstanceId, variableKeys);
}

getBetweenNodes方法的作用是为了确定需要删除的任务节点范围,因为可能在实际业务场景中,我们是跨节点驳回的,那么这些中间跨过的节点的变量也是需要处理的,不然可能对业务有影响,代码如下:

/**
 * 获取sourceTaskId和targetTaskId之间的所有任务节点,包括 targetTask 和 sourceTask.
 *
 * @param sourceTaskId 当前任务id
 * @param targetTaskId 目标任务id
 * @return 任务节点列表
 */
List<HistoricTaskInstance> getBetweenNodes(List<HistoricTaskInstance> historicTasks,
                                           String sourceTaskId,
                                           String targetTaskId) {
   
    //1.初始化一个列表,用于存储源任务和目标任务之间的所有任务节点
    List<HistoricTaskInstance> betweenNodes = new ArrayList<>();
    //2.定义一个标志,用于判断是否开始记录任务节点
    boolean startRecord = false;
    for (HistoricTaskInstance historicTask : historicTasks) {
   
        //3.如果历史任务的ID等于源任务的ID,那么开始记录任务节点
        if (ObjectUtil.equal(historicTask.getId(), sourceTaskId)) {
   
            startRecord = true;
        }
        //4.如果开始记录任务节点,那么将当前历史任务添加到任务节点列表中
        if (startRecord) {
   
            betweenNodes.add(historicTask);
        }
        //5.如果历史任务的ID等于目标任务的ID,那么停止记录任务节点,并结束循环
        if (ObjectUtil.equal(historicTask.getId(), targetTaskId)) {
   
            break;
        }
    }
    //6.返回任务节点列表
    return betweenNodes;
}

四、测试

完成了驳回接口相关代码的编写,我们可以启动我们的绩效流程进行测试,如下:
1.png
目前处于自评节点,我们调用完成任务的接口将流程走到上级评节点,如下:
2.png
然后调用我们的驳回任务接口,将流程驳回到自评节点,如下:
3.png
响应成功后,我们再看下act_ru_task表里的任务又回到了自评节点,如下:
4.png
除此之外,我们再去看看act_hi_varinst表和act_hi_taskinst表,还记得我们代码里有清除节点脏数据的逻辑吗?让我们看看这两张历史表里的数据的变化:
5.png
可以看到历史任务表里只剩下自评了,按照正常流程执行到的节点都会记录进历史,但上级评已经被成功地清理掉了。
6.png
历史流程变量表里“干干净净”,分数变量也被清除掉了,由此我们的清理脏数据的逻辑也正常执行了。还有一个小细节,就是驳回理由,它是Flowable提供的API添加的,所以自然是有被持久化保存下来的,我们可以在act_hi_comment表中查到,如下:
7.png

五、小结

最近工作很忙,更新节奏比较慢,希望各位读者见谅哈!但好饭不怕晚嘛,我宁愿慢点打磨也不想随随便便写些东西。承蒙各位读者的厚爱,Flowable系列文章将会持续更新,你们的点赞、评论和收藏都是我更新的动力!

目录
相关文章
|
7月前
|
Java 关系型数据库 MySQL
springboot项目集成dolphinscheduler调度器 实现datax数据同步任务
springboot项目集成dolphinscheduler调度器 实现datax数据同步任务
752 2
|
7月前
|
人工智能 安全 Java
Spring Boot 中使用 Function 和异步线程池处理列表拆分任务并汇总结果
在Java开发中,处理大规模数据时常常需要将列表拆分为多个子列表进行异步处理并汇总结果。本文介绍如何在Spring Boot中使用Function和异步线程池实现高效且可维护的代码,涵盖结果封装、线程池配置、列表拆分处理及结果汇总等关键步骤。
344 0
|
8月前
|
SQL JSON Java
|
8月前
|
Java Spring
如何优雅的实现 SpringBoot 并行任务
我是小假 期待与你的下一次相遇 ~
184 1
|
分布式计算 大数据 Java
springboot项目集成大数据第三方dolphinscheduler调度器 执行/停止任务
springboot项目集成大数据第三方dolphinscheduler调度器 执行/停止任务
174 0
|
Java 调度 数据库
SpringBoot整合XXL-JOB【05】- 任务分片
在实际业务中,批量定时任务可能因上一批任务未完成而影响业务。为解决此问题,本文介绍如何使用Xxl-job对批量任务进行分片处理,通过分片广播形式调度集群机器并行执行任务,大幅提升执行效率。具体步骤包括环境准备、添加依赖和配置、声明实体类与查询类,以及改造业务逻辑实现分片查询。测试结果显示,分片处理将两千条数据的执行时间从30秒缩短至15秒,性能提升显著。
1799 13
SpringBoot整合XXL-JOB【05】-  任务分片
|
存储 Java 数据安全/隐私保护
SpringBoot整合Flowable【03】- 通过Flowable-UI体验一个简单流程
本文介绍了如何使用Flowable 7.0以下版本的flowable-ui进行流程建模、发布和执行。首先,通过解压并启动flowable-ui war包,访问http://localhost:8080/flowable-ui/idm/#/login登录系统。接着,创建并绘制一个简单的绩效流程模型,包含开始节点、任务节点(自评、上级评、隔级评)和结束节点,并为各节点分配处理人。然后,创建应用并发布绩效流程。最后,通过创建a、b、c三个用户分别完成各节点任务,演示了整个流程的执行过程。本文旨在帮助读者理解Flowable的基本操作和流程元素,后续将介绍通过Java代码控制流程的方法。
3098 1
SpringBoot整合Flowable【03】- 通过Flowable-UI体验一个简单流程
|
XML 前端开发 Java
SpringBoot整合Flowable【04】- 通过代码控制流程流转
本文介绍了如何使用Flowable的Java API控制流程流转,基于前文构建的绩效流程模型。首先,通过Flowable-UI导出模型文件并部署到Spring Boot项目中。接着,详细讲解了如何通过代码部署、启动和审批流程,涉及`RepositoryService`、`RuntimeService`和`TaskService`等核心服务类的使用。最后,通过实际操作演示了流程从部署到完成的全过程,并简要说明了相关数据库表的变化。本文帮助读者初步掌握Flowable在实际业务中的应用,后续将深入探讨更多高级功能。
1938 0
SpringBoot整合Flowable【04】-  通过代码控制流程流转
|
存储 Java API
SpringBoot整合Flowable【02】- 整合初体验
本文介绍了如何基于Flowable 6.8.1版本搭建工作流项目。首先,根据JDK和Spring Boot版本选择合适的Flowable版本(7.0以下)。接着,通过创建Spring Boot项目并配置依赖,包括Flowable核心依赖、数据库连接等。然后,建立数据库并配置数据源,确保Flowable能自动生成所需的表结构。最后,启动项目测试,确认Flowable成功创建了79张表。文中还简要介绍了这些表的分类和常用表的作用,帮助初学者理解Flowable的工作原理。
2973 0
SpringBoot整合Flowable【02】- 整合初体验
|
XML Java 数据库连接
SpringBoot集成Flowable:打造强大的工作流管理系统
在企业级应用开发中,工作流管理是一个核心组件,它能够帮助我们定义、执行和管理业务流程。Flowable是一个开源的工作流和业务流程管理(BPM)平台,它提供了强大的工作流引擎和建模工具。结合SpringBoot,我们可以快速构建一个高效、灵活的工作流管理系统。本文将探讨如何将Flowable集成到SpringBoot应用中,并展示其强大的功能。
3026 1