关于事务流的思考

news2024/11/25 6:42:27

关于事务流的思考

1 事务流业务分析

​ 不同业务可能有不同的审核流程,而activiti为大家提供了一套公用的审核功能,基于这些功能我们可以根据自己的业务需求组合出我们自己的审核流程,而这里我要实现的事务流有如下功能:角色为结点审核人,指定审核人,退回到上一结点,完成任务,流程预判。

1.1 角色审核人

​ 也就是说在每一个审核结点都是某一个角色的所有用户作为候选审核人,当某个用户不再是这个角色的时候也就不能看到对应的事务流程。

1.2 指定审核人

​ 申请人可以指定一级审核人,审核人可以指定下一级审核人

1.3 退回到上一个结点

​ 可以退回到上一结点

1.4 退回到申请人(拒绝申请)

​ 终止流程,设置变量为不通过

1.5 完成任务

​ 审核人完成任务

1.6 流程预判

​ 可以追踪自己的申请流程

2 activiti框架

​ 我们从使用流程数据表activiti架构API整体代码示例五个方面来介绍。

2.1 activiti使用流程

定义流程模板

  1. 定义bpmn图

​ 流程模板有key和name两个主要的属性,key用于唯一标记此流程模板,name用于提示作用,一个流程模板可以部署多次。

image-20240601100206482

  1. 根据bpmn图部署一套事务流程模板

​ 部署流程模板会把此流程模型信息部署定义信息流程定义信息

(1)流程模型信息

​ 根据bpmn图部署一套事务流模板,activiti会把事务流模型以字节流的形式存储在数据库,所以,每次的流程判断都是要把此信息取出来做分析处理的。

(2)部署定义信息

​ 主要记录本次部署的相关信息,比如部署时间等,用处不大。

(3)流程定义数据

​ 记录流程定义相关重要信息,比如流程key、流程name等。与此同时,由于一个bpmn模板可以部署多次,所以有一个版本的属性来代表此bpmn流程模板的版本,所以此时唯一的id就变成了“流程key”和“版本号”的组合

创建流程实例

申请流程实例化对象——》审核结点完成任务——》结束,在这个过程中涉及多个概念,下面一一概述。

  1. 实例化对象

​ 选择一个事务流程模板,根据此模板实例化一个申请流程

  1. 变量

​ 开启一个流程实例的时候可以给整个流程传入各种变量

​ ①存储方式:变量信息存储在ACT_RU_VARIABLE表,是直接跟流程定义信息绑定的

​ ②用法介绍:有自定义使用和自动使用两种用法,前者指的是你可以在任何结点根据流程id获取变量自由使用,后者则是更加常用的一种方式,详见③

​ ③自动使用:在定义bpmn流程模板的时候,凡是可以自己去定义的数据比如审核人等都可以定义成变量,当实例化一个流程的时候只需要将变量和此流程绑定,后续就可以自动赋值(举例:审核人assignee定义为${assignee0},当实例化一个流程的时候只需要将assignee0绑定此流程即可)

  1. businessKey

​ 业务id,可以与流程实例绑定,后续可以根据流程实例取出

完成任务

  1. 任务

​ 流程走到哪个审核结点,都会给此审核结点生成一个对应的任务,不管审核通过还是退回,此结点都会被销毁(但是会被记录在历史记录里面)

  1. 审核结点

​ 审核人可以有一个或者多个,activiti针对这两种情况分别给出了对应的方案

(1)审核人类型

​ 审核人类型有Assignee、CandidateUser两种类型(CandidataGroups在版本7中弃用了)

​ **①Assignee类型:**指的是审核人,只能指定一个

​ **②CandidateUser类型:**指的是候选人,候选人可以有多个,在activiti中用","隔开就行

(2)完成任务

​ 在activiti中的任务没有审核通过和退回的操作,只有完成任务的操作,而不同的审核人类型完成任务的操作有些许不同。

​ **①Assignee类型:**当流程走到自己的审核结点的时候无需拾取任务,可以直接完成任务

​ **②CandidateUser类型:**当流程走到自己的审核结点的时候需要先拾取任务,再完成任务(所有候选人都可以拾取任务,拾取之后其他人将看不到自己的待办任务,然后拾取人完成任务)

2.2 数据库介绍

表名说明
ACT_GE_PROPERTY数据库表 ACT_GE_PROPERTY 用于存储全局属性(Global Properties)。这些全局属性包含了Activiti引擎的配置信息和状态信息。
NAME:属性名称。 VALUE:属性值。 REV_:属性的版本号。
数据库架构版本:Activiti引擎使用此属性来跟踪已安装数据库架构的版本。 ID生成器:用于生成唯一标识符(ID)的策略。 引擎配置:包括JDBC连接信息、事务管理器、任务执行者等。 引擎状态:例如引擎是否正常运行、是否已暂停等。
ACT_RE_PROCDEF用于存储流程定义的相关信息。
ACT_RU_TIMER_JOB用于存储定时任务(Timer Job)相关的信息。
ACT_PROCDEF_INFO用于存储流程定义的详细信息
INFO_JSON_ID_:指向 ACT_GE_BYTEARRAY 表中存储的流程定义详细信息的 JSON 对象的标识符。
表中的记录存储了与流程定义相关的详细信息,例如表单信息、变量定义等。通过 INFO_JSON_ID_ 列,可以检索到 ACT_GE_BYTEARRAY 表中存储的流程定义详细信息的 JSON 对象。
ACT_RE_DEPLOYMENT用于存储部署的流程定义文件信息
ACT_GE_BYTEARRAY用来存储流程定义文件路径、文件大小等信息(其中DEPLOYMENT_ID字段就是DEPLOYMENT表的ID)
ACT_HI_TASKINST用于存储已完成的任务实例的历史信息(细粒度)
ACT_HI_PROCINST用于存储已完成的任务实例的历史信息(粗粒度)
ACT_HI_ACTINST用于存储已完成的活动实例(包括任务、网关、事件等)的历史信息(任务层级,即每个每个实例的某个任务完成都会存储一条记录在这里)
ACT_HI_IDENTITYLINK用于存储流程实例和任务实例的身份关联历史信息。(USER_ID_:关联的用户标识符)
ACT_RU_EXECUTION一个运行时表,用于存储流程实例和执行实例的运行时信息。
ACT_RU_TASK用于存储任务实例的运行时信息。
ACT_RU_IDENTITYLINK用于存储流程实例和执行实例的运行时的身份关联历史信息。
ACT_HI_VARINST用于存储流程实例和任务实例的变量信息
ACT_RU_VARIABLE存储流程实例和任务实例的流程变量信息

ACT_RE_DEPLOYMENT

ACT_RE_DEPLOYMENT 是 Activiti 7 中的一个数据库表,用于存储部署的流程定义文件信息。

该表包含以下列:

  • ID_:部署记录的唯一标识符。
  • REV_:部署记录的版本号。
  • NAME_:部署的名称。
  • CATEGORY_:部署的类别。
  • KEY_:部署的关键字。
  • TENANT_ID_:部署所属的租户标识符。
  • DEPLOY_TIME_:部署时间。

ACT_RE_DEPLOYMENT 表中的记录存储了每次部署的流程定义文件的信息,例如流程定义的名称、类别、关键字和部署时间等。

ACT_RE_PROCDEF 表的区别在于:

  • ACT_RE_DEPLOYMENT 表存储的是每次部署的流程定义文件的整体信息,一次部署可以包含多个流程定义。
  • ACT_RE_PROCDEF 表存储的是具体的流程定义信息,每个流程定义对应一条记录。

通过 ACT_RE_DEPLOYMENT 表,您可以获取有关部署的流程定义文件的信息。如果您需要查询或操作流程定义的部署信息,可以使用 Activiti 提供的相应 API 来进行操作。在 ACT_RE_DEPLOYMENT 表中,ID_ 字段可以用来查看流程定义文件的整体信息。

