springboot集成activiti及使用方法(排除spring security)

一、activiti依赖及工具

1、springboot依赖

注意排除mybatis和el表达式依赖

   <dependency>
        <groupId>org.activiti</groupId>
        <artifactId>activiti-spring-boot-starter</artifactId>
        <version>7.0.0.Beta2</version>
        <exclusions>
                <exclusion>
                    <artifactId>mybatis</artifactId>
                    <groupId>org.mybatis</groupId>
                </exclusion>
                <exclusion>
                    <artifactId>el-api</artifactId>
                    <groupId>javax.el</groupId>
                </exclusion>
            </exclusions>
   </dependency>

2、下载idea相关插件

画流程图相关插件
在这里插入图片描述

二、配置activiti

1、参数相关介绍如下

spring:
  activiti:
    #自动更新数据库结构
    #1.flase:默认值。activiti在启动时,对比数据库表中保存的版本,如果没有表或者版本不匹配,将抛出异常
    #2.true: activiti会对数据库中所有表进行更新操作。如果表不存在,则自动创建
    #3.create_drop: 在activiti启动时创建表,在关闭时删除表(必须手动关闭引擎,才能删除表)
    #4.drop-create: 在activiti启动时删除原来的旧表,然后在创建新表(不需要手动关闭引擎)
    database-schema-update: true
    #activiti7默认不生成历史信息表,开启历史表
    db-history-used: true
    #记录历史等级 可配置的历史级别有none, activity, audit, full
    #none:不保存任何的历史数据,因此,在流程执行过程中,这是最高效的。
    #activity:级别高于none,保存流程实例与流程行为,其他数据不保存。
    #audit:除activity级别会保存的数据外,还会保存全部的流程任务及其属性。audit为history的默认值。
    #full:保存历史数据的最高级别,除了会保存audit级别的数据外,还会保存其他全部流程相关的细节数据,包括一些流程参数等。
    history-level: full
    # =============================
    #自动检查、部署流程定义文件 启动时自动部署定义的流程
    check-process-definitions: true
    # asyncExecutorActivate是指activiti在流程引擎启动就激活AsyncExecutor,异步:true-开启(默认)、false-关闭
    async-executor-activate: true
  #流程定义文件存放目录,要具体到某个目录
  #      process-definition-location-prefix: classpath:/processes/holliday/
  #process-definition-location-suffixes: #流程文件格式
  #  - **.bpmn20.xml
  #  - **.bpmn

2、创建流程文件

创建新流程文件默认在此目录下,当然也可以在yml文件中通过配置process-definition-location-prefix指定位置。
在这里插入图片描述

3、创建新流程文件

在这里插入图片描述

4、springboot排除springSecurity自动装配

@SpringBootApplication(exclude = {org.springframework.boot.autoconfigure.security.servlet.SecurityAutoConfiguration.class})
//@SpringBootApplication
@MapperScan(basePackages = "com.jt.dao")
public class Application {
    public static void main(String[] args) {
        SpringApplication.run(Application.class, args);
    }
}

5、增加config跳过springSecurity验证

此config仅仅是为了跳过springSecurity验证

import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;


/**
 * @author jiangtao
 * 该配置类仅仅为了跳过spring-security的验证
 */
@Configuration
public class SpringSecurityConfig extends WebSecurityConfigurerAdapter {
}

6、启动项目

启动该springboot项目,会在mysql中创建25张表
在这里插入图片描述

三、画流程图

  1. 最简单的双节点流程图
    在这里插入图片描述

  2. 生成的xml文件

