0


Activiti实战——Springboot整合Activiti

Activiti官方文档:Activiti User Guide

本篇博客代码地址:https://gitee.com/zhangyin_0116/springboot-activiti-learn.git

一、Activiti数据库表名说明

Activiti 的数据库名称都以 ACT_ 开头。第二部分是表用例的双字符标识。此用例也将大致匹配服务 API。

  • *ACT_RE_*RE* 代表 。具有此前缀的表包含静态信息,例如流程定义和流程资源(图像、规则等)。repository
  • **ACT_RU_**RU* 代表 。这些是包含流程实例、用户任务、变量、作业等的运行时数据的运行时表。Activiti 仅在流程实例执行期间存储运行时数据,并在流程实例结束时删除记录。这使运行时表保持小而快速。runtime
  • **ACT_ID_**ID* 代表 。这些表包含标识信息,例如用户、组等。identity
  • **ACT_HI_**HI* 代表 。这些是包含历史数据的表,例如过去的流程实例、变量、任务等。history
  • **ACT_GE_***:数据,用于各种用例。general

二、Spring boot整合activiti

1. 创建springboot项目

2. 引入activiti依赖及项目依赖

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-security</artifactId>
        </dependency>

        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>
        
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
        
        <dependency>
            <groupId>org.springframework.security</groupId>
            <artifactId>spring-security-test</artifactId>
            <scope>test</scope>
        </dependency>
        
        <!-- Mysql驱动包 -->
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>8.0.26</version>
        </dependency>

        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>druid</artifactId>
            <version>1.2.8</version>
        </dependency>

        <!-- https://mvnrepository.com/artifact/org.activiti/activiti-spring-boot-starter -->
        <dependency>
            <groupId>org.activiti</groupId>
            <artifactId>activiti-spring-boot-starter</artifactId>
            <version>7.1.0.M6</version>
        </dependency>
    </dependencies>
    通过引入activiti-spring-boot-starter依赖项,可以轻松地在Spring Boot应用程序中集成和配置Activiti引擎,而无需手动配置所有的Activiti相关组件和依赖项。

    该依赖项提供了一系列自动配置类和默认配置,使得Activiti的集成变得更加简单。它会自动配置数据库连接、事务管理器、流程引擎以及与Spring Boot框架的其他集成。

    使用activiti-spring-boot-starter依赖项,可以通过@Autowired注解轻松注入Activiti的各种服务接口(如RepositoryService、RuntimeService、TaskService等),并使用它们操作流程引擎。

    此外,该依赖项还提供了一些自定义属性配置,您可以通过在application.properties或application.yml文件中进行相应配置来定制Activiti的行为。 

3. 配置数据源

(1)创建数据源配置文件

@Configuration
public class DruidConfig {

    @Value("${spring.datasource.url}")
    private String url;

    @Value("${spring.datasource.username}")
    private String username;

    @Value("${spring.datasource.password}")
    private String password;

    @Value("${spring.datasource.driver-class-name}")
    private String driverClassName;

    @Bean
    public DataSource dataSource() {
        DruidDataSource dataSource = new DruidDataSource();
        dataSource.setUrl(url);
        dataSource.setUsername(username);
        dataSource.setPassword(password);
        dataSource.setDriverClassName(driverClassName);
        // 其他 Druid 相关配置,如最大连接数、初始连接数等
        return dataSource;
    }

    @Bean
    public PlatformTransactionManager transactionManager() {
        return new DataSourceTransactionManager(dataSource());
    }

}

(2)配置文件

# 数据源配置
spring:
  datasource:
    driverClassName: com.mysql.cj.jdbc.Driver
    url: jdbc:mysql://localhost:3306/activiti-learn?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8&nullCatalogMeansCurrent=true
    username: root
    password: 123456

4. 配置Acitviti引擎

@Configuration
public class ActivitiConfiguration {
    @Autowired
    private DataSource dataSource;
    @Autowired
    private PlatformTransactionManager platformTransactionManager;

    public ActivitiConfiguration() {
    }

