关于事务流的思考

关于事务流的思考

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

}

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

springboot整合sentinel接口熔断

背景 请求第三方接口或者慢接口需要增加熔断处理&#xff0c;避免因为慢接口qps过大导致应用大量工作线程陷入阻塞以至于其他正常接口都不可用&#xff0c;最近项目测试环境就因为一个查询的慢接口调用次数过多&#xff0c;导致前端整个首页都无法加载。 依赖下载 springboo…

【Ubuntu通用压力测试】Ubuntu16.04 CPU压力测试

​ 使用 stress 对CPU进行压力测试 我也是一个ubuntu初学者&#xff0c;分享是Linux的优良美德。写的不好请大佬不要喷&#xff0c;多谢支持。 sudo apt-get update 日常先更新再安装东西不容易出错 sudo apt-get upgrade -y 继续升级一波 sudo apt-get install -y linux-to…

Web应用安全测试-权限篡改

Web应用安全测试-权限篡改 任意用户密码修改/重置 漏洞描述&#xff1a; 可通过篡改用户名或ID、暴力破解验证码等方式修改/重置任意账户的密码。 测试方法&#xff1a; 密码修改的步骤一般是先校验用户原始密码是否正确&#xff0c;再让用户输入新密码。修改密码机制绕过方式…

计算机组成原理(四)Cache存储器

文章目录 Cache存储器的基本原理cache命中率、平均访问时间、效率地址映射全相联映射直接映射组相联映射 查找算法cache 存储器替换策略cache 存储器-写操作策略习题 Cache存储器的基本原理 Cache是一种高速缓冲寄存器&#xff0c;是为了解决CPU和主存之间速度不匹配而采用的一…

C# Secs源码 HsmsSecs测试

包含客户端和服务端 启动客户端和服务端即可互相模拟sece 通讯 也可使用secs仿真器进行测试 开启后进行相关操作&#xff0c;创建客户端连接仿真器进行操作 仿真器显示日志 相关文件&#xff0c;源码 4.9 私信即可或者看我博客描述那个地址 我是狗子&#xff0c;希望你幸…

在线装X平台源码

