SpringBoot+Activiti6流程节点的任意跳转

简介: 项目中遇到的要求,在此记录一下。可以回退,可以任意跳转,都会有记录。

接口

import org.activiti.bpmn.model.FlowElement;

import java.util.Collection;
import java.util.Map;

/**
 * @author : ZhiPengyu
 * @ClassName :  [ExecutionService]
 * @Description :  [节点跳转操作]
 * @CreateDate :  [2021/8/11 10:24]
 */
public interface ExecutionService {

    /**
     * 获取流程实例的全部节点<br>
     * jumpSpecifiedNode方法的targetTaskId为FlowElement.getId()
     * @param processDefinitionId 流程定义ID ProcessInstance.getProcessDefinitionId()
     * @return
     */
    Collection<FlowElement> getProcessElements(String processDefinitionId);

    /**
     * 跳转到指定节点<br>
     * 可以跳转到本实例的任意节点-向前向后
     * @param processInstanceId 实例ID
     * @param targetTaskId 任务ID,与运行时任务ID不同,为xml文件上的ID
     * @param variables 本节点处理参数
     * @return
     */
    Boolean jumpSpecifiedNode(String processInstanceId, String targetTaskId, Map<String, Object> variables);

    /**
     * 回退到指定历史节点
     * @param processInstanceId 流程实例ID
     * @param targetTaskId 回跳任务ID,为null是默认选择最近的上一任务
     * @param variables 撤回者处理当前任务参数
     * @return
     */
    Boolean rollBackTargetHisNode(String processInstanceId, String targetTaskId, Map<String, Object> variables);
}

实现

import org.activiti.bpmn.model.*;
import org.activiti.bpmn.model.Process;
import org.activiti.engine.*;
import org.activiti.engine.history.HistoricActivityInstance;
import org.activiti.engine.history.HistoricTaskInstance;
import org.activiti.engine.runtime.Execution;
import org.activiti.engine.runtime.ProcessInstance;
import org.activiti.engine.task.Task;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.util.*;

/**
 * @author : ZhiPengyu
 * @ClassName :  [ExecutionServiceImpl]
 * @Description :  []
 * @CreateDate :  [2021/8/11 10:24]
 */
@Service
public class ExecutionServiceImpl implements ExecutionService {

    /**
     * 仓储服务,用于管理流程仓库,例如:部署,删除,读取流程资源
     * 可以用来部署我们的流程图,还可以创建我们的流程部署查询对象,用于查询刚刚部署的流程列表,便于我们管理流程
     */
    @Autowired
    private RepositoryService repositoryService;

    /**
     * 运行时服务,可以处理所有正在运行状态的流程实例,任务等
     * 主要用来开启流程实例,一个流程实例对应多个任务,也就是多个流程节点
     */
    @Autowired
    private RuntimeService runtimeService;

    /**
     * 任务服务,用于管理,查询任务,例如:签收,办理,指派等
     * 是用来可以用来领取,完成,查询任务列表功能的
     */
    @Autowired
    private TaskService taskService;

    /**
     * 历史服务,可以查询所有历史数据,例如:流程实例,任务,活动,变量,附件等
     * 可以查看审批人曾经审批完成了哪些项目,审批项目总共花了多少时间,以及在哪个环节比较耗费时间等等
     */
    @Autowired
    private HistoryService historyService;

    /**
     * 唯一服务,可以管理,查询用户,组之间的关系
     * 操作用户信息,用户分组信息等,组信息包括如部门表和职位表,可以自己建表来存储用户信息和组信息
     */
    @Autowired
    private IdentityService identityService;

    @Override
    public Collection<FlowElement> getProcessElements(String processDefinitionId) {
        Process process = repositoryService.getBpmnModel(processDefinitionId).getMainProcess();
        Collection<FlowElement> flowElements = process.getFlowElements();
        return flowElements;
    }