    //通过@Bean注解将SpringProcessEngineConfiguration实例声明为Spring Bean,使其可供其他组件注入和使用
    @Bean
    public SpringProcessEngineConfiguration springProcessEngineConfiguration() {
        SpringProcessEngineConfiguration spec = new SpringProcessEngineConfiguration();
        //设置数据源,将注入的数据源设置到SpringProcessEngineConfiguration实例中
        spec.setDataSource(this.dataSource);
        //设置事务管理器将注入的事务管理器设置到SpringProcessEngineConfiguration实例中
        spec.setTransactionManager(this.platformTransactionManager);
        //设置数据库模式更新策略 true表示在启动时自动创建或更新Activiti引擎所需的数据库表结构
        spec.setDatabaseSchemaUpdate("true");
        Resource[] resources = null;
        //配置流程部署资源
        //使用PathMatchingResourcePatternResolver从classpath中的bpmn目录下加载所有以.bpmn为扩展名的文件作为流程定义资源,
        // 并将它们设置到SpringProcessEngineConfiguration实例中。
        try {
            resources = (new PathMatchingResourcePatternResolver()).getResources("classpath*:bpmn/*.bpmn");
        } catch (IOException var4) {
            var4.printStackTrace();
        }

        spec.setDeploymentResources(resources);
        return spec;
    }
}

5. 启动项目

启动项目,可以看到对应的数据库中自动生成了与Activiti相关的表。

三、Activiti接口

1. 流程引擎API和服务

从 ProcessEngine 中,可以获取包含工作流/BPM 方法的各种服务。

ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();

RuntimeService runtimeService = processEngine.getRuntimeService();
RepositoryService repositoryService = processEngine.getRepositoryService();
TaskService taskService = processEngine.getTaskService();
ManagementService managementService = processEngine.getManagementService();
IdentityService identityService = processEngine.getIdentityService();
HistoryService historyService = processEngine.getHistoryService();
FormService formService = processEngine.getFormService();
DynamicBpmnService dynamicBpmnService = processEngine.getDynamicBpmnService();

2. 使用Activiti服务

(1)绘制流程图,生成bpmn文件

这里是一个请假的流程定义bpmn文件:

<?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.kafeitu.me/activiti/leave">
  <process id="leave" name="请假流程-普通表单" isExecutable="true">
    <documentation>请假流程演示</documentation>
    <startEvent id="startevent1" name="提交申请" activiti:initiator="applyUserId"></startEvent>
    <userTask id="deptLeaderVerify" name="部门领导审批" activiti:candidateGroups="deptLeader" ></userTask>
    <exclusiveGateway id="exclusivegateway5"></exclusiveGateway>
    <userTask id="modifyApply" name="调整申请" activiti:assignee="${applyUserId}">
      <extensionElements>
        <modeler:initiator-can-complete xmlns:modeler="http://activiti.com/modeler"><![CDATA[false]]></modeler:initiator-can-complete>
      </extensionElements>
    </userTask>
    <userTask id="hrVerify" name="人事审批" activiti:candidateGroups="hr"></userTask>
    <exclusiveGateway id="exclusivegateway6"></exclusiveGateway>
    <userTask id="reportBack" name="销假" activiti:assignee="${applyUserId}">

    </userTask>
    <endEvent id="endevent1" name="结束"></endEvent>
    <exclusiveGateway id="exclusivegateway7"></exclusiveGateway>
    <sequenceFlow id="flow2" sourceRef="startevent1" targetRef="deptLeaderVerify"></sequenceFlow>
    <sequenceFlow id="flow3" sourceRef="deptLeaderVerify" targetRef="exclusivegateway5"></sequenceFlow>
    <sequenceFlow id="flow6" sourceRef="hrVerify" targetRef="exclusivegateway6"></sequenceFlow>
    <sequenceFlow id="flow8" sourceRef="reportBack" targetRef="endevent1"></sequenceFlow>
    <sequenceFlow id="flow11" sourceRef="modifyApply" targetRef="exclusivegateway7"></sequenceFlow>
    <sequenceFlow id="flow5" name="同意" sourceRef="exclusivegateway5" targetRef="hrVerify">
      <conditionExpression xsi:type="tFormalExpression"><![CDATA[${pass}]]></conditionExpression>
    </sequenceFlow>
    <sequenceFlow id="flow4" name="不同意" sourceRef="exclusivegateway5" targetRef="modifyApply">
      <conditionExpression xsi:type="tFormalExpression"><![CDATA[${!pass}]]></conditionExpression>
    </sequenceFlow>
    <sequenceFlow id="flow7" name="同意" sourceRef="exclusivegateway6" targetRef="reportBack">
      <conditionExpression xsi:type="tFormalExpression"><![CDATA[${pass}]]></conditionExpression>
    </sequenceFlow>
    <sequenceFlow id="flow9" name="不同意" sourceRef="exclusivegateway6" targetRef="modifyApply">
      <conditionExpression xsi:type="tFormalExpression"><![CDATA[${!pass}]]></conditionExpression>
    </sequenceFlow>
    <sequenceFlow id="flow12" name="结束流程" sourceRef="exclusivegateway7" targetRef="endevent1">
      <conditionExpression xsi:type="tFormalExpression"><![CDATA[${!pass}]]></conditionExpression>
    </sequenceFlow>
    <sequenceFlow id="flow10" name="重新申请" sourceRef="exclusivegateway7" targetRef="deptLeaderVerify">
      <conditionExpression xsi:type="tFormalExpression"><![CDATA[${pass}]]></conditionExpression>
    </sequenceFlow>
  </process>