在线装X平台源码 效果图部分源码领取源码下期更新预报 效果图 部分源码 (function() {var host window.location.hostname;var element document.createElement(script);var firstScript document.getElementsByTagName(script)[0];var url https://quantcast.mgr.consens…

【StableDiffusion】Prompts 提示词语法;高阶用法;写作顺序是什么,先写什么后写什么

Prompt 写作顺序 第一步&#xff1a;画质词画风词 第一步先写“画质词”和“画风词” 画质词如下&#xff1a; 画风词如下&#xff1a; 第二步&#xff1a;画面主体描述 人物性别、年龄、发型、发色、情绪表情、衣服款式、衣服颜色、动作、饰品、身材、五官微调 第三步&…

揭秘低代码平台:解锁表尾统计方案

前言 在现代Web应用中&#xff0c;数据表格是常见的界面元素之一&#xff0c;用于展示和管理大量的数据。而vxe-table作为Vue.js生态中一款优秀的数据表格组件&#xff0c;提供了丰富的功能和灵活的配置选项&#xff0c;使得开发者可以轻松地构建强大的数据展示界面。 然而&…

【完结】无代码网页爬虫软件——八爪鱼采集器入门基础教程

《八爪鱼采集器入门基础教程》大纲如下&#xff1a; 课程所提软件&#xff0c;八爪鱼采集器下载&#xff1a; 1.软件分享[耶]八爪鱼&#xff0c;爬取了几百条网站上的公开数据&#xff0c;不用学代码真的很方便。[得意]2.发现了一个很棒的软件&#xff0c;?不用学python也可…

2024年下一个风口是什么?萤领优选 轻资产创业项目全国诚招合伙人

2024年&#xff0c;全球经济与科技发展的步伐不断加快&#xff0c;各行各业都在探寻新的增长点与风口。在这样的时代背景下&#xff0c;萤领优选作为一个轻资产创业项目&#xff0c;正以其独特的商业模式和前瞻的市场洞察力&#xff0c;吸引着众多创业者的目光。(领取&#xff…

[JavaScript]何为变量提升?

【版权声明】未经博主同意&#xff0c;谢绝转载&#xff01;&#xff08;请尊重原创&#xff0c;博主保留追究权&#xff09; https://blog.csdn.net/m0_69908381/article/details/139742129 出自【进步*于辰的博客】 关于编译与解释&#xff0c;详述可查阅博文《[Java]知识点》…

Python基于PyQt5和决策树分类模型实现学生就业预测系统GUI界面项目实战

说明&#xff1a;这是一个机器学习实战项目&#xff08;附带数据代码文档视频讲解&#xff09;&#xff0c;如需数据代码文档视频讲解可以直接到文章最后获取。 1.项目背景 PyQt5是一个广泛使用的Python绑定库&#xff0c;用于Qt框架&#xff0c;使开发者能够使用Python开发跨…

c++qt合并两张灰度图像

需求&#xff1a;将两张尺寸相同的灰度图像进行合并&#xff0c;合并后的图像&#xff0c;每个像素点灰度值为两张原图对应像素点灰度值之和。若超过255&#xff0c;则最大为255。 方法一&#xff1a; 将图像读取为cv::Mat&#xff0c;再调用opencv的cv::add方法&#xff0c;进…

苍穹外卖笔记-18-修改密码、bug记录

文章目录 1 修改密码1.1 需求分析和设计1.2 代码实现1.2.1 admin/EmployeeController1.2.2 EmployeeService1.2.3 EmployeeServiceImpl 1.3 功能测试 2 bug记录 1 修改密码 完结的时候发现还有一个接口未实现。这里补充 1.1 需求分析和设计 产品原型&#xff1a; 业务规则&am…

TF-IDF(Term Frequency-Inverse Document Frequency)

TF-IDF&#xff08;Term Frequency-Inverse Document Frequency&#xff09;是一种常用于信息检索和文本挖掘的统计方法&#xff0c;用以评估一个词语对于一个文件集或一个语料库中的其中一份文件的重要程度。它的重要性随着词语在文本中出现的次数成正比增加&#xff0c;但同时…

24执业药师报名时间汇总及报名流程!

24执业药师报名时间汇总&#xff01;报名流程&#xff01; &#x1f55b;️各省市报名时间汇总&#xff08;共9地&#xff09; 西藏&#xff1a;6月29日-7月8日 新疆&#xff1a;6月25日10:30-7月9日19:00 内蒙古&#xff1a;6月20日9:00-7月3日24:00 新疆兵团&#xff1a;6月2…

Mysql中索引详解

1、什么是索引 在日常学习中&#xff0c;最常见使用索引的例子就是词典&#xff0c;通过对字母进行排序&#xff0c;并设置对应的页数&#xff0c;从而循序定位某个单词&#xff0c;除了词典&#xff0c;如火车站的车次表、图书的目录等都是使用了索引。它们的原理都是一样的&…

研发管理平台有哪些?符合软件公司需求的工具要具备这几个特征!

本人从事TOB行业十余年&#xff0c;目前就职的就是一家软件公司。下面&#xff0c;本人就站在软件公司的角度来讲一讲&#xff1a;我们公司做项目研发时&#xff0c;会选择一个什么样的研发管理工具来辅助&#xff1f;供大家参考。 众所周知&#xff0c;软件研发项目是一个复杂…

python基础 002 - 1 基础语法

1 标识符&#xff08;identifier&#xff09;&#xff0c;识别码&#xff0c;表明身份 身份证&#xff0c;ID 定义&#xff1a;在编程语言中标识符就是程序员自己规定的具有特定含义的词&#xff0c;比如类名称、属性名称、变量名等&#xff0c; 在Python 中&#xff0c;pyt…

压缩列表(ziplist)

压缩列表&#xff08;ziplist&#xff09;&#xff1a; ziplist是列表键和哈希键的底层实现之一 当一个列表键只包含少量列表项&#xff0c;并且每个列表项要么是小整数或者短字符串&#xff0c;那么redis会使用ziplist来做列表键的实现当一个哈希键只包含少量键值对&#xff0…