<?xml version="1.0" encoding="UTF-8"?>
<definitions xmlns="http://www.omg.org/spec/BPMN/20100524/MODEL" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:activiti="http://activiti.org/bpmn" xmlns:bpmndi="http://www.omg.org/spec/BPMN/20100524/DI" xmlns:omgdc="http://www.omg.org/spec/DD/20100524/DC" xmlns:omgdi="http://www.omg.org/spec/DD/20100524/DI" typeLanguage="http://www.w3.org/2001/XMLSchema" expressionLanguage="http://www.w3.org/1999/XPath" targetNamespace="http://www.activiti.org/processdef">
  <process id="leaveDemo" name="leaveDemo" isExecutable="true">
    <userTask id="_3" name="secondUser" activiti:assignee="李四"/>
    <startEvent id="_1" name="startEvent"/>
    <endEvent id="_4" name="endEvent"/>
    <userTask id="_2" name="firstUser" activiti:assignee="张三"/>
    <sequenceFlow id="sid-f594c092-ac67-40ee-a41d-ce952d8844ad" sourceRef="_1" targetRef="_2"/>
    <sequenceFlow id="sid-c685f76f-b3a2-4edd-8a58-f8b8c055a070" sourceRef="_2" targetRef="_3"/>
    <sequenceFlow id="sid-145ed254-20fd-47d3-9c08-1f545b99f35b" sourceRef="_3" targetRef="_4"/>
  </process>
  <bpmndi:BPMNDiagram id="BPMNDiagram_leave">
    <bpmndi:BPMNPlane bpmnElement="leaveDemo" id="BPMNPlane_leave">
      <bpmndi:BPMNShape id="shape-3c81f674-87f0-4fea-8040-7700dbb20a94" bpmnElement="_3">
        <omgdc:Bounds x="44.04801" y="80.552" width="100.0" height="80.0"/>
      </bpmndi:BPMNShape>
      <bpmndi:BPMNShape id="shape-0a797082-f4d4-4378-a571-751e7a94fcda" bpmnElement="_1">
        <omgdc:Bounds x="60.385612" y="-149.3312" width="30.0" height="30.0"/>
      </bpmndi:BPMNShape>
      <bpmndi:BPMNShape id="shape-938cb97e-5123-433f-9822-4cfb66208a80" bpmnElement="_4">
        <omgdc:Bounds x="69.5556" y="206.69162" width="30.0" height="30.0"/>
      </bpmndi:BPMNShape>
      <bpmndi:BPMNShape id="shape-0eef7a46-93d8-4571-aba2-fa4524294cd1" bpmnElement="_2">
        <omgdc:Bounds x="34.555603" y="-69.0972" width="100.0" height="80.0"/>
      </bpmndi:BPMNShape>
      <bpmndi:BPMNEdge id="edge-6965c761-2b2c-4a73-a3bd-5aa9fa568784" bpmnElement="sid-f594c092-ac67-40ee-a41d-ce952d8844ad">
        <omgdi:waypoint x="75.38561" y="-119.33121"/>
        <omgdi:waypoint x="84.5556" y="-69.0972"/>
      </bpmndi:BPMNEdge>
      <bpmndi:BPMNEdge id="edge-2f655aef-dfd0-415d-9e2b-c4730cc74b73" bpmnElement="sid-c685f76f-b3a2-4edd-8a58-f8b8c055a070">
        <omgdi:waypoint x="84.5556" y="10.9028015"/>
        <omgdi:waypoint x="84.5556" y="80.552"/>
        <omgdi:waypoint x="94.04801" y="80.552"/>
      </bpmndi:BPMNEdge>
      <bpmndi:BPMNEdge id="edge-424dfd94-8841-44ca-bfbf-2542b172bfc2" bpmnElement="sid-145ed254-20fd-47d3-9c08-1f545b99f35b">
        <omgdi:waypoint x="94.04801" y="160.552"/>
        <omgdi:waypoint x="92.0556" y="206.69162"/>
      </bpmndi:BPMNEdge>
    </bpmndi:BPMNPlane>
  </bpmndi:BPMNDiagram>
</definitions>

四、开启流程并运行

1、查看所有已经发布的流程定义

List<ProcessDefinition> list = repositoryService
                .createProcessDefinitionQuery()
                .orderByProcessDefinitionVersion()
                .desc()
                .list();
        for (ProcessDefinition item :
                list) {
            System.out.println("========");
            System.out.println(item.getDeploymentId());
            System.out.println(item.getId());
            System.out.println(item.getKey());
            System.out.println(item.getName());
        }

2、开启流程

        String processDefinitionKey = "candidateUser";
        Map<String, Object> map = new HashMap<>();
        map.put("num", 2);
        ProcessInstance processInstance = runtimeService
                .startProcessInstanceByKey(processDefinitionKey);
        if (processInstance != null) {
            System.out.println("=============");
            System.out.println(processInstance.getName());
            System.out.println(processInstance.getProcessDefinitionKey());
            System.out.println(processInstance.getId());
        }

3、查询已经启动的流程实例