    @Override
    public Boolean jumpSpecifiedNode(String processInstanceId, String targetTaskId, Map<String, Object> variables) {
        //获取流程实例
        ProcessInstance pi = runtimeService.createProcessInstanceQuery().processInstanceId(processInstanceId).singleResult();
        if (pi == null){//流程已结束
            return false;
        }
        //当前任务
        Task curTask = taskService.createTaskQuery().processInstanceId(processInstanceId).singleResult();

        BpmnModel bpmnModel = repositoryService.getBpmnModel(curTask.getProcessDefinitionId());

        //目标任务
        Collection<FlowElement> flowElements = bpmnModel.getMainProcess().getFlowElements();
        Optional<FlowElement> optional = flowElements.stream().filter(target -> targetTaskId.equals(target.getId())).findFirst();
        if (!optional.isPresent()){//无指定任务
            return false;
        }
        UserTask targetUserTask = (UserTask) optional.get();

        // 获取目标节点的信息
        FlowNode targetFlowNode = (FlowNode) bpmnModel.getMainProcess().getFlowElement(targetUserTask.getId());
        // 取得当前节点的信息
        Execution execution = runtimeService.createExecutionQuery().executionId(curTask.getExecutionId()).singleResult();
        FlowNode curFlowNode = (FlowNode) bpmnModel.getMainProcess().getFlowElement(execution.getActivityId());

        // 记录当前节点的原活动方向
        List<SequenceFlow> oriSequenceFlows = new ArrayList<>();
        oriSequenceFlows.addAll(curFlowNode.getOutgoingFlows());

        // 清理活动方向
        curFlowNode.getOutgoingFlows().clear();

        // 建立新方向
        List<SequenceFlow> newSequenceFlow = newSequenceFlowList(curFlowNode,targetFlowNode);
        curFlowNode.setOutgoingFlows(newSequenceFlow);

        // 添加批注并完成任务
        commentAndComplete(processInstanceId,curTask.getId(),curTask.getAssignee(),variables,"跳转");

        // 恢复原方向
        curFlowNode.setOutgoingFlows(oriSequenceFlows);

        // 设置执行人
        Task nextTask = taskService.createTaskQuery().processInstanceId(processInstanceId).singleResult();
        if (nextTask != null) {
            taskService.setAssignee(nextTask.getId(), targetUserTask.getAssignee());
        }
        return true;
    }

    @Override
    public Boolean rollBackTargetHisNode(String processInstanceId, String targetTaskId, Map<String, Object> variables) {
        //获取流程实例
        ProcessInstance pi = runtimeService.createProcessInstanceQuery().processInstanceId(processInstanceId).singleResult();
        if (pi == null){//流程已结束
            return false;
        }
        //对应实例的任务历史节点
        List<HistoricTaskInstance> htiList = historyService.createHistoricTaskInstanceQuery().processInstanceId(processInstanceId).orderByTaskCreateTime().desc().list();
        // 跳转的之前某一节点
        HistoricTaskInstance targetTask = getTargetTask(htiList,targetTaskId);
        // list里第一条代表当前任务
        HistoricTaskInstance curTask = htiList.get(0);

        if (targetTask == null){
            return false;
        }
        targetTaskId = targetTask.getId();

        BpmnModel bpmnModel = repositoryService.getBpmnModel(targetTask.getProcessDefinitionId());

        // 得到ActivityId,只有HistoricActivityInstance对象里才有此方法
        String lastActivityId = getLastActivityId(targetTaskId,targetTask.getExecutionId());

        // 得到上个节点的信息
        FlowNode lastFlowNode = (FlowNode) bpmnModel.getMainProcess().getFlowElement(lastActivityId);

        // 取得当前节点的信息
        Execution execution = runtimeService.createExecutionQuery().executionId(curTask.getExecutionId()).singleResult();
        FlowNode curFlowNode = (FlowNode) bpmnModel.getMainProcess().getFlowElement(execution.getActivityId());

        // 记录当前节点的原活动方向
        List<SequenceFlow> oriSequenceFlows = new ArrayList<>();
        oriSequenceFlows.addAll(curFlowNode.getOutgoingFlows());

        // 清理活动方向
        curFlowNode.getOutgoingFlows().clear();

        // 建立新方向
        List<SequenceFlow> newSequenceFlow = newSequenceFlowList(curFlowNode,lastFlowNode);
        curFlowNode.setOutgoingFlows(newSequenceFlow);

        // 添加批注并完成任务
        Task task = taskService.createTaskQuery().processInstanceId(processInstanceId).singleResult();
        commentAndComplete(processInstanceId,task.getId(),task.getAssignee(),variables,"撤回");

        // 恢复原方向
        curFlowNode.setOutgoingFlows(oriSequenceFlows);

        Task nextTask = taskService.createTaskQuery().processInstanceId(processInstanceId).singleResult();
        // 设置执行人
        if (nextTask != null) {
            taskService.setAssignee(nextTask.getId(), targetTask.getAssignee());
        }
        return true;
    }