每当部署一个流程定义文件时,会在 ACT_RE_DEPLOYMENT 表中创建一条记录,而该记录的 ID_ 字段就是唯一标识符,可以用来唯一地识别和查找该次部署对应的流程定义文件。

您可以通过查询 ACT_RE_DEPLOYMENT 表,并指定相应的条件和字段,获取与流程定义文件相关的信息。例如,可以使用 ID_ 字段来检索特定部署记录的详细信息,包括流程定义文件的名称、版本号、关键字、类别以及部署时间等。

请注意,ACT_RE_DEPLOYMENT 表存储的是流程定义文件的整体信息,在这个表中可以查看到与部署相关的流程定义文件属性,但不能直接访问流程定义文件的具体内容。要访问流程定义文件的具体内容,需要通过 Activiti 提供的 API 或其他工具进行操作。

ACT_HI_TASKINSTACT_HI_PROCINST

ACT_HI_TASKINSTACT_HI_PROCINST 是 Activiti 7 中的两个不同的历史表,用于存储不同类型的历史信息。

ACT_HI_TASKINST 表用于存储已完成的任务实例的历史信息。每当一个任务实例完成时,就会在 ACT_HI_TASKINST 表中创建一条记录,包括任务的名称、描述、开始时间、结束时间等相关信息。该表主要关注已经完成的个别任务实例的详细信息。

ACT_HI_PROCINST 表用于存储已完成的流程实例的历史信息。每当一个流程实例完成时,就会在 ACT_HI_PROCINST 表中创建一条记录,包括流程实例的开始时间、结束时间、持续时间等相关信息。该表主要关注整个流程实例的执行过程和结果。

因此,两者的区别在于关注的层级和粒度不同。ACT_HI_TASKINST 关注于任务级别的历史信息,而 ACT_HI_PROCINST 关注于流程实例级别的历史信息。

需要根据具体的需求选择适合的历史表进行查询和分析。如果需要查看任务的详细信息,可以使用 ACT_HI_TASKINST 表;如果需要了解流程实例的执行情况,可以使用 ACT_HI_PROCINST 表。同时,还可以结合其他的历史表,如 ACT_HI_ACTINSTACT_HI_VARINST 等,来获取更全面的历史信息。

ACT_RU_EXECUTION

该表包含以下列:

  • ID_:执行实例的唯一标识符。
  • PROC_DEF_ID_:流程定义的标识符。
  • PROC_INST_ID_:流程实例的标识符。
  • BUSINESS_KEY_:流程实例的业务关键字。
  • PARENT_ID_:父级执行实例的标识符(可选)。
  • ACT_ID_:当前活动节点的标识符。
  • IS_ACTIVE_:指示执行实例是否处于活动状态。
  • IS_CONCURRENT_:指示执行实例是否是并发执行。
  • IS_SCOPE_:指示执行实例是否是作用域。
  • START_TIME_:执行实例的开始时间。

ACT_RU_EXECUTION 表记录了流程实例和执行实例在运行时的相关信息。每当一个流程实例启动时,就会在该表中创建一条记录,并且在流程实例执行过程中会根据流程定义进行相应的状态更新。通过这个表,可以获取流程实例和执行实例的运行时信息,包括当前活动节点、是否活动、是否并发等。

需要注意的是,ACT_RU_EXECUTION 表中存储的是当前正在运行的流程实例和执行实例的信息。如果需要查询已完成的流程实例和执行实例的历史信息,可以使用相应的历史表,如 ACT_HI_PROCINSTACT_HI_ACTINST 等。

使用 ACT_RU_EXECUTION 表可以了解流程实例和执行实例在运行时的状态和属性信息,对于监控、跟踪和管理流程实例的执行过程非常有用。

ACT_RU_TASK

该表包含以下列:

  • ID_:任务实例的唯一标识符。
  • PROC_DEF_ID_:流程定义的标识符。
  • PROC_INST_ID_:流程实例的标识符。
  • EXECUTION_ID_:执行实例的标识符。
  • NAME_:任务名称。
  • PARENT_TASK_ID_:父级任务实例的标识符(可为空)。
  • DESCRIPTION_:任务描述。
  • OWNER_:任务的所有者。
  • ASSIGNEE_:任务的代理人。
  • CREATE_TIME_:任务创建时间。
  • DUE_DATE_:任务的到期时间。
  • SUSPENSION_STATE_:任务的暂停状态。

ACT_RU_TASK 表记录了任务实例在运行时的相关信息。每当一个任务实例创建时,就会在该表中创建一条记录,并且在任务的执行过程中会根据任务的状态进行相应的更新。通过这个表,可以获取任务实例的运行时信息,包括任务名称、所有者、代理人、创建时间等。

需要注意的是,ACT_RU_TASK 表中存储的是当前正在运行的任务实例的信息。如果需要查询已完成的任务实例的历史信息,可以使用相应的历史表,如 ACT_HI_TASKINST

使用 ACT_RU_TASK 表可以了解任务实例在运行时的状态和属性信息,对于任务的管理、分配和跟踪非常有用。

2.3 重要概念以及数据结构

image-20240605085714062

RepositoryService

常用方法:

方法名作用
createDeployment()部署流程模型
createDeploymentQuery()查询流程模型
delelteDeployment()删除流程模型
getModel()获取模型信息

RuntimeService

常用方法:

方法名作用
startProcessInstanceById()生成一个流程实例
createProcessInstanceQuery()查询流程实例
delelteProcessInstance()删除流程实例
getVariables()获取变量信息
setVariables()

TaskService

常用方法:

方法名作用
createTaskQuery()查询任务信息
setAssignee()设置任务的执行人
claim()候选人拾取任务
complete()执行人完成任务
addComment()给任务节点添加备注
getVariables()获取流程实例变量

HistoryService

2.4 API代码示例

​ 在版本7中identityService和FormService弃用了

image-20240601110416384

部署流程模板

​ 两种方法:绝对路径和资源路径

  1. 方式一:绝对路径
    @Test
    void deployProcessDefinition() throws FileNotFoundException {
        String processDefinitionFilePath = "D:\\reponsitory\\maven+activiti7\\springboot_activiti\\springboot_activiti\\src\\main\\resources\\test.bpmn20.xml";
        Deployment deployment = this.repositoryService.createDeployment()
                .addInputStream(processDefinitionFilePath, new FileInputStream(processDefinitionFilePath))
                .name("test")//设置部署定义名
                .key("test")//设置部署定义key
                .deploy();
        System.out.println("部署流程定义成功:"+ deployment);
    }
  1. 方式二:资源路径
    @Test
    public void testDeployment(){
        // 使用RepositoryService进行部署
        Deployment deployment = repositoryService.createDeployment()
                .addClasspathResource("test.bpmn20.xml") // 添加bpmn资源,在resouces里面的路径
                .name("test")//设置部署定义名
                .key("test")//设置部署定义key
                .deploy();
        //输出部署信息
        System.out.println("部署流程定义成功:"+ deployment);
    }

启动流程实例

 @Test
    void startProcessInstance() {

        //1 启动流程时传递的参数列表 这里根据实际情况 也可以选择不传
        Map<String, Object> variables = new HashMap<String, Object>();
        variables.put("assignee1", "张三");
        
        //2 定义businessKey  businessKey一般为流程实例key与实际业务数据的结合
        String businessKey = "1001";//假设一个请假的业务 在数据库中的id是1001
        //3 设置启动流程的人
        Authentication.setAuthenticatedUserId("sheshe");

        //4 根据流程定义ID查询流程定义
        ProcessDefinition processDefinition = repositoryService.createProcessDefinitionQuery()
                .deploymentId("69317be4-1f50-11ef-915a-00ff011acb82")
                .singleResult();

        //5 根据流程定义key启动一个流程实例,传入业务id以及变量
        ProcessInstance processInstance = runtimeService.startProcessInstanceByKey(processDefinition.getKey(), businessKey, variables);
        processInstance.getProcessDefinitionId();
        System.out.println("流程启动成功:" + processInstance);
    }

查找组任务