(2)部署流程定义

部署流程定义的方法有很多种,这里只介绍一种实际项目中最常用的。

    @Autowired
    private ProcessEngine processEngine;

    @Autowired
    private RepositoryService repositoryService;

    @Test
    void deployProcessDefinition() throws FileNotFoundException {
        String processDefinitionFilePath = "D:\\Java_Dev\\IDEA_Projects\\Personal_project\\springboot-activiti-learn\\src\\main\\resources\\leave.bpmn";
        Deployment deploy = this.repositoryService.createDeployment()
                .addInputStream(processDefinitionFilePath, new FileInputStream(processDefinitionFilePath))
                .deploy();
        System.out.println("部署流程定义成功:"+ deploy);
    }

查看数据库表 act_re_procdef:

可以看到已经部署成功

(3)启动流程

启动流程的方法也有很多种,这里介绍的方法是比较常用的启动流程的方法,需要传递三个参数,分别是:

processDefinitionKey(流程定义key), businessKey(业务key), variables(参数)

这里我们使用流程定义的id去查询流程定义的key,用流程定义的key来启动流程

    @Test
    void startProcessInstance() {

        //启动流程时传递的参数列表 这里根据实际情况 也可以选择不传
        Map<String, Object> variables = new HashMap<String, Object>();
        variables.put("姓名", "张三");
        variables.put("请假天数", new Integer(4));
        variables.put("请假原因", "我很累!");

        // 根据流程定义ID查询流程定义  leave:1:10004是我们刚才部署的流程定义的id
        ProcessDefinition processDefinition = repositoryService.createProcessDefinitionQuery()
                .processDefinitionId("leave:1:10004")
                .singleResult();

        // 获取流程定义的Key
        String processDefinitionKey = processDefinition.getKey();

        //定义businessKey  businessKey一般为流程实例key与实际业务数据的结合
        //假设一个请假的业务 在数据库中的id是1001
        String businessKey = processDefinitionKey + ":" + "1001";
        //设置启动流程的人
        Authentication.setAuthenticatedUserId("xxyin");
        ProcessInstance processInstance = this.runtimeService
                .startProcessInstanceByKey(processDefinitionKey, businessKey, variables);
        System.out.println("流程启动成功:" + processInstance);
    }

查看数据库表 act_hi_procinst:

(4)查询待办任务列表

    @Test
    void findTodoTask() {
        TaskQuery taskQuery = ((TaskQuery)taskService.createTaskQuery().orderByTaskCreateTime()).asc();
        // <userTask id="deptLeaderVerify" name="部门领导审批" activiti:assignee="zhangsan" ></userTask>
        //添加查询条件 查询指派给 zhangsan 的任务  假设这个任务指派给了zhangsan
        taskQuery.taskAssignee("zhangsan");
        //添加查询条件 查询流程定义key为 leave 的任务
        taskQuery.processDefinitionKey("leave");
        List<Task> tasks = taskQuery.list();
        // 处理查询结果
        for (Task task : tasks) {
            System.out.println("Task ID: " + task.getId());
            System.out.println("Task Name: " + task.getName());
            // 其他任务属性的获取和处理
        }
    }

运行结果:

Task ID: 15010
Task Name: 部门领导审批

(5)完成任务

通过查询可以看到当前流转到的任务是“部门领导审批”,可以在表act_ru_task中看到:

    @Test
    void complete() {
        //这里模拟传进来的参数
        String taskId = "15010";
        String assignee = "zhangsan";
        Map<String, Object> variables = new HashMap<>();
        variables.put("pass",true);
        variables.put("comment","同意请假");

        //根据任务id获取到当前的任务
        TaskQuery query = this.taskService.createTaskQuery();
        Task task = (Task)((TaskQuery)query.taskId(taskId)).singleResult();

        //将参数列表的评论赋值给comment
        String taskComment = variables.remove("comment").toString();
        //向特定的任务添加评论
        if (!taskComment.equals("")) {
            taskService.addComment(taskId, task.getProcessInstanceId(), taskComment);
        }
        //如果当前任务没有指派人,需要先使用 claim() 方法领取任务
        taskService.claim(taskId,assignee);
        //如果有指派人,直接完成任务
        taskService.complete(taskId,variables);
    }

完成该任务后,再次查看 act_ru_task 表,可以看到任务已经流转到了“人事审批”:

查看评论表act_hi_comment,可以看到任务评论已经添加到对应的任务上:

(6)暂停和激活流程定义

可以挂起进程定义。挂起流程定义时,无法创建新的流程实例(将引发异常)。暂停进程定义是通过 :**

RepositoryService

**

    @Test
    void suspendProcessDefinition() {
        repositoryService.suspendProcessDefinitionByKey("leave");
        try {
            runtimeService.startProcessInstanceByKey("leave");
        } catch (ActivitiException e) {
            e.printStackTrace();
        }
    }

运行结果:

2023-05-21 21:10:08.425 ERROR 29036 --- [           main] o.a.e.impl.interceptor.CommandContext    : Error while closing command context

org.activiti.engine.ActivitiException: Cannot start process instance. Process definition 请假流程-普通表单 (id = leave:1:10004) is suspended

如果想要激活,执行:

repositoryService.activateProcessDefinitionByKey("leave");

(7)查询接口

有两种方式可以从引擎查询数据,查询API和native查询

  • 查询 API

      允许使用流畅的 API 对完全类型安全的查询进行编程,例如:
    
List<Task> tasks = taskService.createTaskQuery()
    .taskAssignee("zhangsan")
    .processVariableValueEquals("name", "zhangsan")
    .orderByDueDate().asc()
    .list();
  • native查询

       允许您编写自己的 SQL 查询。返回类型由您使用的 Query 对象定义,数据映射到正确的对象中,例如 Task、ProcessInstance、Execution 等。由于查询将在数据库中触发,因此必须使用数据库中定义的表名和列名;
    

这个是查询任务名为“人事审批”的任务:

    @Test
    void nativeAPI() {
        // 创建原生任务查询对象 查询任务名为 人事审批 的任务
        NativeTaskQuery nativeTaskQuery = taskService.createNativeTaskQuery()
                .sql("SELECT * FROM " + managementService.getTableName(Task.class) + " T WHERE T.NAME_ = #{taskName}")
                .parameter("taskName", "人事审批");

        // 执行查询并获取结果
        List<Task> tasks = nativeTaskQuery.list();

        // 处理查询结果
        for (Task task : tasks) {
            System.out.println("Task ID: " + task.getId());
            System.out.println("Task Name: " + task.getName());
            // 其他任务属性的获取和处理
        }
    }