    /**
     * 添加批注并完成任务
     * @param processInstanceId
     * @param curTaskId 当前任务ID
     * @param targetTaskAssignee
     * @param variables
     * @param message
     */
    private void commentAndComplete(String processInstanceId,String curTaskId,String targetTaskAssignee,Map<String, Object> variables,String message){
        identityService.setAuthenticatedUserId(targetTaskAssignee);
        taskService.addComment(curTaskId,processInstanceId,message);
        taskService.complete(curTaskId,variables);
    }

    /**
     * 获取指定的目标历史任务
     * @param htiList 历史任务列表
     * @param targetTaskId 目标任务ID,为null是默认选择最近的上一任务
     * @return
     */
    private HistoricTaskInstance getTargetTask(List<HistoricTaskInstance> htiList,String targetTaskId){
        HistoricTaskInstance targetTask = null;
        if (targetTaskId == null){
            targetTask = htiList.get(1);
        } else {
            Optional<HistoricTaskInstance> optional = htiList.stream().filter(hisTask -> targetTaskId.equals(hisTask.getId())).findFirst();
            targetTask = optional.isPresent() ? optional.get() : null;
        }
        return targetTask;
    }

    /**
     * 新建连线
     * @param curFlowNode 当前节点
     * @param targetFlowNode 目标节点
     * @return
     */
    private List<SequenceFlow> newSequenceFlowList(FlowNode curFlowNode,FlowNode targetFlowNode) {
        List<SequenceFlow> newSequenceFlowList = new ArrayList<>();
        SequenceFlow newSequenceFlow = new SequenceFlow();
        newSequenceFlow.setId("newJumpFlow");
        newSequenceFlow.setSourceFlowElement(curFlowNode);
        newSequenceFlow.setTargetFlowElement(targetFlowNode);
        newSequenceFlowList.add(newSequenceFlow);
        return newSequenceFlowList;
    }