候选人查找可以拾取的任务

    @Test
    public void findGroupTaskList(){
        //1 流程定义的Key(注意这里是流程定义key,不是部署定义key)
        String key = "test";
        //2 任务候选人
        String candidateUser = "2022073";
        //3 查询组任务
        List<Task> taskList = taskService.createTaskQuery()
                .processDefinitionKey(key)
                .taskCandidateUser(candidateUser) //根据候选人查询任务
                .list();
        for (Task task : taskList) {
            System.out.println("========================");
            System.out.println("流程实例ID="+task.getProcessInstanceId());
            System.out.println("任务id="+task.getId());
            System.out.println("任务负责人="+task.getAssignee());
        }
    }

候选人拾取任务

    @Test
    public void claimTask(){
        // 当前任务的id
        String taskId = "75002";
        // 任务候选人
        String candidateUser = "wangwu";
        //1 查询任务
        Task task = taskService.createTaskQuery()
                .taskId(taskId)
                .taskCandidateUser(candidateUser)
                .singleResult();
        //2 拾取任务
        if(task != null){
            taskService.claim(taskId,candidateUser);
            System.out.println("taskid-"+taskId+"-用户-"+candidateUser+"-拾取任务完成");
        }
    }

完成任务

    @Test
    public void completTask(){
        // 返回一个任务对象
        Task task = taskService.createTaskQuery()
                .processDefinitionKey("test") //流程Key
                .taskAssignee("worker")  //要查询的负责人
                .singleResult();

        // 完成任务,参数:任务id
        taskService.complete(task.getId());
    }

指定审核人

指定下一个审核人

    @Test
    public void testAssigneeToCandidateUser(){
        // 当前任务的id
        String taskId = "75002";
        // 要指定的任务负责人
        String assignee = "lisi";
        // 查询任务
        Task task = taskService.createTaskQuery()
                .taskId(taskId)
                .singleResult();
        // 交接任务
        if(task != null){
            taskService.setAssignee(taskId,assignee);
            System.out.println("taskid-"+taskId+"-交接任务完成");
        }
    }

流程预判

public class ApproveNode {
    private String nodeName;
    private String approvers;

    public ApproveNode(String nodeName, String approvers) {
        this.nodeName = nodeName;
        this.approvers = approvers;
    }

    //getter and setter
    //....
}
import com.roy.demo.pojo.ApproveNode;
import de.odysseus.el.ExpressionFactoryImpl;
import de.odysseus.el.util.SimpleContext;
import org.activiti.bpmn.model.*;
import org.activiti.engine.RepositoryService;
import org.activiti.engine.RuntimeService;
import org.activiti.engine.TaskService;
import org.activiti.engine.task.Task;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import javax.el.ExpressionFactory;
import javax.el.ValueExpression;
import java.util.*;

/**
 * @Description:
 * @author: jianfeng.zheng
 * @since: 2021/1/28 12:40 上午
 * @history: 1.2021/1/28 created by jianfeng.zheng
 */
@Service
public class PreviewProcessService {

    @Autowired
    private RuntimeService runtimeService;
    @Autowired
    private TaskService taskService;
    @Autowired
    private RepositoryService repositoryService;


    public List<ApproveNode> getPreviewNodes(String taskId) {
        /**
         * 获取代办任务信息
         */
        Task task = taskService.createTaskQuery()
                .taskId(taskId)
                .singleResult();
        //获取流程模型
        BpmnModel model = repositoryService.getBpmnModel(task.getProcessDefinitionId());
        //获取当前节点
        FlowElement flowElement = model.getFlowElement(task.getTaskDefinitionKey());
        //获取流程变量
        Map<String, Object> params = runtimeService.getVariables(task.getExecutionId());
        //保存访问过的节点,避免死循环
        Set<String> visitedElements = new HashSet<>();
        //递归获取所有预测节点
        List<ApproveNode> approveNodes = visiteElement(flowElement, params, visitedElements);
        return approveNodes;
    }

    /**
     * 递归获取预测节点列表
     *
     * @param flowElement
     * @param params
     * @param visitedElements
     * @return
     */
    private List<ApproveNode> visiteElement(FlowElement flowElement, Map<String, Object> params, Set<String> visitedElements) {
        String id = flowElement.getId();
        //如果之前访问过的节点就不再访问
        if (visitedElements.contains(id)) {
            return Collections.EMPTY_LIST;
        }
        visitedElements.add(id);
        List<ApproveNode> nodes = new ArrayList<>();
        //UserTask是实际的审批节点,如果是UserTask就可以加入到预测的节点中
        if (flowElement instanceof UserTask) {
            UserTask item = (UserTask) flowElement;
            nodes.add(new ApproveNode(item.getName(), this.executeExpression(item.getAssignee(), params, String.class)));
        }

        //获取所有的出口,也就是流程模型中的连线
        List<SequenceFlow> sequenceFlows = this.getElementSequenceFlow(flowElement);
        if (sequenceFlows == null || sequenceFlows.isEmpty()) {
            return nodes;
        }
        FlowElement nextElement = null;
        if (sequenceFlows.size() == 1 && sequenceFlows.get(0).getConditionExpression() == null) {
            /**
             * 如果只有一条连线并且没有设置流转条件,直接获取连线目标节点作为下一节点
             */
            nextElement = sequenceFlows.get(0).getTargetFlowElement();
        } else {
            for (SequenceFlow seq : sequenceFlows) {
                if (seq.getConditionExpression() == null) {
                    /**
                     * 如果没有条件符合,那么就取获取到的第一条条件为空的节点
                     */
                    if (nextElement == null) {
                        nextElement = seq.getTargetFlowElement();
                    }
                } else {
                    /**
                     * 计算条件
                     */
                    boolean value = this.verificationExpression(seq.getConditionExpression(), params);
                    if (value) {
                        nextElement = seq.getTargetFlowElement();
                        break;
                    }
                }
            }
        }
        nodes.addAll(this.visiteElement(nextElement, params, visitedElements));
        return nodes;
    }

    /**
     * 获取流程连线
     *
     * @param flowElement
     * @return
     */
    private List<SequenceFlow> getElementSequenceFlow(FlowElement flowElement) {
        if (flowElement instanceof FlowNode) {
            return ((FlowNode) flowElement).getOutgoingFlows();
        }
        return Collections.EMPTY_LIST;
    }

    /**
     * 执行表达式计算
     * @param expression
     * @param variableMap
     * @param returnType
     * @param <T>
     * @return
     */
    private <T> T executeExpression(String expression, Map<String, Object> variableMap, Class<T> returnType) {
        if (expression == null) {
            return null;
        }
        ExpressionFactory factory = new ExpressionFactoryImpl();
        SimpleContext context = new SimpleContext();
        for (String k : variableMap.keySet()) {
            context.setVariable(k, factory.createValueExpression(variableMap.get(k), Object.class));
        }
        ValueExpression valueExpression = factory.createValueExpression(context, expression, returnType);
        return (T) valueExpression.getValue(context);

    }

    /**
     * 验证表达式结果 true/false
     * @param expression
     * @param variableMap
     * @return
     */
    private Boolean verificationExpression(String expression, Map<String, Object> variableMap) {
        Boolean value = this.executeExpression(expression, variableMap, Boolean.class);
        return value == null ? false : value;
    }
}

2.5 整体代码示例

@Service
public class WorkflowServiceImpl implements WorkflowService {

    @Autowired
    private PreviewWorkFlowProcessUtil previewWorkFlowProcessUtil;

    @Autowired
    private WfRoleMapService wfRoleMapService;

    @Autowired
    private WfRoleMapMapper wfRoleMapMapper;

    @Autowired
    private RuntimeService runtimeService;

    @Autowired
    private TaskService taskService;

    @Autowired
    private RepositoryService repositoryService;

    @Autowired
    private HistoryService historyService;