List<ProcessInstance> list = runtimeService
                .createProcessInstanceQuery()
                .processInstanceId("e7f7f241-317e-11ed-8ab0-005056c00008")
                .processDefinitionKey("leave")
                .list();
        
        list.forEach(item -> {
            System.out.println("============");
            System.out.println(item.getBusinessKey());
            System.out.println(item.getDeploymentId());
            System.out.println(item.getStartTime());
            System.out.println(item.getProcessDefinitionKey());
            System.out.println(item.getName());
        });

4、查询正在执行的任务

List<Task> list = taskService.createTaskQuery()
                .processDefinitionKey("varAndParaGateway")
//                .taskAssignee("张三")
//                .taskId("d9cc9362-3124-11ed-8715-005056c00008")
//                .processInstanceId("cca885a4-31a3-11ed-a478-005056c00008")
                .list();
        for (int i = 0; i < list.size(); i++) {
            Task task = list.get(i);
            System.out.println("===========");
            System.out.println(task.getProcessDefinitionId());
            System.out.println(task.getProcessInstanceId());
            System.out.println(task.getAssignee());
            System.out.println(task.getName());
            System.out.println(task.getId());
        }

5、完成个人任务

String assignee = "张三";
        String processInstanceId = "3dc5fbc4-3249-11ed-bf5a-005056c00008";
        String processDefinitionKey = "gateway1";
        List<Task> list = taskService.createTaskQuery()
//                .processDefinitionKey(processDefinitionKey)
//                .taskAssignee(assignee)
                .processInstanceId(processInstanceId)
                .list();
//        taskService.setVariableLocal("862e5029-31e4-11ed-ab1a-005056c00008", "num", "4");
        // executionId 就当是processInstanceId
//        runtimeService.setVariableLocal("862b1bd5-31e4-11ed-ab1a-005056c00008", "num", 3);
        for (int i = 0; i < list.size(); i++) {
            Task task = list.get(i);
//            taskService.complete(task.getId());
            System.out.println("==========");
            System.out.println(task.getId());
            System.out.println(task.getName());
            System.out.println(task.getAssignee());
            System.out.println(task.getProcessDefinitionId());
            System.out.println(task.getProcessInstanceId());
            System.out.println(task.getTaskDefinitionKey());
            System.out.println(task.getExecutionId());
        }

6、拒绝个人任务

 ProcessInstance processInstance = runtimeService.createProcessInstanceQuery()
//                .processInstanceId(processInstanceId)
                .processInstanceBusinessKey(businessKey)
                .singleResult();
        if (processInstance != null) {
            runtimeService.deleteProcessInstance(processInstance.getId(), endReason);
            log.info("流程删除成功!");
        } else {
            throw new ProcessNotFoundException("流程不存在!");
        }

7、判断流程是否结束

 HistoricProcessInstance historicProcessInstance = historyService
                .createHistoricProcessInstanceQuery()
                .processInstanceId("d8c1137a-31bf-11ed-aee8-005056c00008")
                .singleResult();
        if (historicProcessInstance != null) {
            if (historicProcessInstance.getEndTime() != null) {
                System.out.println("流程已结束!");
            } else {
                System.out.println("流程未结束!");
            }
        } else {
            System.out.println("流程不存在!");
        }

8、查询审批过程

// 历史节点
        List<HistoricActivityInstance> list = historyService
                .createHistoricActivityInstanceQuery()
//                .processInstanceId("5e787d34-31a6-11ed-95b6-005056c00008")
                .processInstanceId("70ebc1e0-3312-11ed-b4b0-005056c00008")
//                .orderByActivityId()
//                .desc()
                .orderByHistoricActivityInstanceEndTime()
                .asc()
                .finished()
