红茶的个人站点

  • 首页
  • 专栏
  • 开发工具
  • 其它
  • 隐私政策
Awalon
Talk is cheap,show me the code.
  1. 首页
  2. 未分类
  3. 正文

Activiti 学习笔记 1:开始

2025年5月13日 63点热度 0人点赞 0条评论

新建一个 Maven 项目。

依赖

添加相关依赖:

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
​
    <groupId>cn.icexmoon</groupId>
    <artifactId>activiti-maven-demo</artifactId>
    <description>maven 项目整合 activiti 的示例</description>
    <version>1.0-SNAPSHOT</version>
​
    <properties>
        <maven.compiler.source>17</maven.compiler.source>
        <maven.compiler.target>17</maven.compiler.target>
        <slf4j.version>1.7.30</slf4j.version>
        <log4j.version>1.2.12</log4j.version>
        <activiti.version>7.1.0.M6</activiti.version>
    </properties>
    <dependencies>
        <dependency>
            <groupId>org.activiti</groupId>
            <artifactId>activiti-engine</artifactId>
            <version>${activiti.version}</version>
        </dependency>
        <dependency>
            <groupId>org.activiti</groupId>
            <artifactId>activiti-spring</artifactId>
            <version>${activiti.version}</version>
        </dependency>
        <!-- bpmn 模型处理 -->
        <dependency>
            <groupId>org.activiti</groupId>
            <artifactId>activiti-bpmn-model</artifactId>
            <version>${activiti.version}</version>
        </dependency>
        <!-- bpmn 转换 -->
        <dependency>
            <groupId>org.activiti</groupId>
            <artifactId>activiti-bpmn-converter</artifactId>
            <version>${activiti.version}</version>
        </dependency>
        <!-- bpmn json数据转换 -->
        <dependency>
            <groupId>org.activiti</groupId>
            <artifactId>activiti-json-converter</artifactId>
            <version>${activiti.version}</version>
        </dependency>
        <!-- bpmn 布局 -->
        <dependency>
            <groupId>org.activiti</groupId>
            <artifactId>activiti-bpmn-layout</artifactId>
            <version>${activiti.version}</version>
        </dependency>
        <!-- mysql驱动 -->
        <!-- https://mvnrepository.com/artifact/com.mysql/mysql-connector-j -->
        <dependency>
            <groupId>com.mysql</groupId>
            <artifactId>mysql-connector-j</artifactId>
            <version>9.3.0</version>
        </dependency>
        <!-- mybatis -->
        <dependency>
            <groupId>org.mybatis</groupId>
            <artifactId>mybatis</artifactId>
            <version>3.5.0</version>
        </dependency>
        <!-- 链接池 -->
        <dependency>
            <groupId>commons-dbcp</groupId>
            <artifactId>commons-dbcp</artifactId>
            <version>1.4</version>
        </dependency>
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.12</version>
        </dependency>
        <!-- log start -->
        <dependency>
            <groupId>log4j</groupId>
            <artifactId>log4j</artifactId>
            <version>${log4j.version}</version>
        </dependency>
        <dependency>
            <groupId>org.slf4j</groupId>
            <artifactId>slf4j-api</artifactId>
            <version>${slf4j.version}</version>
        </dependency>
        <dependency>
            <groupId>org.slf4j</groupId>
            <artifactId>slf4j-log4j12</artifactId>
            <version>${slf4j.version}</version>
        </dependency>
    </dependencies>
​
</project>

最好用 Maven Helper 或其它插件/工具检查下 Maven 依赖是否有冲突,最常见的是外部引用的 Mybatis 与 activiti-core 中的 Mybatis 版本不一致。

配置

添加日志配置文件log4j.properties:

# Set root category priority to INFO and its only appender to CONSOLE.
#log4j.rootCategory=INFO, CONSOLE debug info warn error fatal
log4j.rootCategory=debug, CONSOLE, LOGFILE
# Set the enterprise logger category to FATAL and its only appender to CONSOLE.
log4j.logger.org.apache.axis.enterprise=FATAL, CONSOLE
# CONSOLE is set to be a ConsoleAppender using a PatternLayout.
log4j.appender.CONSOLE=org.apache.log4j.ConsoleAppender
log4j.appender.CONSOLE.layout=org.apache.log4j.PatternLayout
log4j.appender.CONSOLE.layout.ConversionPattern=%d{ISO8601} %-6r[%15.15t] %-5p %30.30c %x - %m\n
# LOGFILE is set to be a File appender using a PatternLayout.
log4j.appender.LOGFILE=org.apache.log4j.FileAppender
log4j.appender.LOGFILE.File=D:\workspace\learn-activiti\ch1\activiti-maven-demo\logs\activiti.log
log4j.appender.LOGFILE.Append=true
log4j.appender.LOGFILE.layout=org.apache.log4j.PatternLayout
log4j.appender.LOGFILE.layout.ConversionPattern=%d{ISO8601} %-6r[%15.15t] %-5p %30.30c %x - %m\n