    @Autowired
    SystemAPI systemAPI;
    // 部署流程模板
    @Override
    public String deployProcessDefinition(String business, String businessName, String tableName) {
        // 使用RepositoryService进行部署
//        String processDefinitionFilePath = "D:\\reponsitory\\collegeManageSystem\\server\\wisdomAcademy-microservices-new\\publicservice\\src\\main\\resources\\bpmn\\employmentIntention.bpmn20.xml";
        Deployment deployment = repositoryService.createDeployment()
//                .addInputStream(processDefinitionFilePath, new FileInputStream(processDefinitionFilePath))
                .addClasspathResource("bpmn/"+ business +".bpmn20.xml") // 添加bpmn资源,在resouces里面的路径
                .name(businessName)//设置部署定义名
                .key(business)//设置部署定义key
                .deploy();
        //4 根据流程部署ID查询流程定义
        ProcessDefinition processDefinition = repositoryService.createProcessDefinitionQuery()
                .deploymentId(deployment.getId())
                .singleResult();
        //输出部署信息
        System.out.println("流程部署id:"+ deployment.getId());
        System.out.println("流程定义id:"+ processDefinition.getId());
        WfRoleMap wfRoleMap = new WfRoleMap();
        wfRoleMap.setDefinitionId(processDefinition.getId());
        wfRoleMap.setDeploymentId(deployment.getId());
        wfRoleMap.setBusinessTable(tableName);
        wfRoleMap.setBusinessName(businessName);
        return "流程部署成功";
    }


    // 提交一个申请,可以指定一级审核人
    @Override
    public String startProcessInstance(String tableName, String businessKey, String assignee, String apply) {
        String deploymentId = "";
        String definitionId = "";

        List<WfRoleMap> wfRoleMapList = wfRoleMapService.list(new LambdaQueryWrapper<WfRoleMap>().eq(WfRoleMap::getBusinessTable, tableName));
        if (wfRoleMapList.size() != 0){
            //1. 获取此流程所需哪些变量以及对应的业务id
            deploymentId = wfRoleMapList.get(0).getDeploymentId();
            definitionId = wfRoleMapList.get(0).getDefinitionId();
            // 获取角色信息并存储变量
            Map<String, Object> variables = new HashMap<String, Object>();
            for (int i = 0; i < wfRoleMapList.size(); i++) {
                WfRoleMap wfRoleMap = wfRoleMapList.get(i);
                List<SysUserDTO> sysUserDTOList = systemAPI.getUsersByRoleId(wfRoleMap.getRoleId());
                variables.put(wfRoleMap.getRoleType(), sysUserDTOList.stream().map(SysUserDTO::getUserAccount).collect(Collectors.joining(",")));
            }
            //2. 设置启动流程的人
            Authentication.setAuthenticatedUserId(apply);
            //3. 启动流程实例
            ProcessInstance processInstance = runtimeService.startProcessInstanceById(definitionId, businessKey, variables);
            System.out.println("流程启动成功:" + processInstance.getProcessInstanceId());
            //4. 如果指定了一级审核人就设置上
            if (!StringUtils.isEmpty(assignee)){
                Task task = taskService.createTaskQuery()
                        .processInstanceId(processInstance.getProcessInstanceId())
                        .singleResult();
                taskService.setAssignee(task.getId(), assignee);
            }
            return WorkFlowENUM.GLOBAL_PENDING.getAbbreviation();
        }
        return "失败";
    }

    // 返回自己待办的任务
    @Override
    public List<WfTaskInfoAuitVO> findTaskUnCompleted(String account, String tableName) {
        // 查询组任务
        TaskQuery taskQuery = taskService.createTaskQuery().taskCandidateOrAssigned(account);

        // 如果tableName不为空,则只查询此业务的任务
        if (!StringUtils.isEmpty(tableName)) {
            List<WfRoleMap> wfRoleMapList = wfRoleMapService.list(new LambdaQueryWrapper<WfRoleMap>().eq(WfRoleMap::getBusinessTable, tableName));
            if (!wfRoleMapList.isEmpty()) {
                taskQuery.processDefinitionId(wfRoleMapList.get(0).getDefinitionId());
            } else {
                // 如果不存在则填入一个不存在的流程定义
                taskQuery.processDefinitionId("0");
            }
        }

        List<Task> taskList = taskQuery.list();
        List<WfTaskInfoVO> wfTaskInfoVOList = new ArrayList<>();

        for (Task task : taskList) {
            ProcessInstance processInstance = runtimeService.createProcessInstanceQuery().processInstanceId(task.getProcessInstanceId()).singleResult();
            if (processInstance != null) {
                WfTaskInfoVO wfTaskInfoVO = new WfTaskInfoVO();
                wfTaskInfoVO.setTaskId(task.getId());
                wfTaskInfoVO.setNodeName(task.getName());
                wfTaskInfoVO.setApplyUser(UserUtil.getSysUserDTO(processInstance.getStartUserId())); // 获取启动实例的人
                wfTaskInfoVO.setApplyTime(processInstance.getStartTime());
                wfTaskInfoVO.setBusinessName(processInstance.getName());
                wfTaskInfoVO.setBusinessKey(processInstance.getBusinessKey());
                wfTaskInfoVOList.add(wfTaskInfoVO);
            } else {
                System.out.println("ProcessInstance not found for taskId: " + task.getId());
            }
        }

        // 按照申请时间排序
        wfTaskInfoVOList.sort(Comparator.comparing(WfTaskInfoVO::getApplyTime));
        List<WfTaskInfoAuitVO> wfTaskInfoAuitVOList = new ArrayList<>();
        for (WfTaskInfoVO wfTaskInfoVO : wfTaskInfoVOList) {
            WfTaskInfoAuitVO wfTaskInfoAuitVO = new WfTaskInfoAuitVO();
            wfTaskInfoAuitVO.setTaskId(wfTaskInfoVO.getTaskId());
            wfTaskInfoAuitVO.setApplyUser(wfTaskInfoVO.getApplyUser());
            wfTaskInfoAuitVO.setBusinessName(wfTaskInfoVO.getBusinessName());
            wfTaskInfoAuitVO.setApplyTime(wfTaskInfoVO.getApplyTime());
            wfTaskInfoAuitVO.setBusinessKey(wfTaskInfoVO.getBusinessKey());
            wfTaskInfoAuitVO.setNodeName(wfTaskInfoVO.getNodeName());
            wfTaskInfoAuitVO.setStatus("1");
            wfTaskInfoAuitVOList.add(wfTaskInfoAuitVO);
        }
        return wfTaskInfoAuitVOList;
    }



    // 获取自己的申请(正在进行中的)
    @Override
    public List<WfTaskInfoVO> findTaskByStartUser(String account, String tableName){
        List<WfTaskInfoVO> wfTaskInfoVOList = new ArrayList<>();
        ProcessInstanceQuery processInstanceQuery = runtimeService.createProcessInstanceQuery().startedBy(account);
        // 如果tablename不为空,则只查询此业务的任务
        if (StringUtils.isEmpty(tableName)){
            List<WfRoleMap> wfRoleMapList = wfRoleMapService.list(new LambdaQueryWrapper<WfRoleMap>().eq(WfRoleMap::getBusinessTable, tableName));
            if (wfRoleMapList.size() != 0){
                processInstanceQuery.processDefinitionId(wfRoleMapList.get(0).getDefinitionId());
            }else {
                processInstanceQuery.processDefinitionId("0");// 如果不存在则填入一个不存在的流程定义
            }
        }
        List<ProcessInstance> processInstanceList = processInstanceQuery.list();
        for (int i = 0; i < processInstanceList.size(); i++) {
            ProcessInstance processInstance = processInstanceList.get(i);
            Task task = taskService.createTaskQuery().processInstanceId(processInstance.getProcessInstanceId()).singleResult();
            WfTaskInfoVO wfTaskInfoVO = new WfTaskInfoVO();
            wfTaskInfoVO.setBusinessName(processInstance.getName());
            wfTaskInfoVO.setBusinessKey(processInstance.getBusinessKey());
            wfTaskInfoVO.setApplyUser(UserUtil.getSysUserDTO(account));
            wfTaskInfoVO.setApplyTime(processInstance.getStartTime());
            wfTaskInfoVO.setNodeName(task.getName());
            wfTaskInfoVOList.add(wfTaskInfoVO);
        }
        // 按照申请时间排序
        wfTaskInfoVOList.sort(Comparator.comparing(WfTaskInfoVO::getApplyTime));
        return wfTaskInfoVOList;
    }