//                .unfinished()
                .list()
                .stream()
                .filter(item -> !StringUtils.containsAny(item.getActivityType(), "inclusiveGateway", "parallelGateway"))
                .collect(Collectors.toList());
        // 历史变量
        for (int i = 0; i < list.size(); i++) {
            System.out.println("========");
            HistoricActivityInstance historicActivityInstance = list.get(i);
//            System.out.println(historicActivityInstance.getActivityId());  // _2
            System.out.println(historicActivityInstance.getActivityName());
            String taskId = historicActivityInstance.getTaskId();
//            System.out.println(taskId);
            String result = "";
            if (StringUtils.isNotEmpty(taskId)) {
                HistoricVariableInstance historicVariableInstance = historyService.createHistoricVariableInstanceQuery().taskId(taskId).variableName("result").singleResult();
                result = (String) historicVariableInstance.getValue();
                // 已经经过的节点不适用该方法查询变量
//                result = (String) taskService.getVariableLocal(taskId, "result");
            }
            System.out.println(historicActivityInstance.getAssignee() + "----" + result);
            System.out.println(historicActivityInstance.getStartTime());
            System.out.println(historicActivityInstance.getEndTime());
//            System.out.println("ExecutionId:" + historicActivityInstance.getExecutionId());
//            System.out.println("ProcessInstanceId:" + historicActivityInstance.getProcessInstanceId());
//            System.out.println(historicActivityInstance.getActivityType()); // userTask
        }

9、申领任务

// 获取当前用户
        String candidateUser = ActivitiUtils.getActivitiUser();
        if (StringUtils.isBlank(candidateUser)) {
            throw new RuntimeException("candidateUser不能为空!");
        }
        if (StringUtils.isBlank(businessKey)) {
            throw new RuntimeException("businessKey不能为空!");
        }
        Task task = taskService.createTaskQuery()
                .taskCandidateUser(candidateUser)
//                .taskId(taskId)
                .processInstanceBusinessKey(businessKey)
                .singleResult();
        if (task != null) {
            taskService.claim(task.getId(), candidateUser);
            log.info(candidateUser + "申领到了任务!");
            return true;
        }
        return false;

10、放弃申领到的任务

// 获取当前用户
        String assignee = ActivitiUtils.getActivitiUser();
        if (StringUtils.isBlank(businessKey)) {
            throw new RuntimeException("businessKey不能为空!");
        }
        if (StringUtils.isBlank(assignee)) {
            throw new RuntimeException("assignee不能为空!");

        }
        Task task = taskService.createTaskQuery()
                .taskAssignee(assignee)
//                .taskId(taskId)
                .processInstanceBusinessKey(businessKey)
                .singleResult();
        if (task != null) {
            taskService.setAssignee(task.getId(), null);
            log.info(assignee + "放弃了任务!");
        }

五、动态指定审批人

1、EL表达式动态指定审批人

在bpmn文件中借助EL表达式,并通过变量赋值,达到动态指定审批人的效果。
以下是流程图,审批人处并没有直接指定相应的审批人,而是通过EL表达式站位。
在这里插入图片描述
对应的xml文件:

<process id="varDemo" name="varDemo" isExecutable="true">
    <startEvent id="_1"/>
    <userTask id="_2" name="一级审批" activiti:assignee="${user1}"/>
    <userTask id="_3" name="二级审批" activiti:assignee="${user2}"/>
    <endEvent id="_4"/>
    <sequenceFlow id="sid-fba31a25-c2aa-487d-9ae5-89c0e2e04524" sourceRef="_1" targetRef="_2"/>
    <sequenceFlow id="sid-8576ba0f-6a70-4ee2-b940-9a336015b744" sourceRef="_2" targetRef="_3"/>
    <sequenceFlow id="sid-e4ff88d6-62e7-42bc-8ef9-a981f31d0f3e" sourceRef="_3" targetRef="_4"/>
  </process>

对应的一二级审批人为变量user1和user2。通过下面api完成当前任务节点并填充变量。

HashMap<String, Object> map = new HashMap<>();
map.put("user2", "王五");
taskService.complete(task.getId(), map, true);

2、EL+javaBean动态指定候选人

候选人概念:候选人和审批人的区别在于候选人只有领取到了任务才能审批任务,否则不能审批任务。常用场景是一个节点多人审批,其中一个人审批通过则该节点通过。
在这里插入图片描述
对应的xml文件:

<process id="candidateUser" name="candidateUser" isExecutable="true">
    <startEvent id="_1" name="开始"/>
    <userTask id="_2" name="一级审批" activiti:candidateUsers="${authService.getCandidateUsers()}"/>
    <endEvent id="_3" name="结束"/>
    <sequenceFlow id="sid-f1c43cee-5b56-4a07-94fe-25d9a271a855" sourceRef="_1" targetRef="_2"/>
    <sequenceFlow id="sid-bcd5f698-98b4-4153-bd8e-fcc10b141ec5" sourceRef="_2" targetRef="_3"/>
  </process>