这个是查询任务id为 35006 ,并且没有指派人的任务:

    @Test
    void nativeAPI2() {
        // 创建原生任务查询对象
        NativeTaskQuery nativeTaskQuery = taskService.createNativeTaskQuery()
                .sql("SELECT * FROM " + managementService.getTableName(Task.class) +
                        " T WHERE T.ID_ = #{taskId} AND T.ASSIGNEE_ = #{assignee}" )
                .parameter("taskId","35006")
                .parameter("assignee", "");

        // 执行查询并获取结果
        List<Task> tasks = nativeTaskQuery.list();

        // 处理查询结果
        for (Task task : tasks) {
            System.out.println("Task ID: " + task.getId());
            System.out.println("Task Name: " + task.getName());
            // 其他任务属性的获取和处理
        }
    }
}

运行结果:

Task ID: 35006
Task Name: 人事审批

(8)EUL表达式

Activiti 使用 UEL 进行表达式解析。UEL 代表统一表达式语言,是 EE6 规范的一部分。分为两种:值表达式和方法表达式

  • 值表达式:解析为一个。默认情况下,所有流程变量都可供使用。
${myVar}
${myBean.myProperty}
  • 方法表达式:调用方法,带或不带参数。调用不带参数的方法时,请确保在方法名称后添加空括号(因为这会将表达式与值表达式区分开来)。传递的参数可以是文本值或自行解析的表达式。例子:
${printer.print()}
${myBean.addNewOrder('orderName')}
${myBean.doSomething(myVar, execution)}

在所有流程变量之上,还有一些默认对象可用于表达式:

  • execution:保存有关正在进行的执行的其他信息的 。DelegateExecution
  • task:保存有关当前任务的其他信息的 。注意:仅适用于从任务侦听器计算的表达式。DelegateTask
  • **authenticatedUserId**:当前经过身份验证的用户的 ID。如果没有用户经过身份验证,则该变量不可用。

四、BPMN2.0简介

1. 定义一个过程

  • process:有两个属性 id 和 name。
<definitions
  xmlns="http://www.omg.org/spec/BPMN/20100524/MODEL"
  xmlns:activiti="http://activiti.org/bpmn"
  targetNamespace="Examples">

  <process id="myProcess" name="My First Process">
    ..
  </process>

</definitions>
  • sequenceFlow:序列流
    <sequenceFlow id="flow5" name="同意" sourceRef="exclusivegateway5" targetRef="hrVerify">
      <conditionExpression xsi:type="tFormalExpression">
        <![CDATA[${pass}]]>
      </conditionExpression>
    </sequenceFlow>
<conditionExpression xsi:type="tFormalExpression">
  <![CDATA[${order.price > 100 && order.price < 250}]]>
</conditionExpression>
  • exclusiveGateway 排他网关

在XML中的表示:

<exclusiveGateway id="exclusiveGw" name="Exclusive Gateway" />

<sequenceFlow id="flow2" sourceRef="exclusiveGw" targetRef="theTask1">
  <conditionExpression xsi:type="tFormalExpression">${input == 1}</conditionExpression>
</sequenceFlow>

<sequenceFlow id="flow3" sourceRef="exclusiveGw" targetRef="theTask2">
  <conditionExpression xsi:type="tFormalExpression">${input == 2}</conditionExpression>
</sequenceFlow>

<sequenceFlow id="flow4" sourceRef="exclusiveGw" targetRef="theTask3">
  <conditionExpression xsi:type="tFormalExpression">${input == 3}</conditionExpression>
</sequenceFlow>
  • parallelGateway:并行网关

XML定义如下:

<startEvent id="theStart" />
<sequenceFlow id="flow1" sourceRef="theStart" targetRef="fork" />