    // 查看自己已完成的任务
    @Override
    public List<WfTaskInfoVO> findTaskCompleted(String account, String tableName){
        // 查询已完成的任务
        HistoricTaskInstanceQuery historicTaskInstanceQuery = historyService
                .createHistoricTaskInstanceQuery()
                .finished()
                .taskAssignee(account);
        // 如果tablename不为空,则只查询此业务的任务
        if (StringUtils.isEmpty(tableName)){
            List<WfRoleMap> wfRoleMapList = wfRoleMapService.list(new LambdaQueryWrapper<WfRoleMap>().eq(WfRoleMap::getBusinessTable, tableName));
            if (wfRoleMapList.size() != 0){
                historicTaskInstanceQuery.processDefinitionId(wfRoleMapList.get(0).getDefinitionId());
            }else {
                historicTaskInstanceQuery.processDefinitionId("0");// 如果不存在则填入一个不存在的流程定义
            }
        }
        List<HistoricTaskInstance> historicTaskInstances = historicTaskInstanceQuery.list();
        // 设置返回信息
        List<WfTaskInfoVO> wfTaskInfoVOList = new ArrayList<>();
        for (HistoricTaskInstance task : historicTaskInstances) {
            ProcessInstance processInstance = runtimeService.createProcessInstanceQuery().processInstanceId(task.getProcessInstanceId()).singleResult();
            String startUserId = "";
            String name = "";
            Date startTime = new Date();
            if (processInstance == null){
                HistoricProcessInstance historicProcessInstance = historyService.createHistoricProcessInstanceQuery().processInstanceId(task.getProcessInstanceId()).singleResult();
                startUserId = historicProcessInstance.getStartUserId();
                startTime = historicProcessInstance.getStartTime();
                name = historicProcessInstance.getName();
            }else {
                startUserId = processInstance.getStartUserId();
                startTime = processInstance.getStartTime();
                name = processInstance.getName();
            }
            WfTaskInfoVO wfTaskInfoVO = new WfTaskInfoVO();
            wfTaskInfoVO.setTaskId(task.getId());
            wfTaskInfoVO.setApplyUser(UserUtil.getSysUserDTO(startUserId));// 获取启动实例的人
            wfTaskInfoVO.setApplyTime(startTime);
            wfTaskInfoVO.setBusinessName(name);
            wfTaskInfoVOList.add(wfTaskInfoVO);
        }
        // 按照申请时间排序:由近及远
        wfTaskInfoVOList.sort(Comparator.comparing(WfTaskInfoVO::getApplyTime).reversed());
        for(WfTaskInfoVO wfTaskInfoVO:wfTaskInfoVOList){
            if(wfTaskInfoVO.getBusinessName()==null){
                wfTaskInfoVO.setBusinessName("学生活动");
            }
        }
        return wfTaskInfoVOList;
    }


    @Override
    public String completeTaskByBusinessKey(String tableName, String businessKey, String remark, String assignee, String nextAssignee){
        ProcessInstance processInstance = this.getProcessInstanceByBusinessKey(tableName, businessKey);
        Task task = taskService.createTaskQuery().processInstanceId(processInstance.getProcessInstanceId()).singleResult();
        String result = this.completeTask(task.getId(), assignee, remark, nextAssignee);
        return result;
    }


    // 完成任务,并指定下一审核人(下一级审核人可省略)
    public String completeTask(String taskId, String assignee, String remark, String nextAssignee) {
        Task task = taskService.createTaskQuery().taskId(taskId).singleResult();
        ProcessInstance processInstance = runtimeService.createProcessInstanceQuery().processInstanceId(task.getProcessInstanceId()).singleResult();
        // 候选人认领任务
        taskService.claim(taskId, assignee);
        // 添加评论到任务APPROVED通过审核
        taskService.addComment(taskId, processInstance.getProcessInstanceId(), "APPROVED");
        // 完成任务
        taskService.complete(taskId);
        // 添加备注(暂未实现)

        // 判断流程是否结束,如果没结束进行接下来的操作
        ProcessInstance processInstance1 = runtimeService.createProcessInstanceQuery().processInstanceId(processInstance.getProcessInstanceId()).singleResult();
        if (processInstance1 == null){
            return WorkFlowENUM.GLOBAL_SUCCESS.getAbbreviation();
        }
        // 如果nextAssignee不空,则指定下一级审核人
        if (!StringUtils.isEmpty(nextAssignee)){
            Task task1 = taskService.createTaskQuery().processInstanceId(processInstance.getProcessInstanceId()).singleResult();
            taskService.setAssignee(task1.getId(), nextAssignee);
        }
        return WorkFlowENUM.GLOBAL_PENDING.getAbbreviation();
    }

    @Override
    public String refuseTaskByBusinessKey(String tableName, String businessKey, String reason, String assignee){
        ProcessInstance processInstance = this.getProcessInstanceByBusinessKey(tableName, businessKey);
        Task task = taskService.createTaskQuery().processInstanceId(processInstance.getProcessInstanceId()).singleResult();
        String result = this.refuseTask(task.getId(), reason, assignee);
        return result;
    }

    // 退回任务并备注理由
    public String refuseTask(String taskId, String reason, String assignee) {
        Task task = taskService.createTaskQuery().taskId(taskId).singleResult();
        // 设置审批结果为不通过
        runtimeService.setVariable(task.getProcessInstanceId(), "status", WorkFlowENUM.GLOBAL_REFUSE.getAbbreviation());
        runtimeService.setVariable(task.getProcessInstanceId(), "reason", reason);
        // 添加评论到任务REJECTED不通过
        taskService.addComment(taskId, task.getProcessInstanceId(), WorkFlowENUM.NODE_REFUSE.getAbbreviation());
        // 拾取任务
        taskService.setAssignee(taskId, assignee);
        // 结束流程
        this.endTask(task.getId());
        return WorkFlowENUM.GLOBAL_REFUSE.getAbbreviation();
    }

    @Override
    public String cancelProcessByBusinessKey(String tableName, String businessKey){
        ProcessInstance processInstance = this.getProcessInstanceByBusinessKey(tableName, businessKey);
        Task task = taskService.createTaskQuery().processInstanceId(processInstance.getProcessInstanceId()).singleResult();
        String result = this.cancelProcess(task.getId());
        return result;
    }

    // 撤销流程
    public String cancelProcess(String taskId){
        Task task = taskService.createTaskQuery().taskId(taskId).singleResult();
        ProcessInstance processInstance = runtimeService.createProcessInstanceQuery().processInstanceId(task.getProcessInstanceId()).singleResult();
        // 设置这个任务的审核人是发起人,然后结束流程
        task.setAssignee(processInstance.getStartUserId());
        runtimeService.setVariable(task.getProcessInstanceId(), "status", WorkFlowENUM.GLOBAL_CANCEL.getAbbreviation());
        this.endTask(taskId);
        return WorkFlowENUM.GLOBAL_CANCEL.getAbbreviation();
    }


