一、Activiti6简介
Activiti是由Alfresco软件在2010年5月17日发布的业务流程管理(BPM)框架,它是覆盖了业务流程管理,工作流,服务协作等领域的一个开源,灵活的,易扩展的可执行流程语言框架。
官网:https://www.activiti.org/before-you-start
二、Maven依赖
<!--activiti-->
<dependency>
<groupId>org.activiti</groupId>
<artifactId>activiti-spring-boot-starter-basic</artifactId>
<version>6.0.0</version>
</dependency>
<!--jdbc-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jdbc</artifactId>
</dependency>
<!-- mysql-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
三、添加Processes目录
SpringBoot集成activiti默认会从classpath下的processes目录下读取流程定义文件,所以需要在src/main/resources目录下添加processes目录,并在目录中创建流程文件,添加目录后,目录结构变为:
如果没有processes目录,则需要修改配置spring.activiti.process-definition-location-prefix,指定流程文件存放目录。
Spring集成Activiti6默认支持.bpmn20.xml和.bpmn格式的流程定义文件,修改支持的文件格式,通过配置spring.activiti.process-definition-location-suffixes修改:
server.port=8888
# 自动检查、部署流程定义文件
spring.activiti.check-process-definitions=true
# 流程定义文件存放目录
spring.activiti.process-definition-location-prefix=classpath:/processes
# 流程文件格式后缀 **.bpmn20.xml **.bpmn 自定义(**.bpmn.zip)
#spring.activiti.process-definition-location-suffixes=**.bpmn.zip
# JDBC
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
spring.datasource.url=jdbc:mysql://127.0.0.1:3306/activiti?useUnicode=true&characterEncoding=UTF-8&useSSL=false&autoReconnect=true&failOverReadOnly=false&serverTimezone=Asia/Shanghai&nullCatalogMeansCurrent=true
spring.datasource.username=root
spring.datasource.password=1234
# 数据库连接池
spring.datasource.type=com.zaxxer.hikari.HikariDataSource
# 最小空闲连接数量
spring.datasource.hikari.minimum-idle=5
# 连接池最大连接数,默认是10
spring.datasource.hikari.maximum-pool-size=15
#此属性控制从池返回的连接的默认自动提交行为,默认值:true
spring.datasource.hikari.auto-commit=true
spring.datasource.hikari.idle-timeout=30000
spring.datasource.hikari.pool-name=UserHikariCP
# 此属性控制池中连接的最长生命周期,值0表示无限生命周期,默认1800000即30分钟
spring.datasource.hikari.max-lifetime=1800000
#数据库连接超时时间,默认30秒,即30000
spring.datasource.hikari.connection-timeout=30000
#指定校验连接合法性执行的sql语句
spring.datasource.hikari.connection-test-query=SELECT 1
# 配置JPA,用于初始化数据结构
spring.jpa.properties.hibernate.hbm2ddl.auto=update
spring.jpa.show-sql=true
# 每次应用启动不检查Activiti数据表是否存在及版本号是否匹配,提升应用启动速度
spring.activiti.database-schema-update=false
spring.activiti.database-schema-update
配置项可以设置流程引擎启动和关闭时数据库执行的策略,可以选择四种模式
false:false为默认值,设置为该值后,Activiti在启动时,会对比数据库表中保存的版本,如果没有表或者版本不匹配时,将在启动时抛出异常。
true:设置为该值后,Activiti会对数据库中所有的表进行更新,如果表不存在,则Activiti会自动创建。
create-drop:Activiti启动时,会执行数据库表的创建操作,在Activiti关闭时,执行数据库表的删除操作。
drop-create:Activiti启动时,执行数据库表的删除操作在Activiti关闭时,会执行数据库表的创建操作。
spring.activiti.history-level 配置项
对于历史数据,保存到何种粒度,Activiti提供了history-level属性对其进行配置。history-level属性有点像log4j的日志输出级别,该属性有以下四个值:
none:不保存任何的历史数据,因此,在流程执行过程中,这是最高效的。
activity:级别高于none,保存流程实例与流程行为,其他数据不保存。
audit:除activity级别会保存的数据外,还会保存全部的流程任务及其属性。audit为history的默认值。
full:保存历史数据的最高级别,除了会保存audit级别的数据外,还会保存其他全部流程相关的细节数据,包括一些流程参数等。
四、数据库表
Activiti的后台是有数据库的支持,所有的表都以ACT_开头。
第二部分是表示表的用途的两个字母标识。用途也和服务的API对应。
act_re_: 'RE’表示repository。 这个前缀的表包含了流程定义和流程静态资源(图片,规则,等等)。
act_ru_: 'RU’表示runtime。 这些运行时的表,包含流程实例,任务,变量,异步任务,等运行中的数据。 Activiti只在流程实例执行过程中保存这些数据,在流程结束时就会删除这些记录。 这样运行时表可以一直很小速度很快。
act_id_: 'ID’表示identity。 这些表包含身份信息,比如用户,组等等。
act_hi_: 'HI’表示history。 这些表包含历史数据,比如历史流程实例,变量,任务等等。
act_ge_*: 通用数据,用于不同场景下,如存放资源文件。
资源库流程规则表
act_re_deployment 部署信息表
act_re_model 流程设计模型部署表
act_re_procdef 流程定义数据表
运行时数据库表
act_ru_execution 运行时流程执行实例表
act_ru_identitylink 运行时流程人员表,主要存储任务节点与参与者的相关信息
act_ru_task 运行时任务节点表
act_ru_variable 运行时流程变量数据表
历史数据库表
act_hi_actinst 历史节点表
act_hi_attachment 历史附件表
act_hi_comment 历史意见表
act_hi_identitylink 历史流程人员表
act_hi_detail 历史详情表,提供历史变量的查询
act_hi_procinst 历史流程实例表
act_hi_taskinst 历史任务实例表
act_hi_varinst 历史变量表
组织机构表
act_id_group 用户组信息表
act_id_info 用户扩展信息表
act_id_membership 用户与用户组对应信息表
act_id_user 用户信息表
这四张表很常见,基本的组织机构管理,关于用户认证方面建议还是自己开发一套,组件自带的功能太简单,使用中有很多需求难以满足
通用数据表
act_ge_bytearray 二进制数据表
act_ge_property 属性数据表存储整个流程引擎级别的数据,初始化表结构时,会默认插入三条记录
五、工作流常用service
RepositoryService
Activiti 中每一个不同版本的业务流程的定义都需要使用一些定义文件,部署文件和支持数据 ( 例如 BPMN2.0 XML 文件,表单定义文件,流程定义图像文件等 ),这些文件都存储在 Activiti 内建的 Repository 中。Repository Service 提供了对 repository 的存取服务。
RuntimeService
Activiti 中,每当一个流程定义被启动一次之后,都会生成一个相应的流程对象实例。Runtime Service 提供了启动流程、查询流程实例、设置获取流程实例变量等功能。此外它还提供了对流程部署,流程定义和流程实例的存取服务。
TaskService
在 Activiti 中业务流程定义中的每一个执行节点被称为一个 Task,对流程中的数据存取,状态变更等操作均需要在 Task 中完成。Task Service 提供了对用户 Task 和 Form相关的操作。它提供了运行时任务查询、领取、完成、删除以及变量设置等功能。
IdentityService
Activiti 中内置了用户以及组管理的功能,必须使用这些用户和组的信息才能获取到相应的 Task。Identity Service 提供了对 Activiti 系统中的用户和组的管理功能。
ManagementService
Management Service 提供了对 Activiti 流程引擎的管理和维护功能,这些功能不在工作流驱动的应用程序中使用,主要用于 Activiti 系统的日常维护。
HistoryService
History Service 用于获取正在运行或已经完成的流程实例的信息,与 Runtime Service 中获取的流程信息不同,历史信息包含已经持久化存储的永久信息,并已经被针对查询优化。
FormService
Activiti 中的流程和状态 Task 均可以关联业务相关的数据。通过使用 Form Service可以存取启动和完成任务所需的表单数据并且根据需要来渲染表单。
DynamicBpmnService
一个新增的服务,用于动态修改流程中的一些参数信息等,是引擎中的一个辅助的服务
FormRepositoryService
部署表单等
五、安装activiti
-
下载
https://www.activiti.org
文档地址:https://www.activiti.org/userguide/
-
布署
需要将以下war包,布署到tomcat中。
解压之后,将wars文件夹下的activiti-admin.war、activiti-app.war拷贝到tomcat的webapps下:
cp activiti-6.0.0/wars/activiti-admin.war apache-tomcat-8.0.36/webapps/
cp activiti-6.0.0/wars/activiti-app.war apache-tomcat-8.0.36/webapps/
通过tomcat的bin目录下startup.sh启动tomcat:
浏览器访问http://192.168.11.126:8080/activiti-app访问activiti,初始账号密码是admin/test
六、idea安装Activiti插件
-
安装插件
点击菜单【File】-->【Settings...】打开【Settings】窗口。
点击左侧【Plugins】按钮,在右侧输出"actiBPM",点击下面的【Search in repositories】链接会打开【Browse Repositories】窗口。
-
创建BPMN文件
-
查看流程图代码
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<definitions xmlns="http://www.omg.org/spec/BPMN/20100524/MODEL" 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" xmlns:tns="http://www.activiti.org/test" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" expressionLanguage="http://www.w3.org/1999/XPath" id="m1569472781925" name="" targetNamespace="http://www.activiti.org/test" typeLanguage="http://www.w3.org/2001/XMLSchema">
<process id="myProcess_1" isClosed="false" isExecutable="true" processType="None">
<startEvent id="_2" name="开始"/>
<endEvent id="_3" name="结束"/>
<userTask activiti:exclusive="true" id="_4" name="学生请假"/>
<userTask activiti:exclusive="true" id="_5" name="老师申批"/>
<sequenceFlow id="_6" sourceRef="_2" targetRef="_4"/>
<sequenceFlow id="_7" sourceRef="_4" targetRef="_5"/>
<sequenceFlow id="_8" sourceRef="_5" targetRef="_3"/>
</process>
<bpmndi:BPMNDiagram documentation="background=#3C3F41;count=1;horizontalcount=1;orientation=0;width=842.4;height=1195.2;imageableWidth=832.4;imageableHeight=1185.2;imageableX=5.0;imageableY=5.0" id="Diagram-_1" name="New Diagram">
<bpmndi:BPMNPlane bpmnElement="myProcess_1">
<bpmndi:BPMNShape bpmnElement="_2" id="Shape-_2">
<omgdc:Bounds height="32.0" width="32.0" x="190.0" y="55.0"/>
<bpmndi:BPMNLabel>
<omgdc:Bounds height="32.0" width="32.0" x="0.0" y="0.0"/>
</bpmndi:BPMNLabel>
</bpmndi:BPMNShape>
<bpmndi:BPMNShape bpmnElement="_3" id="Shape-_3">
<omgdc:Bounds height="32.0" width="32.0" x="195.0" y="335.0"/>
<bpmndi:BPMNLabel>
<omgdc:Bounds height="32.0" width="32.0" x="0.0" y="0.0"/>
</bpmndi:BPMNLabel>
</bpmndi:BPMNShape>
<bpmndi:BPMNShape bpmnElement="_4" id="Shape-_4">
<omgdc:Bounds height="55.0" width="85.0" x="160.0" y="140.0"/>
<bpmndi:BPMNLabel>
<omgdc:Bounds height="55.0" width="85.0" x="0.0" y="0.0"/>
</bpmndi:BPMNLabel>
</bpmndi:BPMNShape>
<bpmndi:BPMNShape bpmnElement="_5" id="Shape-_5">
<omgdc:Bounds height="55.0" width="85.0" x="165.0" y="225.0"/>
<bpmndi:BPMNLabel>
<omgdc:Bounds height="55.0" width="85.0" x="0.0" y="0.0"/>
</bpmndi:BPMNLabel>
</bpmndi:BPMNShape>
<bpmndi:BPMNEdge bpmnElement="_6" id="BPMNEdge__6" sourceElement="_2" targetElement="_4">
<omgdi:waypoint x="206.0" y="87.0"/>
<omgdi:waypoint x="206.0" y="140.0"/>
<bpmndi:BPMNLabel>
<omgdc:Bounds height="0.0" width="0.0" x="0.0" y="0.0"/>
</bpmndi:BPMNLabel>
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge bpmnElement="_7" id="BPMNEdge__7" sourceElement="_4" targetElement="_5">
<omgdi:waypoint x="205.0" y="195.0"/>
<omgdi:waypoint x="205.0" y="225.0"/>
<bpmndi:BPMNLabel>
<omgdc:Bounds height="0.0" width="0.0" x="0.0" y="0.0"/>
</bpmndi:BPMNLabel>
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge bpmnElement="_8" id="BPMNEdge__8" sourceElement="_5" targetElement="_3">
<omgdi:waypoint x="211.0" y="280.0"/>
<omgdi:waypoint x="211.0" y="335.0"/>
<bpmndi:BPMNLabel>
<omgdc:Bounds height="0.0" width="0.0" x="0.0" y="0.0"/>
</bpmndi:BPMNLabel>
</bpmndi:BPMNEdge>
</bpmndi:BPMNPlane>
</bpmndi:BPMNDiagram>
</definitions>
-
生成图片
-
部署流程
部署之后就可以在act_re_procdef表中看到对相应的流程信息
/**
* 数据存储服务
*/
@Resource
private RepositoryService repositoryService;
/**
* 1. 部署流程
* 部署之后就可以在act_re_procdef表中看到对相应的流程信息
*/
@Test
public void deployProcess() {
DeploymentBuilder builder = repositoryService.createDeployment();
// bpmn文件的名称
builder.addClasspathResource("processes/test.bpmn");
// 设置key
builder.key("myProcess_1");
// 设定名称,也可以在图中定义
builder.name("请假流程");
// 进行布署
Deployment deployment = builder.deploy();
// 获取deployment的ID
String deploymentId = deployment.getId();
// 根据deploymentId来获取流程定义对象
ProcessDefinition processDefinition = repositoryService.createProcessDefinitionQuery()
.deploymentId(deploymentId).singleResult();
log.info("流程定义文件 [{}] , 流程ID [{}]", processDefinition.getName(), processDefinition.getId());
}
注意:
默认,必须为.bpmn文件或 bpmn20.xml文件,可通过配置修改。
- 启动流程
/**
* 运行服务
*/
@Resource
private RuntimeService runtimeService;
/**
* 2. 启动流程
* 启动流程之后就会有相应的任务产生,存在act_ru_task表中,可以查看任务节点
*/
@Test
public void startProcess() {
// 流程的名称,也可以使用ByID来启动流程
// key为流程图的ID,
ProcessInstance processInstance = runtimeService.startProcessInstanceByKey("myProcess_1");
log.info("流程启动成功,流程id:" + processInstance.getId());
}
-
查看任务节点
/**
* 任务服务
*/
@Resource
private TaskService taskService;
/**
* 3. 查看任务节点
*/
@Test
public void queryTask() {
// 根据assignee(代理人)查询任务
String assignee = "admin";
// 注意所在包。
List<Task> tasks = taskService.createTaskQuery().taskAssignee(assignee).list();
int size = tasks.size();
for (int i = 0; i < size; i++) {
Task task = tasks.get(i);
}
// 首次运行的时候这个没有输出,因为第一次运行的时候扫描act_ru_task的表里面是空的,
// 但第一次运行完成之后里面会添加一条记录,之后每次运行里面都会添加一条记录
for (Task task : tasks) {
log.info("taskId:" + task.getId() +
",taskName:" + task.getName() +
",assignee:" + task.getAssignee() +
",createTime:" + task.getCreateTime());
}
}
-
完成流程(审核通过)
/**
* 4. 完成流程,admin通过审核
*/
@Test
public void completeTasks() {
// 审批后,任务列表数据减少
Map<String,Object> vars = new HashMap<>();
// 按配置的任务id填写
vars.put("_4","true");
taskService.complete("2505", vars);
}
再次进行查询admin的任务,已经有了。act_ru_task表中已没有该记录。
- 申请驳回
//审批不通过,结束流程
runtimeService.deleteProcessInstance(vacationAudit.getProcessInstanceId(), auditId);
- 转办
- 签收
- 历史任务查询
/**
* 历史服务
*/
@Resource
private HistoryService historyService;
/**
* 历史任务查询
*
* @throws ParseException
*/
@Test
public void findHistoricTasks() throws ParseException {
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
List<HistoricTaskInstance> list = historyService.createHistoricTaskInstanceQuery() // 创建历史任务实例查询
.taskAssignee("admin") // 指定办理人
// .finished() // 查询已经完成的任务
.list();
for (HistoricTaskInstance hti : list) {
log.info("任务ID:" + hti.getId());
log.info("流程实例ID:" + hti.getProcessInstanceId());
log.info("班里人:" + hti.getAssignee());
log.info("创建时间:" + sdf.format(hti.getCreateTime()));
log.info("结束时间:" + sdf.format(hti.getEndTime()));
log.info("===========================");
}
}
历史活动查询
/**
* 历史活动查询
* 指定流程实例id,启动流程时,获取的实例ID
*/
@Test
public void historyActInstanceList(){
List<HistoricActivityInstance> list = historyService // 历史任务Service
.createHistoricActivityInstanceQuery() // 创建历史活动实例查询
.processInstanceId("2501") // 指定流程实例id
// .finished() // 查询已经完成的任务
.list();
for (HistoricActivityInstance hai : list) {
log.info("任务ID:" + hai.getId());
log.info("流程实例ID:" + hai.getProcessInstanceId());
log.info("活动名称:" + hai.getActivityName());
log.info("办理人:" + hai.getAssignee());
log.info("开始时间:" + hai.getStartTime());
log.info("结束时间:" + hai.getEndTime());
log.info("===========================");
}
}
八、常见问题:
- caused by: java.lang.ArrayStoreException: sun.reflect.annotation.TypeNotPresentExceptionProxy
解决:
@SpringBootApplication(exclude = SecurityAutoConfiguration.class)
- 不能自动创建表结构
- 从mysql-connector-java 5.x 到 6.x,nullCatalogMeansCurrent属性由原来的默认true改为了false。
- true 使用指定的数据库进行查询。优先取当前传入的数据库名,其次取当前链接的数据库名。
将连接字符串追加:
nullCatalogMeansCurrent=true
jdbc:mysql://127.0.0.1:3306/activiti?useUnicode=true&characterEncoding=UTF-8&useSSL=false&autoReconnect=true&failOverReadOnly=false&serverTimezone=Asia/Shanghai&nullCatalogMeansCurrent=true
- idea activiti报错no processes deployed with key 'leave'
出错原因:processes目录下
leave.xml改为leave.bpmn问题解决
那就是 activiti 的模版必须以 bpmn20.xml 或者 bpmn结尾