<parallelGateway id="fork" />
<sequenceFlow sourceRef="fork" targetRef="receivePayment" />
<sequenceFlow sourceRef="fork" targetRef="shipOrder" />

<userTask id="receivePayment" name="Receive Payment" />
<sequenceFlow sourceRef="receivePayment" targetRef="join" />

<userTask id="shipOrder" name="Ship Order" />
<sequenceFlow sourceRef="shipOrder" targetRef="join" />

<parallelGateway id="join" />
<sequenceFlow sourceRef="join" targetRef="archiveOrder" />

<userTask id="archiveOrder" name="Archive Order" />
<sequenceFlow sourceRef="archiveOrder" targetRef="theEnd" />

<endEvent id="theEnd" />

五、查询历史

在 API 中,可以查询所有 5 个历史记录实体:

1. 历史过程实例查询

    @Test
    void historyAPI1() {
        List<HistoricProcessInstance> processInstances = historyService.createHistoricProcessInstanceQuery()
                .processDefinitionKey("leave")
                .orderByProcessInstanceDuration()
                .desc()
                .listPage(0, 10);
        System.out.println("ProcessInstance: "+ processInstances);
    }

2. 历史变量实例查询

    @Test
    void historyAPI2() {
        List<HistoricVariableInstance> historicVariableInstances = historyService.createHistoricVariableInstanceQuery()
                .processInstanceId("60002")
                .orderByVariableName().desc()
                .list();
        System.out.println("historicVariableInstances: "+ historicVariableInstances);
    }

3. 历史活动实例查询

查询已经完成的任务(用户任务):

    @Test
    void historyAPI3() {
        List<HistoricActivityInstance> userTask = historyService.createHistoricActivityInstanceQuery()
                .activityType("userTask")
                .processDefinitionId("leave:1:10004")
                .finished()
                .orderByHistoricActivityInstanceEndTime()
                .desc()
                .listPage(0, 1);
        System.out.println("userTask: "+ userTask);
    }

4. 历史细节查询

    @Test
    void historyAPI4() {
        List<HistoricDetail> historicDetails = historyService.createHistoricDetailQuery()
                .variableUpdates()
                .processInstanceId("60002")
                .orderByVariableName().asc()
                .list();
        System.out.println("historicDetails: "+ historicDetails);
    }

5. 历史任务实例查询

查询已完成的任务:

    @Test
    void historyAPI5() {
        List<HistoricTaskInstance> historicTaskInstances = historyService.createHistoricTaskInstanceQuery()
                .finished()
                .orderByHistoricTaskInstanceDuration()
                .desc()
                .listPage(0, 10);
        System.out.println("historicTaskInstances: "+ historicTaskInstances);
    }

查询未完成的任务:

    @Test
    void historyAPI6() {
        List<HistoricTaskInstance> historicTaskInstances = historyService.createHistoricTaskInstanceQuery()
                .unfinished()
                .orderByHistoricTaskInstanceDuration()
                .desc()
                .listPage(0, 10);
        System.out.println("historicTaskInstances: "+ historicTaskInstances);
    }

6. 历史配置

  • **none**:跳过所有历史记录存档。这是运行时进程执行的最高性能,但没有历史信息可用。
  • **activity**:存档所有流程实例和活动实例。在流程实例结束时,顶级流程实例变量的最新值将被复制到历史变量实例中。不会存档任何详细信息。
  • **audit**:这是默认值。它存档所有流程实例、活动实例,使变量值持续同步以及提交的所有表单属性,以便通过表单进行的所有用户交互都是可追溯的并且可以审核的。
  • **full**:这是历史存档的最高级别,因此速度最慢。此级别存储级别中的所有信息以及所有其他可能的详细信息,主要是过程变量更新。audit

本文转载自: https://blog.csdn.net/weixin_49561506/article/details/130791619
版权归原作者 小小印z 所有, 如有侵权,请联系我们删除。

“Activiti实战——Springboot整合Activiti”的评论:

还没有评论