    // 预测正在执行中的任务流程
    @Override
    public WfProcessPreviewVO previewProcess(String tableName, String businessKey) {
        Map<String, String> processDefinition = this.getProcessIdByTableName(tableName);
        WfProcessPreviewVO wfProcessPreviewVo = new WfProcessPreviewVO();// 初始化返回信息
        ProcessInstance processInstance = runtimeService.createProcessInstanceQuery().processInstanceBusinessKey(businessKey).processDefinitionId(processDefinition.get("definitionId")).singleResult();// 获取流程实例
        if (processInstance != null){
            // processInstance 不为空说明流程还没结束
            String processInstanceId = processInstance.getProcessInstanceId();
            Task task = taskService.createTaskQuery().processInstanceId(processInstanceId).singleResult();// 获取当前任务
            BpmnModel model = repositoryService.getBpmnModel(processInstance.getProcessDefinitionId());// 获取流程模型
            // 这里将分为三种情况:审核过的结点、审核中的结点、未审核的结点
            // 1. 获取整个流程
            List<WfApproveNodeBO> wfApproveNodeBOList = previewWorkFlowProcessUtil.getCurrentPreviewNodes(processInstance.getProcessDefinitionId(), processInstance.getProcessInstanceId());// 获取整个流程图
            // 2. 设置结点信息
            for (int i = 0; i < wfApproveNodeBOList.size(); i++) {
                WfApproveNodeBO wfApproveNodeBO = wfApproveNodeBOList.get(i);
                // 如果到了正在审核的结点
                if (task.getName().equals(wfApproveNodeBO.getNodeName())){
                    // 审核状态之前存储到comment里面了
                    wfApproveNodeBO.setStatus(WorkFlowENUM.NODE_APPROVING.getAbbreviation());
                    wfApproveNodeBO.setAssignee(UserUtil.getSysUserDTO(task.getAssignee()));
                    break;
                }
                // 否则就查找历史任务信息
                HistoricTaskInstance historicTaskInstance = historyService.createHistoricTaskInstanceQuery().processInstanceId(processInstanceId).taskDefinitionKey(wfApproveNodeBO.getTaskDefinitionKey()).singleResult();
                // 审核状态之前存储到comment里面了
                wfApproveNodeBO.setStatus(taskService.getTaskComments(historicTaskInstance.getId()).get(0).getFullMessage());
                wfApproveNodeBO.setAssignee(UserUtil.getSysUserDTO(historicTaskInstance.getAssignee()));
                // 存储审核时间
                wfApproveNodeBO.setReviewTime(historicTaskInstance.getEndTime());
            }
            Map<String, Object> map = processInstance.getProcessVariables();
            wfProcessPreviewVo.setWfApproveNodeBOList(wfApproveNodeBOList);
            wfProcessPreviewVo.setApplyTime(processInstance.getStartTime());
            wfProcessPreviewVo.setApply(UserUtil.getSysUserDTO(processInstance.getStartUserId()));
            Object status = map.get("status");
            if (status != null && StringUtils.isEmpty(status.toString()) && WorkFlowENUM.GLOBAL_REFUSE.getAbbreviation().equals(status.toString())){
                // 如果是已拒绝状态
                wfProcessPreviewVo.setStatus(status.toString());
            }else if (status == null || StringUtils.isEmpty(status.toString()) || !WorkFlowENUM.GLOBAL_REFUSE.getAbbreviation().equals(status.toString())){
                // 如果status是空或者不是空但是没有被拒绝,则是“审核中”(因为已结束的流程使用runtimeService查不出来)
                wfProcessPreviewVo.setStatus(WorkFlowENUM.GLOBAL_PENDING.getAbbreviation());
            }
            if (map.get("reason") != null){
                wfProcessPreviewVo.setComment(map.get("reason").toString());
            }
        }else {
            HistoricProcessInstance historicProcessInstance = historyService.createHistoricProcessInstanceQuery().processDefinitionId(processDefinition.get("definitionId")).processInstanceBusinessKey(businessKey).singleResult();
            String historicProcessInstanceId = historicProcessInstance.getId();
            // 这里分为三种情况:审核通过的结点、拒绝的结点、未审核的结点
            // 1. 获取整个流程
            List<WfApproveNodeBO> wfApproveNodeBOList = previewWorkFlowProcessUtil.getCurrentPreviewNodes(historicProcessInstance.getProcessDefinitionId(), historicProcessInstanceId);// 获取整个流程图
//            // 预设状态为已通过
//            wfProcessPreviewVo.setStatus(WorkFlowENUM.GLOBAL_SUCCESS.getAbbreviation());
            // 2. 设置结点信息
            for (int i = 0; i < wfApproveNodeBOList.size(); i++) {
                WfApproveNodeBO wfApproveNodeBO = wfApproveNodeBOList.get(i);
                // 查找历史任务信息
                HistoricTaskInstance historicTaskInstance = historyService.createHistoricTaskInstanceQuery().processInstanceId(historicProcessInstanceId).taskDefinitionKey(wfApproveNodeBO.getTaskDefinitionKey()).singleResult();
                // 如果此任务已经完成
                if (historicTaskInstance != null){
                    // 审核状态之前存储到comment里面了
                    wfApproveNodeBO.setStatus(taskService.getTaskComments(historicTaskInstance.getId()).get(0).getFullMessage());
                    wfApproveNodeBO.setAssignee(UserUtil.getSysUserDTO(historicTaskInstance.getAssignee()));
                    // 存储审核时间
                    wfApproveNodeBO.setReviewTime(historicTaskInstance.getEndTime());
                }
                // 如果任务尚未完成说明前面有节点是拒绝状态
                else {
                    wfApproveNodeBO.setStatus(WorkFlowENUM.NODE_ENDING.getAbbreviation());
                    wfProcessPreviewVo.setStatus(WorkFlowENUM.GLOBAL_REFUSE.getAbbreviation());
                }
            }
            wfProcessPreviewVo.setWfApproveNodeBOList(wfApproveNodeBOList);
            wfProcessPreviewVo.setApplyTime(historicProcessInstance.getStartTime());
            wfProcessPreviewVo.setApply(UserUtil.getSysUserDTO(historicProcessInstance.getStartUserId()));
            if (StringUtils.isEmpty(wfProcessPreviewVo.getStatus())){
                Map<String, Object> map = historicProcessInstance.getProcessVariables();
                Object status = map.get("status");
                if (status != null && StringUtils.isEmpty(status.toString()) && WorkFlowENUM.GLOBAL_REFUSE.getAbbreviation().equals(status.toString())){
                    // 如果是已拒绝状态
                    wfProcessPreviewVo.setStatus(status.toString());
                    if (map.get("reason") != null){
                        wfProcessPreviewVo.setComment(map.get("reason").toString());
                    }
                }else if (status == null || StringUtils.isEmpty(status.toString()) || !WorkFlowENUM.GLOBAL_REFUSE.getAbbreviation().equals(status.toString())){
                    // 如果status是空或者不是空但是没有被拒绝,则是“已通过”(因为historic查出来的都是结束的,要么通过要么拒绝)
                    wfProcessPreviewVo.setStatus(WorkFlowENUM.GLOBAL_SUCCESS.getAbbreviation());
                }
            }
        }


        return wfProcessPreviewVo;
    }

    public Map<String, String> getProcessIdByTableName(String tableName){
        Map<String, String> map = new HashMap<>();
        // 获取此流程所需哪些变量以及对应的业务id
        List<WfRoleMap> wfRoleMapList = wfRoleMapMapper.selectList(new LambdaQueryWrapper<WfRoleMap>().eq(WfRoleMap::getBusinessTable, tableName));
        if (wfRoleMapList.size() != 0) {
            map.put("deploymentId", wfRoleMapList.get(0).getDeploymentId());
            map.put("definitionId", wfRoleMapList.get(0).getDefinitionId());
        }
        return map;
    }


    /**
     * 结束任务,直达结束节点
     * @param taskId    当前任务ID
     */
    public void endTask(String taskId) {
        //  当前任务
        Task task = taskService.createTaskQuery().taskId(taskId).singleResult();

        BpmnModel bpmnModel = repositoryService.getBpmnModel(task.getProcessDefinitionId());
        List endEventList = bpmnModel.getMainProcess().findFlowElementsOfType(EndEvent.class);
        FlowNode endFlowNode = (FlowNode) endEventList.get(0);
        FlowNode currentFlowNode = (FlowNode) bpmnModel.getMainProcess().getFlowElement(task.getTaskDefinitionKey());

        //  临时保存当前活动的原始方向
        List originalSequenceFlowList = new ArrayList<>();
        originalSequenceFlowList.addAll(currentFlowNode.getOutgoingFlows());
        //  清理活动方向
        currentFlowNode.getOutgoingFlows().clear();

        //  建立新方向
        SequenceFlow newSequenceFlow = new SequenceFlow();
        newSequenceFlow.setId("newSequenceFlowId");
        newSequenceFlow.setSourceFlowElement(currentFlowNode);
        newSequenceFlow.setTargetFlowElement(endFlowNode);
        List newSequenceFlowList = new ArrayList<>();
        newSequenceFlowList.add(newSequenceFlow);
        //  当前节点指向新的方向
        currentFlowNode.setOutgoingFlows(newSequenceFlowList);

        //  完成当前任务
        taskService.complete(task.getId());

        //  可以不用恢复原始方向,不影响其它的流程
        currentFlowNode.setOutgoingFlows(originalSequenceFlowList);
    }