    /**
     * 获取目标历史节点的活动ID
     * @param targetTaskId 目标历史节点
     * @param targetExecutionId 目标历史执行流ID
     * @return
     */
    private String getLastActivityId(String targetTaskId,String targetExecutionId){
        List<HistoricActivityInstance> haiFinishedList = historyService.createHistoricActivityInstanceQuery().executionId(targetExecutionId).finished().list();
        Optional<HistoricActivityInstance> optional = haiFinishedList.stream().filter(his -> targetTaskId.equals(his.getTaskId())).findFirst();
        String lastActivityId = optional.isPresent() ? optional.get().getActivityId() : null;
        return lastActivityId;
    }

}
相关文章
|
10月前
|
XML JSON Java
【SpringBoot(三)】从请求到响应再到视图解析与模板引擎,本文带你领悟SpringBoot请求接收全流程!
Springboot专栏第三章,从请求的接收到视图解析,再到thymeleaf模板引擎的使用! 本文带你领悟SpringBoot请求接收到渲染的使用全流程!
647 3
SpringBoot启动流程
springboot项目在启动的时候, 首先会执行启动引导类里面的SpringApplication.run(AdminApplication.class, args)方法 这个run方法主要做的事情可以分为三个部分 : 第一部分进行SpringApplication的初始化模块,配置一些基本的环境变量、资源、构造器、监听器 第二部分实现了应用具体的启动方案,包括启动流程的监听模块、加载配置环境模块、及核心的创建上下文环境模块 第三部分是自动化配置模块,该模块作为springboot自动配置核心,在后面的分析中会详细讨论
|
Java 数据库 开发者
详细介绍SpringBoot启动流程及配置类解析原理
通过对 Spring Boot 启动流程及配置类解析原理的深入分析,我们可以看到 Spring Boot 在启动时的灵活性和可扩展性。理解这些机制不仅有助于开发者更好地使用 Spring Boot 进行应用开发,还能够在面对问题时,迅速定位和解决问题。希望本文能为您在 Spring Boot 开发过程中提供有效的指导和帮助。
2502 12
|
IDE Java 应用服务中间件
spring boot 启动流程
Spring Boot 启动流程简介: 在使用 Spring Boot 之前,启动 Java Web 应用需要配置 Web 容器(如 Tomcat),并将应用打包放入容器目录。而使用 Spring Boot,只需运行 main() 方法即可启动 Web 应用。Spring Boot 的核心启动方法是 SpringApplication.run(),它负责初始化和启动应用上下文。 主要步骤包括: 1. **应用启动计时**:使用 StopWatch 记录启动时间。 2. **打印 Banner**:显示 Spring Boot 的 LOGO。 3. **创建上下文实例**:通过反射创建
950 5
|
XML Java 应用服务中间件
SpringBoot项目打war包流程
本文介绍了将Spring Boot项目改造为WAR包并部署到外部Tomcat服务器的步骤。主要内容包括:1) 修改pom.xml中的打包方式为WAR;2) 排除Spring Boot内置的Tomcat依赖;3) 添加Servlet API依赖;4) 改造启动类以支持WAR部署;5) 打包和部署。通过这些步骤,可以轻松地将Spring Boot应用转换为适合外部Tomcat服务器的WAR包。
927 64
SpringBoot项目打war包流程
|
XML 前端开发 Java
SpringBoot整合Flowable【04】- 通过代码控制流程流转
本文介绍了如何使用Flowable的Java API控制流程流转,基于前文构建的绩效流程模型。首先,通过Flowable-UI导出模型文件并部署到Spring Boot项目中。接着,详细讲解了如何通过代码部署、启动和审批流程,涉及`RepositoryService`、`RuntimeService`和`TaskService`等核心服务类的使用。最后,通过实际操作演示了流程从部署到完成的全过程,并简要说明了相关数据库表的变化。本文帮助读者初步掌握Flowable在实际业务中的应用,后续将深入探讨更多高级功能。
2517 0
SpringBoot整合Flowable【04】-  通过代码控制流程流转
|
存储 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代码控制流程的方法。
4918 2
SpringBoot整合Flowable【03】- 通过Flowable-UI体验一个简单流程
|
NoSQL Java Redis
shiro学习四:使用springboot整合shiro,正常的企业级后端开发shiro认证鉴权流程。使用redis做token的过滤。md5做密码的加密。
这篇文章介绍了如何使用Spring Boot整合Apache Shiro框架进行后端开发,包括认证和授权流程,并使用Redis存储Token以及MD5加密用户密码。
598 0
shiro学习四:使用springboot整合shiro,正常的企业级后端开发shiro认证鉴权流程。使用redis做token的过滤。md5做密码的加密。
|
JSON 前端开发 JavaScript
优雅!Spring Boot 3.3 实现职责链模式,轻松应对电商订单流程
本文介绍如何使用 Spring Boot 3.3 实现职责链模式,优化电商订单处理流程。通过将订单处理的各个环节(如库存校验、优惠券核验、支付处理等)封装为独立的处理器,并通过职责链将这些处理器串联起来,实现了代码的解耦和灵活扩展。具体实现包括订单请求类 `OrderRequest`、抽象处理器类 `OrderHandler`、具体处理器实现(如 `OrderValidationHandler`、`VerifyCouponHandler` 等)、以及初始化职责链的配置类 `OrderChainConfig`。
|
XML Java 应用服务中间件
【Spring】运行Spring Boot项目,请求响应流程分析以及404和500报错
【Spring】运行Spring Boot项目,请求响应流程分析以及404和500报错
2024 2