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;
    }

}
相关文章
|
7月前
|
前端开发 Java 应用服务中间件
SpringBoot-Run启动流程
探索Spring Boot启动流程,从加载配置、创建应用上下文、自动配置到启动内嵌服务器。启动类是入口点,`@SpringBootApplication`标记启动,`SpringApplication.run`启动应用。自动配置基于条件注解配置Bean,应用上下文由`SpringApplication`创建并刷新。内嵌服务器如Tomcat随应用启动,简化部署。理解此流程有助于深入掌握Spring Boot。
215 2
|
7月前
|
缓存 Java 程序员
springboot的启动流程总结
springboot的启动流程总结
|
7月前
|
设计模式 Java 容器
SpringBoot2 | SpringBoot启动流程源码分析(二)
SpringBoot2 | SpringBoot启动流程源码分析(二)
80 0
|
2月前
|
前端开发 Java 数据安全/隐私保护
用户登录前后端开发(一个简单完整的小项目)——SpringBoot与session验证(带前后端源码)全方位全流程超详细教程
文章通过一个简单的SpringBoot项目,详细介绍了前后端如何实现用户登录功能,包括前端登录页面的创建、后端登录逻辑的处理、使用session验证用户身份以及获取已登录用户信息的方法。
237 2
用户登录前后端开发(一个简单完整的小项目)——SpringBoot与session验证(带前后端源码)全方位全流程超详细教程
|
6月前
|
JavaScript Java 测试技术
基于springboot+vue.js+uniapp小程序的销售项目流程化管理系统附带文章源码部署视频讲解等
基于springboot+vue.js+uniapp小程序的销售项目流程化管理系统附带文章源码部署视频讲解等
66 3
|
2月前
|
NoSQL Java Redis
shiro学习四:使用springboot整合shiro,正常的企业级后端开发shiro认证鉴权流程。使用redis做token的过滤。md5做密码的加密。
这篇文章介绍了如何使用Spring Boot整合Apache Shiro框架进行后端开发,包括认证和授权流程,并使用Redis存储Token以及MD5加密用户密码。
32 0
shiro学习四:使用springboot整合shiro,正常的企业级后端开发shiro认证鉴权流程。使用redis做token的过滤。md5做密码的加密。
|
2月前
|
XML Java 应用服务中间件
【Spring】运行Spring Boot项目,请求响应流程分析以及404和500报错
【Spring】运行Spring Boot项目,请求响应流程分析以及404和500报错
187 2
|
2月前
|
JSON 前端开发 JavaScript
优雅!Spring Boot 3.3 实现职责链模式,轻松应对电商订单流程
本文介绍如何使用 Spring Boot 3.3 实现职责链模式,优化电商订单处理流程。通过将订单处理的各个环节(如库存校验、优惠券核验、支付处理等)封装为独立的处理器,并通过职责链将这些处理器串联起来,实现了代码的解耦和灵活扩展。具体实现包括订单请求类 `OrderRequest`、抽象处理器类 `OrderHandler`、具体处理器实现(如 `OrderValidationHandler`、`VerifyCouponHandler` 等)、以及初始化职责链的配置类 `OrderChainConfig`。
|
3月前
|
Java 网络架构
springboot配合thymeleaf,调用接口不跳转页面只显示文本
springboot配合thymeleaf,调用接口不跳转页面只显示文本
137 0
|
4月前
|
XML Java 应用服务中间件
SpringBoot启动流程解析
SpringBoot启动流程解析
54 0