    /**
     * 根据业务id和业务表获取运行中的流程实例
     * @param tableName
     * @param businessKey
     * @return
     */
    public ProcessInstance getProcessInstanceByBusinessKey(String tableName, String businessKey){
        Map<String, String> processDefinition = this.getProcessIdByTableName(tableName);
        ProcessInstance processInstance = runtimeService.createProcessInstanceQuery().processDefinitionId(processDefinition.get("definitionId")).processInstanceBusinessKey(businessKey).singleResult();
        return processInstance;
    }
}

预测流程信息

import com.hebut.publicservice.common.utils.StringUtils;
import com.hebut.publicservice.common.utils.UserUtil;
import com.hebut.publicservice.entity.BO.WfApproveNodeBO;
import com.hebut.publicservice.entity.DTO.SysUserDTO;
import de.odysseus.el.ExpressionFactoryImpl;
import de.odysseus.el.util.SimpleContext;
import org.activiti.bpmn.model.*;
import org.activiti.engine.HistoryService;
import org.activiti.engine.RepositoryService;
import org.activiti.engine.RuntimeService;
import org.activiti.engine.TaskService;
import org.activiti.engine.history.HistoricVariableInstance;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import javax.el.ExpressionFactory;
import javax.el.ValueExpression;
import java.util.*;

/**
 * @Description:
 * @author: sheshe
 * @since:
 * @history: created by sheshe
 */
@Component
public class PreviewWorkFlowProcessUtil {
    @Autowired
    private RuntimeService runtimeService;
    @Autowired
    private TaskService taskService;
    @Autowired
    private RepositoryService repositoryService;

    @Autowired
    private HistoryService historyService;
    // 获取当前节点以后得流程(需要注意的是,这里获取的是流程定义模板的流程信息而不是流程实例的流程实例信息)
    public List<WfApproveNodeBO> getCurrentPreviewNodes(String processDefinitionId, String processInstanceId) {
        /**
         * 获取代办任务信息
         */
        //获取流程模型
        BpmnModel model = repositoryService.getBpmnModel(processDefinitionId);
        List<FlowElement> flowElements = new ArrayList<>(model.getMainProcess().getFlowElements());
        //获取第一个审核节点
        FlowElement flowElement = model.getFlowElement(flowElements.get(1).getId());
        //获取运行时流程变量
        Map<String, Object> params = new HashMap<>();
        if (runtimeService.createProcessInstanceQuery().processInstanceId(processInstanceId).singleResult() == null){
            List<HistoricVariableInstance> variableInstances = historyService.createHistoricVariableInstanceQuery().processInstanceId(processInstanceId).list();
            for (HistoricVariableInstance variableInstance : variableInstances) {
                params.put(variableInstance.getVariableName(), variableInstance.getValue());
            }
        }else {
            params = runtimeService.getVariables(processInstanceId);
        }
        //保存访问过的节点,避免死循环
        Set<String> visitedElements = new HashSet<>();
        //递归获取所有预测节点
        List<WfApproveNodeBO> approveNodes = visiteElement(flowElement, params, visitedElements);
        return approveNodes;
    }

    /**
     * 递归获取预测节点列表
     *
     * @param flowElement
     * @param params
     * @param visitedElements
     * @return
     */
    private List<WfApproveNodeBO> visiteElement(FlowElement flowElement, Map<String, Object> params, Set<String> visitedElements) {
        String id = flowElement.getId();
        //如果之前访问过的节点就不再访问
        if (visitedElements.contains(id)) {
            return Collections.EMPTY_LIST;
        }
        visitedElements.add(id);
        List<WfApproveNodeBO> nodes = new ArrayList<>();
        //UserTask是实际的审批节点,如果是UserTask就可以加入到预测的节点中
        if (flowElement instanceof UserTask) {
            UserTask item = (UserTask) flowElement;
            List<SysUserDTO> sysUserDTOList = new ArrayList<>();
            // 遍历键的集合,逐个取出对应的值
            for (String key : params.keySet()) {
                if (item.getCandidateUsers().get(0).equals("${" + key + "}")){
                    List<String> accounts = StringUtils.splitList(params.get(key).toString());
                    for (int i = 0; i < accounts.size(); i++) {
                        sysUserDTOList.add(UserUtil.getSysUserDTO(accounts.get(i)));
                    }
                    break;
                }
            }
            SysUserDTO assignee = new SysUserDTO();
            if (item.getAssignee() != null){
                assignee = UserUtil.getSysUserDTO(item.getAssignee());
            }
            nodes.add(new WfApproveNodeBO(item.getName(), assignee, sysUserDTOList, "PENDING", item.getId()));
        }

        //获取所有的出口,也就是流程模型中的连线
        List<SequenceFlow> sequenceFlows = this.getElementSequenceFlow(flowElement);
        if (sequenceFlows == null || sequenceFlows.isEmpty()) {
            return nodes;
        }
        FlowElement nextElement = null;
        if (sequenceFlows.size() == 1 && sequenceFlows.get(0).getConditionExpression() == null) {
            /**
             * 如果只有一条连线并且没有设置流转条件,直接获取连线目标节点作为下一节点
             */
            nextElement = sequenceFlows.get(0).getTargetFlowElement();
        } else {
            for (SequenceFlow seq : sequenceFlows) {
                if (seq.getConditionExpression() == null) {
                    /**
                     * 如果没有条件符合,那么就取获取到的第一条条件为空的节点
                     */
                    if (nextElement == null) {
                        nextElement = seq.getTargetFlowElement();
                    }
                } else {
                    /**
                     * 计算条件
                     */
                    boolean value = this.verificationExpression(seq.getConditionExpression(), params);
                    if (value) {
                        nextElement = seq.getTargetFlowElement();
                        break;
                    }
                }
            }
        }
        nodes.addAll(this.visiteElement(nextElement, params, visitedElements));
        return nodes;
    }

    /**
     * 获取流程连线
     *
     * @param flowElement
     * @return
     */
    private List<SequenceFlow> getElementSequenceFlow(FlowElement flowElement) {
        if (flowElement instanceof FlowNode) {
            return ((FlowNode) flowElement).getOutgoingFlows();
        }
        return Collections.EMPTY_LIST;
    }

    /**
     * 执行表达式计算
     * @param expression
     * @param variableMap
     * @param returnType
     * @param <T>
     * @return
     */
    private <T> T executeExpression(String expression, Map<String, Object> variableMap, Class<T> returnType) {
        if (expression == null) {
            return null;
        }
        ExpressionFactory factory = new ExpressionFactoryImpl();
        SimpleContext context = new SimpleContext();
        for (String k : variableMap.keySet()) {
            context.setVariable(k, factory.createValueExpression(variableMap.get(k), Object.class));
        }
        ValueExpression valueExpression = factory.createValueExpression(context, expression, returnType);
        return (T) valueExpression.getValue(context);

    }

    /**
     * 验证表达式结果 true/false
     * @param expression
     * @param variableMap
     * @return
     */
    private Boolean verificationExpression(String expression, Map<String, Object> variableMap) {
        Boolean value = this.executeExpression(expression, variableMap, Boolean.class);
        return value == null ? false : value;
    }

}

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

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

相关文章

MySQL 数据库 Navicat Premium 16.01 安装教程