创建一个名为activiti的测试用数据库。

Activiti 的核心组件是 ProcessEngine,ProcessEngine的创建需要ProcessEngineConfiguration提供相关配置信息,该类型的 spring bean 在默认情况下由source目录下的activiti.cfg.xml配置文件定义。

image-20250513102008011

添加 Activiti 的配置文件activiti.cfg.xml:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:context="http://www.springframework.org/schema/context"
       xmlns:tx="http://www.springframework.org/schema/tx"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
                    http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/contex
http://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/tx
http://www.springframework.org/schema/tx/spring-tx.xsd">
    <bean id="processEngineConfiguration"
          class="org.activiti.engine.impl.cfg.StandaloneProcessEngineConfiguration">
        <property name="jdbcDriver" value="com.mysql.cj.jdbc.Driver"/>
        <property name="jdbcUrl" value="jdbc:mysql:///activiti"/>
        <property name="jdbcUsername" value="root"/>
        <property name="jdbcPassword" value="mysql"/>
        <!-- activiti数据库表处理策略 -->
        <property name="databaseSchemaUpdate" value="true"/>
    </bean>
</beans>

databaseSchemaUpdate 属性决定启动时 activiti 如何处理数据库中 activiti 相关表结构:

  • false(默认),在创建 Process Engine 时,根据库检查 DB 架构的版本,如果版本不匹配,则引发异常。

  • create-drop,在创建 Process Engine 时创建架构,并在关闭 Process Engine 时删除架构。

  • true,在构建流程引擎时,将执行检查,并在必要时执行架构更新

编写一个入口文件用于测试:

public class Application {
    public static void main(String[] args){
        ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();
        System.out.println(processEngine);
    }
}

实际执行报错,报错信息为 Activiti 试图从数据库读取版本等相关信息时发现表不存在。按理说此时 Activit 会自动生成相关表,但并不会,不知道是不是版本问题。

幸运的是 Activiti 的表结构生成 SQL 保存在包中,我们可以通过解析包来获取这些 SQL,直接手动生成表结构。

get-activiti-sql

除了上面那种配置,还可以将数据库连接池和 Activiti 配置类分别配置:

<bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource">
    <property name="driverClassName" value="com.mysql.cj.jdbc.Driver"/>
    <property name="url" value="jdbc:mysql:///activiti"/>
    <property name="username" value="root"/>
    <property name="password" value="mysql"/>
    <property name="maxActive" value="3"/>
    <property name="maxIdle" value="1"/>
</bean>
<bean id="processEngineConfiguration"
      class="org.activiti.engine.impl.cfg.StandaloneProcessEngineConfiguration">
    <!--引入上面配置好的 链接池-->
    <property name="dataSource" ref="dataSource"/>
    <!-- activiti数据库表处理策略 -->
    <property name="databaseSchemaUpdate" value="true"/>
</bean>

这样 Activiti 使用我们设置好的数据库连接池连接数据库。

除了使用默认的activiti.cfg.xml配置 ProcessEngineConfiguration,还可以用编程的方式加载指定的配置文件来配置:

ProcessEngineConfiguration configuration = ProcessEngineConfiguration.createProcessEngineConfigurationFromResource("activiti.cfg.xml");
ProcessEngine processEngine = configuration.buildProcessEngine();

BPMN2

BPMN(Business Process Model and Notation),直接翻译是业务流程建模和注释,换言之用于对工作流进行建模的一门语言,作用和定位与 UML 类似,不过适用范围没有那么广。

BPMN 2.0 是最新的 BPMN 版本,Activiti 使用 BPMN2 描述工作流定义文件。

在设计一个简单的 BPMN2 流程定义前,需要先连接一些 BPMN2 的简单术语:

Event