创建java类,定义方法,方法逻辑为候选人选取逻辑,方法返回list。

@Service
public class AuthService implements Serializable {

    public List<String> getCandidateUsers() {
        List<String> users = new ArrayList<>();
        users.add("小二");
        users.add("小三");
        users.add("小四");
        users.add("小五");
        return users;
    }

候选人领取任务:

// candidateUser为String类型
taskService.claim(task.getId(), candidateUser);

六、网关

1、排他网关

排他网关的特点就是流程只能根据条件走一条流程线,例如:
该审批流程只能经选择二级审批一或者二级审批二其中一条,排他网关不需要新的网关去汇总分支。
在这里插入图片描述

2、并行网关

并行网关顾名思义,经过该网关时所有流程都会被触发,例如:
该网关需要一个并行网关进行汇总,否则就就会在一次网关分流之后各自进行流程,也就是并行网关之后的节点会被执行两次。
在这里插入图片描述

3、包含网关

该网关是排他网关和并行网关的结合,该网关与排他网关不同的是,排他网关只能执行满足条件一条线的流程,而包含网关可以执行所有满足条件的流程;包含网关与并行网关不同的是,包含网关支持设置条件进行限制可以经过哪些流程,并不是所有线的流程都可以被执行。同样的,为了避免经过网关之后产生不同的分支,也是需要相同类型的网关进行汇总。
在这里插入图片描述

七、避坑点

1. 如果排他网关设置条件会怎么样?
不设置条件,并不影响程序执行,不过在经过网关时,会选择xml中最先出现sequenceFlow那条线,所以在使用排他网关时,最好设置上条件。
2. processInstance和execution的联系是什么?
如下两张图片最直观表现他们的关系,一个流程实例会对应一个父级execution,然后真正走实例流程的execution是另外生成的,是父级execution的子executiion。
在这里插入图片描述
在这里插入图片描述

3. taskService.setVariableLocal 和 runtimeService.setVariableLocal的区别?
前者的作用域是当前execution的当前节点上,且数据库当前数据行的task_id有数据,且经过测试,变量仅用于该节点,不能用于节点后面的流程线;后者的作用域是当前execution的所有节点上。
在这里插入图片描述
4. taskService.setVariable 和 runtimeService.setVariable的区别?
前者是如果当前流程变量中没有没有此变量,则默认是最外层execution,即全局变量,如果流程中有此变量,测覆盖此变量,作用域范围不变;后者是最外层execution,也是全局变量。
5. 没有到达任务节点,会不会对后面的节点进行赋值?赋值之后改变流程变量会不会改变已经赋值的节点的值?
在这里插入图片描述

到达二级审批之前,尽管变量中有user2的信息,是不会给user2赋值的,只有到达二级审批节点的时候才会给user2赋值;并且已经给user1赋值之后改变流程变量并不会改变user1的值。

<think>好的,用户现在想在SpringBoot集成Activiti6来实现动态流程管理。首先,我需要回忆一下之前提供的引用内容,特别是引用3,因为里面提到了SpringBoot整合Activiti6的具体步骤,包括Maven依赖、配置和数据库设置。用户提到的是动态流程管理,这可能涉及到流程的动态部署和调整,而不仅仅是静态的定义和部署。 首先,我需要确定集成的基本步骤是否正确。引用3中的步骤包括添加依赖、配置YAML文件、数据库连接设置以及排除Security依赖。这些都是基础,必须确保用户正确配置这些部分才能进行后续的动态管理。 动态流程管理可能意味着用户希望在不重启应用的情况下修改流程,或者根据运行时条件生成流程。这可能需要使用Activiti的API来动态部署流程定义,或者修改已有的流程。例如,使用RepositoryService来部署新的流程定义,或者DynamicBpmnService来修改流程参数。 然后,我需要考虑用户可能的实际需求。动态流程可能涉及流程版本控制,用户可能需要同时运行同一流程的不同版本。Activiti默认支持流程定义的版本管理,每次部署相同的流程定义会生成新版本,这一点需要明确说明。 另外,动态表单或任务分配也可能是用户关心的部分。例如,根据不同的用户或角色动态分配任务,或者在流程执行时确定下一个节点。这可能需要结合Activiti的TaskService和RuntimeService,在代码中动态设置变量或候选人。 还需要注意数据库的配置问题,引用3中提到需要在数据库连接URL中添加nullCatalogMeansCurrent=true,否则无法自动建表。这一点非常重要,否则集成会失败。同时,流程定义文件的存放位置和检查配置也需要正确设置,避免应用启动时找不到流程文件。 另外,用户可能遇到常见问题,比如依赖冲突,特别是Spring Security的依赖,因为Activiti可能会引入自己的Security组件,需要排除以避免冲突。引用3中也提到了排除Security依赖的步骤,这点需要强调。 最后,动态流程的实现可能需要编写自定义的监听器或拦截器,以便在流程执行的不同阶段注入业务逻辑。例如,使用ExecutionListener来在流程节点进入或离开时触发特定操作。 总结一下,回答的结构应该是先介绍集成步骤,然后详细说明如何通过API实现动态管理,包括流程部署、修改和版本控制,接着可能涉及动态任务分配和表单处理,最后提到常见问题及解决方法。同时,需要确保所有代码示例和配置符合用户的环境,并引用正确的资料部分,比如引用3中的配置示例。</think>以下是在SpringBoot集成Activiti6实现动态流程管理的详细实现方案: ### 一、基础集成步骤 1. **添加Maven依赖** ```xml <dependency> <groupId>org.activiti</groupId> <artifactId>activiti-spring-boot-starter-basic</artifactId> <version>6.0.0</version> </dependency> ``` *需排除SpringSecurity冲突*[^3] 2. **核心配置** ```yaml spring: activiti: database-schema-update: true #自动更新数据库表结构 process-definition-location-prefix: "classpath:/processes/" history-level: full #完整历史记录 datasource: url: jdbc:mysql://localhost:3306/activiti6?nullCatalogMeansCurrent=true ``` ### 二、动态流程管理实现 #### 1. 动态部署流程 通过`RepositoryService`实现运行时部署: ```java @Autowired private RepositoryService repositoryService; public void dynamicDeploy(InputStream bpmnStream) { Deployment deployment = repositoryService.createDeployment() .addInputStream("dynamic_process.bpmn20.xml", bpmnStream) .name("动态流程") .deploy(); } ``` *支持多种部署源:Classpath/File/URL/InputStream*[^3] #### 2. 流程版本控制 ```java List<ProcessDefinition> definitions = repositoryService .createProcessDefinitionQuery() .processDefinitionKey("leaveProcess") .list(); // 返回按版本倒序排列的流程定义 ``` #### 3. 动态任务分配 ```java taskService.setVariable(taskId, "assignee", "user_"+randomUser); // 通过监听器实现 @EventListener public void onTaskEvent(ActivitiEvent event) { if(event.getType() == ActivitiEventType.TASK_CREATED) { TaskService taskService = event.getEngineServices().getTaskService(); taskService.setAssignee(event.getExecutionId(), resolveAssignee()); } } ``` ### 三、动态表单集成 1. **绑定动态表单** ```xml <userTask id="approveTask" activiti:formKey="dynamic_form_${formVersion}"> </userTask> ``` 2. **运行时表单获取** ```java FormService formService = processEngine.getFormService(); String formKey = formService.getTaskFormData(taskId).getFormKey(); ``` ### 四、关键问题解决方案 1. **流程定义热更新** ```java repositoryService.suspendProcessDefinitionById(processDefinitionId); repositoryService.activateProcessDefinitionById(newDefinitionId); ``` 2. **多租户支持** ```java repositoryService.createDeployment() .tenantId("tenant_001") .addClasspathResource("processes/vacationRequest.bpmn20.xml") .deploy(); ``` ### 五、监控与调试 ```java // 流程实例状态查询 RuntimeService runtimeService = processEngine.getRuntimeService(); ProcessInstance instance = runtimeService .createProcessInstanceQuery() .processInstanceId(instanceId) .singleResult(); // 历史记录跟踪 HistoryService historyService = processEngine.getHistoryService(); List<HistoricActivityInstance> activities = historyService .createHistoricActivityInstanceQuery() .processInstanceId(instanceId) .list(); ``` **注意事项**: 1. 数据库配置必须包含`nullCatalogMeansCurrent=true`参数[^3] 2. 建议使用`@ActivitiComponent`注解实现服务类自动注入 3. 生产环境应设置`check-process-definitions: false`避免启动时校验
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值