Activit流程中心
简单聊一聊
这两天学习工作流,网上找了很多资料但是或多或少有一定的差异,不是时间太久就是信息不完善,因此我寻思着一直找别人的不如自己来写一篇基本入门。所有的流程操作大致都是如此,在多租户情况下,需要对所有用户的权限进行控制,然后通过身份验证进行流程操作。自习阅读activit工作流的日志记录,不难发现,每一步操作都涉及到了多了多表的同步更新,原理不难,总要的是去理解这个思想。
简介
Activiti项目是一项新的基于Apache许可的开源BPM平台,从基础开始构建,旨在提供支持新的BPMN 2.0标准,包括支持对象管理组(OMG),面对新技术的机遇,诸如互操作性和云架构,提供技术实现。
数据库相关
activit为我们提供了25张表,每张表对应了不同的功能作用。
Activiti数据库支持:
Activiti的后台是有数据库的支持,所有的表都以ACT_开头。 第二部分是表示表的用途的两个字母标识。 用途也和服务的API对应。
ACT_RE_*: 'RE’表示repository。 这个前缀的表包含了流程定义和流程静态资源 (图片,规则,等等)。
ACT_RU_*: 'RU’表示runtime。 这些运行时的表,包含流程实例,任务,变量,异步任务,等运行中的数据。 Activiti只在流程实例执行过程中保存这些数据, 在流程结束时就会删除这些记录。 这样运行时表可以一直很小速度很快。
ACT_ID_*: 'ID’表示identity。 这些表包含身份信息,比如用户,组等等。
ACT_HI_*: 'HI’表示history。 这些表包含历史数据,比如历史流程实例, 变量,任务等等。
ACT_GE_*: 通用数据, 用于不同场景下,如存放资源文件。
表结构操作:
1:资源库流程规则表
-
act_re_deployment 部署信息表
-
act_re_model 流程设计模型部署表
-
act_re_procdef 流程定义数据表
2:运行时数据库表
-
act_ru_execution 运行时流程执行实例表
-
act_ru_identitylink 运行时流程人员表,主要存储任务节点与参与者的相关信息
-
act_ru_task 运行时任务节点表
-
act_ru_variable 运行时流程变量数据表
3:历史数据库表
-
act_hi_actinst 历史节点表
-
act_hi_attachment 历史附件表
-
act_hi_comment 历史意见表
-
act_hi_identitylink 历史流程人员表
-
act_hi_detail 历史详情表,提供历史变量的查询
-
act_hi_procinst 历史流程实例表
-
act_hi_taskinst 历史任务实例表
-
act_hi_varinst 历史变量表
4:组织机构表
-
act_id_group 用户组信息表
-
act_id_info 用户扩展信息表
-
act_id_membership 用户与用户组对应信息表
-
act_id_user 用户信息表
这四张表很常见,基本的组织机构管理,关于用户认证方面建议还是自己开发一套,组件自带的功能太简单,使用中有很多需求难以满足
5:通用数据表
-
act_ge_bytearray 二进制数据表
-
act_ge_property 属性数据表存储整个流程引擎级别的数据,初始化表结构时,会默认插入三条记录,
整合SpringBoot
自己在整合时候一步一步填的坑,有兴趣的小伙伴可以看看。保证让你成功入门。
环境配置
pom.xml
<?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.fyyice</groupId>
<artifactId>activiti</artifactId>
<version>1.0-SNAPSHOT</version>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.1.0.RELEASE</version>
</parent>
<!-- activiti 的相关包 mysql的驱动包 mybatis log4j 数据库链接池-->
<properties>
<slf4j.version>1.6.6</slf4j.version>
<log4j.version>1.2.12</log4j.version>
<activiti.version>7.0.0.Beta1</activiti.version>
<project.build.soureEnconding>UTF-8</project.build.soureEnconding>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<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>
<!-- activiti 云支持 -->
<dependency>
<groupId>org.activiti.cloud</groupId>
<artifactId>activiti-cloud-services-api</artifactId>
<version>${activiti.version}</version>
</dependency>
<!-- mysql驱动 -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.20</version>
</dependency>
<!-- mybatis -->
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.4.5</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>
<dependency>
<groupId>commons-io</groupId>
<artifactId>commons-io</artifactId>
<version>2.6</version>
</dependency>
</dependencies>
</project>
application.yml
这里数据库版本根据自己的装的版本号进行相关配置,没有强制要求
spring:
datasource:
driver-class-name: com.mysql.jdbc.Driver
url: jdbc:mysql://127.0.0.1:3306/activiti?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8
username: root
password: root
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
history-level: full
#校验流程文件,默认校验resources下的processes文件夹里的流程文件
check-process-definitions: false
activit.cfg.xml
activit策略,可以调用相关方法自动生成数据库
<?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">
<!--dbcp链接池-->
<bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource">
<property name="driverClassName" value="com.mysql.jdbc.Driver"/>
<property name="url" value="jdbc:mysql:///activiti"/>
<property name="username" value="root"/>
<property name="password" value="root"/>
<property name="maxActive" value="3"/>
<property name="maxIdle" value="1"/>
</bean>
<!--在默认方式下 bean的id 固定为 processEngineConfiguration-->
<bean id="processEngineConfiguration"
class="org.activiti.engine.impl.cfg.StandaloneProcessEngineConfiguration">
<!--配置数据库相关的信息-->
<property name="jdbcDriver" value="com.mysql.jdbc.Driver"/>
<property name="jdbcUrl" value="jdbc:mysql://activiti"/>
<property name="jdbcUsername" value="root"/>
<property name="jdbcPassword" value="root"/>
<!--直接引用上面配置的链接池-->
<property name="dataSource" ref="dataSource"/>
<!--actviti数据库表在生成时的策略
true - 如果数据库中已经存在相应的表,那么直接使用,
如果不存在,那么会创建-->
<property name="databaseSchemaUpdate" value="true"/>
</bean>
</beans>
实例
这里所有的编码都是在测试环境下进行的@Test
1.自动生成数据库表
@org.junit.Test
public void createDB(){
ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();
System.out.println(processEngine);
}
概述
getDefaultProcessEngine方法会传入一个default的processEngineName进入到方法中。通过isInitialized判断配置文件中的数据库表是否存在,如果存在,则返回一个activit工作流引擎,如果没有则初始化,自动策略,根据resources = classLoader.getResources(“activiti.cfg.xml”);中的相关配置信息进行初始化。
public static ProcessEngine getProcessEngine(String processEngineName) {
if (!isInitialized()) {
init();
}
return (ProcessEngine)processEngines.get(processEngineName);
}
2.提交一个流程申请
这里我以请假流程进行说明。在加载流程时,我们需要对每一个节点指定对应的执行人。
请假流程: start → 创建请假流程(对应张三) → 小组长审批(对应leader) → 负责人审批(对应boss) → 结束
@org.junit.Test
//启动一个流程
public void get(){
//获取工作流引擎
ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();
RepositoryService repositoryService = processEngine.getRepositoryService();
//加载并实例化流程配置
Deployment deployment = repositoryService.createDeployment().name("请假申请流程")
.addClasspathResource("bnmp/myAction.bpmn20.xml")
.deploy();
System.out.println("流程id:"+deployment.getId());
System.out.println("流程名:"+deployment.getName());
System.out.println("流程key:"+deployment.getKey());
System.out.println("流程Category:"+deployment.getCategory());
}
这里在提交过后我们可以看到对应的流程提交记录(对应act_re_deployment表)
踩坑:这里可能会遇到如下
org.activiti.bpmn.exceptions.XMLException: 3 字节的 UTF-8 序列的字节 3 无效。
解决方案:
网上百度了很多,但是几乎没有一个成功,比如改编码格式啊,maven中加入什么配置啊,都没用。说个简单粗暴的,把我们画好的流程图myProccess.bpmn文件复制一个,然后修改为xml文件,打开将其乱码文字修改掉过后就好了。还有一个需要注意的是**.addClasspathResource**文件格式的解析,如果不规范则会导致解析失败
public static final String[] BPMN_RESOURCE_SUFFIXES = new String[]{"bpmn20.xml", "bpmn"};
3.启动一个流程
在提交流程申请过后,我们需要将这个流程启动。启动的方式有多种,可以更具id、key等,这里以key为例,其他详情大家可以查阅相关api
@org.junit.Test
public void start(){
//创建获取流程引擎
ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();
//获取运行器
RuntimeService runtimeService = processEngine.getRuntimeService();
//根据id启动流程 这个leaveProccess就是前面提交流程的key
ProcessInstance instance = runtimeService.startProcessInstanceByKey("leaveProccess");
System.out.println("流程定义id:"+instance.getProcessDefinitionId());
System.out.println("流程实例id:"+instance.getId());
System.out.println("当前活动id:"+instance.getActivityId());
}
控制台输出:
流程定义id:leaveProccess:2:27503
流程实例id:42501
当前活动id:null
在启动成功后,我们可以通过TaskService对启动的流程进行查看
@org.junit.Test
public void findPersonTaskList(){
//获取流程引擎
ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();
//获取taskService
TaskService taskService = processEngine.getTaskService();
//根据流程key 和 任务负责人 查询任务
List<Task> taskList = taskService.createTaskQuery()
.processDefinitionKey("leaveProccess")
.taskAssignee("zhangsan")
.list();
System.out.println(taskList);
for (Task t: taskList ) {
System.out.println("流程实例id:"+ t.getProcessDefinitionId());
System.out.println("任务id:"+ t.getId());
System.out.println("任务负责人:" + t.getAssignee());
System.out.println("任务名称:" + t.getName());
}
}
控制台输出:
流程实例id:leaveProccess:2:27503
任务id:42505
任务负责人:zhangsan
任务名称:创建请假流程
4.开始任务
流程启动后,每个节点会对应我们指定的操作者,然后对应的操作者可以对自己当前任务流程进行操作
//这里就是刚才的张三,我们通过指定流程任务的key和对应人,获取其任务id。调用taskService.complete()方法后相当于完成了当前节点的操作,节点按照顺序传达到下一个人。后面的审批操作同理。
@org.junit.Test
public void complete(){
ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();
TaskService taskService = processEngine.getTaskService();
Task t = taskService.createTaskQuery().processDefinitionKey("leaveProccess")
.taskAssignee("zhangsan").singleResult();
taskService.complete(t.getId());
System.out.println(t.getAssignee()+"已完成审批");
}
完成节点操作后,再次调用该方法会报出空指针方法,因为该用户已经完成了此节点操作,数据将同步到历史记录中。
java.lang.NullPointerException
at cn.fyyice.activiti.Test.complete(Test.java:74)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:498)
at org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:50)
at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:12)
at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:47)
at org.junit.internal.runners.statements.InvokeMethod.evaluate(InvokeMethod.java:17)
at org.junit.runners.ParentRunner.runLeaf(ParentRunner.java:325)
at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:78)
at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:57)
at org.junit.runners.ParentRunner$3.run(ParentRunner.java:290)
at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:71)
at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:288)
at org.junit.runners.ParentRunner.access$000(ParentRunner.java:58)
at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:268)
at org.junit.runners.ParentRunner.run(ParentRunner.java:363)
at org.junit.runner.JUnitCore.run(JUnitCore.java:137)
at com.intellij.junit4.JUnit4IdeaTestRunner.startRunnerWithArgs(JUnit4IdeaTestRunner.java:68)
at com.intellij.rt.execution.junit.IdeaTestRunner$Repeater.startRunnerWithArgs(IdeaTestRunner.java:47)
at com.intellij.rt.execution.junit.JUnitStarter.prepareStreamsAndStart(JUnitStarter.java:242)
at com.intellij.rt.execution.junit.JUnitStarter.main(JUnitStarter.java:70)