中文可以称作事件,主要包含以下类型:

image-20250514123042541

Activity

中文可以称作“活动”,一个活动可以是一个任务,或者一个当前流程的子处理流程。

Task

中文可以称作任务,一个任务表示工作需要被外部实体完成,比如人工或自动服务。任务的类型显示在矩形的左 上角,用小图标区别。根据任务的类型,引擎会执行不同的功能。

常见的任务类型:

image-20250514123340589

利用这些简单术语(组件)就可以定义一个作为示例的简单 BPMN2 工作流,更多术语在需要的时候介绍。

定义工作流

Activiti 使用 BPMN2 建模语言来定义工作流,有多种工具可以绘制 BPMN2 定义的工作流,在 Idea 中可以使用以下插件:

image-20250513103630914

旧版本 Idea 可以使用 actiBPM 插件。

activiti-plugin

有一部分菜单操作没录上,XML 跳转到 BPMN2 绘图板时右键点击的菜单为:

image-20250513105357626

作为参考,这里我定义的示例工作流可以从这里获取。

定义好工作流文件(*.bmpn20.xml)后,为了方便以图形化方式查看工作流定义,还需要将其转换为 png 文件保存一份,Idea 的工作流插件就可以完成此过程:

activiti-plugin-png

奇怪的是我这里生成的 png 文件看不到 Task 和工作流的名称,所以选择使用另一个处理 BPMN 的工具 Camunda Modeler。

先从官网下载安装 Camunda Modeler。为了方便 Idea 调用,最好在 Idea 中关联 Camunda Modeler:

Camunda

External Tool 具体定义可以参考我的:

image-20250513113355811

Program: "D:\software\coding\camunda-modeler-5.34.0-win-x64\Camunda Modeler.exe"
Arguments: "$FilePath$"
Working directory: $ProjectFileDir$

Program 和 Arguments 设置最好加引号,否则可能因为路径存在空格而程序出错。

之后就可以用 Camunda Modeler 另存为 PNG 文件,这里不再演示。

Camunda Modeler 生成的 PNG 文件美观度较差,不过可以满足使用。

部署工作流

ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();
RepositoryService repositoryService = processEngine.getRepositoryService();
Deployment deploy = repositoryService.createDeployment()
    .name("出差申请")
    .addClasspathResource("bpmn/test.bpmn20.xml")
    .addClasspathResource("bpmn/test.png")
    .deploy();
System.out.println(deploy.getId());

要注意的是,BPMN 文件和 PNG 文件命名要保持一致,否则 Activiti 不能正确将其一一对应(可能导致 act_re_procdef 表中 DGRM_RESOURCE_NAME_字段信息缺失)。此外,test.bpmn20.xml对应的 PNG 文件命名应当是test.png而非test.bpmn20.png。

RepositoryService 负责处理流程定义的相关工作。

createDeployment方法返回一个DeploymentBuilder类型,这是一个构建器(Builder),利用它可以构建一个部署(Deployment)。在调用DeploymentBuilder.deploy进行部署前,可以指定本次部署的相关设置,比如name方法可以指定本次部署名称,addClasspathResource方法可以指定部署使用的 BPMN2 文件以及对应的 PNG 文件。

部署成功后,Activiti 会在以下表生成工作流相关信息:

  • act_re_deployment,保存部署的相关信息,每次部署生成一条记录。

  • act_re_procdef,保存工作流定义,部署成功后会插入或更新一条工作流定义。

  • act_ge_bytearray,保存二进制文件,部署时使用过的二进制文件(工作流定义和PNG文件)保存在这里。

除了单个 BPMN 文件和 PNG 文件部署流程外,还可以用 zip 压缩包一次性部署多个流程。

假设我们的资源目录(resources/bpmn)下有一个压缩包processes.zip,包含多个 BPMN 和 PNG 文件,对其进行部署:

RepositoryService repositoryService = processEngine.getRepositoryService();
InputStream inputStream = this.getClass().getResourceAsStream("/bpmn/processes.zip");
ZipInputStream zipInputStream = new ZipInputStream(inputStream);
repositoryService.createDeployment()
    .addZipInputStream(zipInputStream)
    .deploy();

注意,这里没有为部署指定名称,是因为一次性部署多个,很难说明这次部署包含了哪些流程,因此省略了部署名称。

启动流程实例

启动一个流程实例:

RuntimeService runtimeService = processEngine.getRuntimeService();
ProcessInstance instance = runtimeService.startProcessInstanceByKey("test");
System.out.println("流程实例id:" + instance.getId());
System.out.println("流程定义id:" + instance.getProcessDefinitionId());
System.out.println("当前活动id:" + instance.getActivityId());

RuntimeService负责处理流程实例。这里的 startProcessInstanceByKey 方法接收的是流程定义(Process Define)的 key,即 act_procdef 表中的 key 字段的值。当然,该 key 由 BPMN2 文件定义:

<process id="test" name="test" isExecutable="true">

流程实例启动后,Activiti 会向以下表写入信息:

  • act_hi_actinst,流程实例执行时活动(activity)和事件(event)的历史记录

  • act_hi_identitylink,流程实例执行时活动负责人的历史记录

  • act_hi_procinst,流程实例执行时流程实例的历史记录

  • act_hi_taskinst,流程实例执行时任务(task)的历史记录

  • act_ru_execution,流程实例执行的相关记录

  • act_ru_identitylink,记录负责处理流程实例当前任务的人

  • act_ru_task,流程实例的当前任务

获取任务列表

可以通过以下方式获取指定某个工作流(流程定义)下指定用户的待处理任务列表:

final String PROCESS_DEFINE_KEY = "test";
final String USER_ID = "Jack";
TaskService taskService = processEngine.getTaskService();
List<Task> tasks = taskService.createTaskQuery()
        .processDefinitionKey(PROCESS_DEFINE_KEY)
        .taskAssignee(USER_ID)
        .list();
for (Task task : tasks) {
    System.out.println("流程实例id" + task.getProcessInstanceId());
    System.out.println("任务id" + task.getId());
    System.out.println("任务负责人" + task.getAssignee());
    System.out.println("任务名称" + task.getName());
}

TaskService是负责处理任务(Task)相关的服务,createTaskQuery返回一个任务查询,可以利用它完成对任务的查询和筛选。

完成任务

工作流最常见的操作是完成当前任务并让流程进入到下一个任务:

final String TASK_ID = "2505";
TaskService taskService = processEngine.getTaskService();
taskService.complete(TASK_ID);

这个操作涉及以下表数据的修改:

  • act_hi_taskinst,记录上个任务的完成时间,并写入下个任务的相关信息

  • act_hi_actinst,记录上个活动的完成时间,并写入下个活动的相关信息

  • act_hi_identitylink,写入下个任务的负责人信息

  • act_ru_task,删除上个任务信息,写入下个任务的相关信息

  • act_ru_identitylink,写入下个任务负责人信息

  • act_ru_execution,写入任务执行信息

实际上,主要就是将act_ru_前缀表示的当前运行时的表中任务相关信息移动到act_hi_前缀表示的历史表中记录,然后将下个任务写入到运行时相关表中。

在目前这个示例中,第一个任务“创建出差申请”已经完成,还有三个用户任务(User Task)待完成,都完成后流程才能结束:

TaskService taskService = processEngine.getTaskService();
// 剩余的待审批人
final List<String> userIds = List.of("Tom", "Brus", "Jerry");
// 遍历审批人,完成审批动作
for (String userId : userIds) {
    Task task = taskService.createTaskQuery()
        .processDefinitionKey("test") // 查询 test 审批流实例
        .taskAssignee(userId) // 当前审批人
        .singleResult();// 示例中每个审批人只有1个待审批流程
    taskService.complete(task.getId()); // 完成审批
}

流程实例结束后,运行时相关表中的信息将被删除,流程实例执行过程中的活动(包括事件)记录保留在act_hi_actinst表中,任务相关记录保留在act_hi_taskinst表中。

本文的完整示例可以从这里获取。

The End.

参考资料

  • 黑马程序员java教程工作流引擎Activiti7基础到进阶

本作品采用 知识共享署名 4.0 国际许可协议 进行许可
标签: activiti bpmn
最后更新:2025年5月14日

魔芋红茶

加一点PHP,加一点Go,加一点Python......

点赞
< 上一篇
下一篇 >

文章评论

取消回复

*

code

COPYRIGHT © 2021 icexmoon.cn. ALL RIGHTS RESERVED.
本网站由提供CDN加速/云存储服务

Theme Kratos Made By Seaton Jiang

宁ICP备2021001508号

宁公网安备64040202000141号