MySQL 数据库 Navicat Premium 16.01 安装教程 目录 MySQL 数据库 Navicat Premium 16.01 安装教程前言安装步骤同意协议选择安装目录桌面快捷方式安装正在安装安装完成 步骤获取 前言 MySQL数据库管理用Navicat更加方便&#xff0c;可视化效果更好&#xff0c;今天给大家带来…

八股文之JVM

目录 1.JVM内存划分 2.JVM类加载过程 3.JVM垃圾回收机制GC 3.1.判断谁是垃圾 3.2.如何释放对应的内存 1.JVM内存划分 在一个Java程序运行起来之后&#xff0c;jvm就会从操作系统中申请一块内存&#xff0c;然后就会将该内存划分成多个部分&#xff0c;用于不同的用途。 …

python-03

使用File操作文件 open(file, moder, buffering-1,encodingNone, errorsNone, newlineNone, closefdTrue, openerNone) file&#xff1a;要打开的文件 mode&#xff1a;文件打开模式。默认访问模式是读&#xff08;r&#xff09; buffering&#xff1a;缓冲区大小 encodin…

淘宝商品评论API接口测试实例(获取淘宝商品评论,翻页展示、支持并发)

item_review-获得淘宝商品评论 测试页 公共参数 名称类型必须描述keyString是调用key&#xff08;必须以GET方式拼接在URL中&#xff09;secretString是调用密钥api_nameString是API接口名称&#xff08;包括在请求地址中&#xff09;[item_search,item_get,item_search_shop…

学生党蓝牙耳机性价比排行榜,好评榜TOP5机型推荐

随着科技的飞速发展&#xff0c;蓝牙耳机已经成为我们日常生活中不可或缺的一部分&#xff0c;无论是运动健身、通勤路上还是休闲娱乐&#xff0c;它都为我们带来了极大的便利&#xff0c;然而&#xff0c;市面上的蓝牙耳机种类繁多&#xff0c;价格参差不齐&#xff0c;如何选…

pg——psql命令行交互式客户端工具

1、启动数据库 ./pg_ctl -D /usr/local/pgsql/data/ -l /usr/local/pgsql/log 2、登录数据库 psql template1 3、查看所有数据库 \l 4、创建数据库 create database testdb; 5、连接某数据库 \c 数据库 6、查看数据下的表 \d 7、 查看数据库下的所有schema \dn 8、查看表的结构…

业务表对应主键为varChar类型且无自增策略的新增与修改问题

业务表对应主键为varChar类型且无自增策略的新增与修改问题 上周五领到一个需求&#xff0c;在开发过程中遇到一点小插曲&#xff0c;在此记录下&#xff0c;话不多说&#xff0c;直接看表结构&#xff1a; 这里机构id为主键且无自增策略&#xff0c;也就是说机构id这个字段是…

PHP安装配置

文章目录 1.下载PHP2.配置环境变量3.Apache安装配置 1.下载PHP PHP即“超文本预处理器”&#xff0c;是一种通用开源脚本语言。PHP是在服务器端执行的脚本语言&#xff0c;与C语言类似&#xff0c;是常用的网站编程语言。PHP独特的语法混合了C、Java、Perl以及 PHP 自创的语法…

Samba 服务器的搭建以及windows server 2008客户端的使用实验报告

一、 实验目的 通过 Samba 服务器的搭建&#xff0c;基本了解搭建服务器的基本步骤&#xff0c;理解 Samba 服务器的实现文件共享的功能&#xff0c;如何配置 Samba服务器配置文件等。 二、 实验环境 准备一台安装 centOS7系统的 Linux 虚拟机作为 Samba 服务器 server,准备…

海外仓系统有哪些?主流海外仓系统类型、优缺点,不同海外仓如何选择

作为海外仓的经营者&#xff0c;不管海外仓大小&#xff0c;你都应该知道海外仓系统对提升仓库管理效率有多重要。 不过现在市场上的海外仓系统确实种类太多了&#xff0c;想选到一个适合自己海外仓&#xff0c;性价比又比较高的wms海外仓系统也不是一件容易的事情。 本文会详…

GD32 MCU启动后如何运行到main函数

GD32 MCU启动后如何运行到main函数入口&#xff1f;你是否也有这样的疑虑。在执行到main函数之前MCU干了哪些事情呢&#xff1f;下面为大家解答。 MCMCU上电后&#xff0c;首先会根据BOOT0和BOOT1的状态判断从主Flash/SRAM/ISP中启动&#xff0c;如果BOOT0为低电平&#xff0c…

windows反弹shell的方法

什么是正向shell和反向shell 首先说&#xff0c;正向shell是控制端主动连接被控制端&#xff0c;通过目标主机开放一个监听端口等待其他主机访问&#xff0c;从而获得对目标主机的shell访问&#xff0c;优点是控制端可以整个控制目标主机&#xff0c;但缺点会受到防火墙的连&a…

多标签识别:JoyTag模型的图像标注革命【开源】

公共视觉模型通常会对其训练数据集进行严格过滤&#xff0c;这限制了这些基础模型在广泛概念上的表现&#xff0c;进而限制了表达自由、包容性和多样性。JoyTag通过结合Danbooru 2021数据集和一组手动标记的图像&#xff0c;努力提高模型对不同类型图像的泛化能力。 JoyTag项目…

简易版 | 代码生成器(包含插件)

一、代码生成器 先导入依赖 <!-- Mybatis-Plus --> <dependency><groupId>com.baomidou</groupId><artifactId>mybatis-plus-boot-starter</artifactId><version>3.5.6</version> </dependency><!-- 代码生成器 --…

jvm必知必会-类的生命周期图文详解

类的生命周期描述了一个从加载、使用到卸载的过程; 而其中的 连接 部分又分为一下三个阶段: 验证准备解析6.1 加载阶段 Loading阶段第一步是 类加载器 会根据类全限定名通过不同的渠道以二进制流的方式获取字节码信息,程序员可以使用Java代码扩展不同的渠道。 比如通过 …

超强生图模型,抢先体验!

文生图模型Stable Diffusion 3 Medium开源啦&#xff01;优刻得GPU云主机目前已上线该模型镜像&#xff0c;开箱即用&#xff0c;即刻体验&#xff01;具体操作见文末。 近年来&#xff0c;生成式人工智能的快速发展正在改变我们的生活。作为这一领域的前沿技术之一&#xff0…

一个软件是如何开发出来的呢?

一、前言 如今&#xff0c;AI大爆发的时代&#xff0c;作为一名IT从业者&#xff0c;你是否也想尝试开发一套自己的系统&#xff0c;实现那些看似有可能实现的天马行空的想法&#xff0c;变成一个优秀甚至伟大的产品&#xff0c;甚至带来某个行业的革新&#xff0c;那作为一名…

rtthread stm32h743的使用(九)RT_WEAK报错

rtthread stm32h743的使用&#xff08;九&#xff09;RT_WEAK报错 我们要在rtthread studio 开发环境中建立stm32h743xih6芯片的工程。我们使用一块stm32h743及fpga的核心板完成相关实验&#xff0c;核心板如图&#xff1a; 我们新建工程&#xff0c;参考前面rtthread stm32h…

springboot 腾讯地图接口验签 java

1. 原因 需求需要通过小程序定位拿到用户所在行政区信息,但是小程序定位只能拿到经纬度信息,所以需要调用腾讯地图的逆地址解析(我认为:微信是腾讯的,那么使用腾讯地图的逆地址解析经度应该不会损失太多)如果WebServiceAPI Key配置中签名校验,那么调用接口就需要进行验签 2. W…

YOLOv8 + SAM实现自动标注分割数据集【附完整源码+步骤详解】

《博主简介》 小伙伴们好&#xff0c;我是阿旭。专注于人工智能、AIGC、python、计算机视觉相关分享研究。 ✌更多学习资源&#xff0c;可关注公-仲-hao:【阿旭算法与机器学习】&#xff0c;共同学习交流~ &#x1f44d;感谢小伙伴们点赞、关注&#xff01; 《------往期经典推…