新建一个 Maven 项目。
依赖
添加相关依赖:
<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
配置文件定义。
添加 Activiti 的配置文件activiti.cfg.xml
:
<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,直接手动生成表结构。
除了上面那种配置,还可以将数据库连接池和 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
中文可以称作事件,主要包含以下类型:
Activity
中文可以称作“活动”,一个活动可以是一个任务,或者一个当前流程的子处理流程。
Task
中文可以称作任务,一个任务表示工作需要被外部实体完成,比如人工或自动服务。任务的类型显示在矩形的左 上角,用小图标区别。根据任务的类型,引擎会执行不同的功能。
常见的任务类型:
利用这些简单术语(组件)就可以定义一个作为示例的简单 BPMN2 工作流,更多术语在需要的时候介绍。
定义工作流
Activiti 使用 BPMN2 建模语言来定义工作流,有多种工具可以绘制 BPMN2 定义的工作流,在 Idea 中可以使用以下插件:
旧版本 Idea 可以使用 actiBPM 插件。
有一部分菜单操作没录上,XML 跳转到 BPMN2 绘图板时右键点击的菜单为:
作为参考,这里我定义的示例工作流可以从获取。
定义好工作流文件(*.bmpn20.xml)后,为了方便以图形化方式查看工作流定义,还需要将其转换为 png 文件保存一份,Idea 的工作流插件就可以完成此过程:
奇怪的是我这里生成的 png 文件看不到 Task 和工作流的名称,所以选择使用另一个处理 BPMN 的工具 Camunda Modeler。
先从下载安装 Camunda Modeler。为了方便 Idea 调用,最好在 Idea 中关联 Camunda Modeler:
External Tool 具体定义可以参考我的:
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.
文章评论