Activiti7工作流知识点详解
当前市场中绝大部分公司都有自己特有的审批流程,那么为什么会出现繁琐的审批流程呢?这就涉及到了公司的管理模式,那么具体什么是工作流呢? 下面就是一种现在流行的工作流引擎 Activiti
概念:
工作流(Work flow)
: 对业务流程的自动化执行管理,为了完成某项业务或任务而需要进行的一系列有序的工作步骤和流程
Activiti就是一个工作流的引擎,用来管理工作流的框架,提供大量对工作流进行操作的API
没有工作流之前:
- 创建数据表
- 允许用户发起申请
- 审批过程中,更新状态,生成历史记录
- 结束申请
工作流的逻辑
- 发起申请流程
- 查看目前流程节点
- 查看审批记录
- 结束
什么是工作流引擎
就是一种按照规则(BPMN规范)进行部署.用于将业务和节点进行分离(特定情况下页可能是关联),以实现节点的自动流转的工作流框架
工作流引擎工作原理
- 将配置好的流程文件BPMN部署到工作流引擎中,会把节点路径存储到数据库中
- 工作流引擎提供了大量的API对流程进行查询处理,细节都是对应用程序屏蔽的,大大提供开发效率
- 业务逻辑的处理和流程的流转是分离的,是通过BusinessKey进行关联的.
什么是BPMN
BPMN(Business Process Model And Notation)业务流程模型和符号, 建模语言-----业务流程图----描述流程怎么走,本质是一个XML文件
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-adFTlD2A-1689668312677)(F:\WolfCode\projects\Activiti-\src\main\resources\bpmn\leave.png)]
核心机制:
- 业务流程图要规范化,需要遵守一套标准。
- 业务流程图本质上就是一个XML文件,而XML可以存放所要的数据。
- 读取业务流程图的过程就是解析XML文件的过程。
- 读取一个业务流程图的结点就相当于解析一个XML的结点,进一步将数据插入到MySQL表中,形成一条记录。
- 将一个业务流程图的所有节点都读取并存入到MySQL表中。
- 后面只要读取MySQL表中的记录就相当于读取业务流程图的一个节点。
- 业务流程的推进,后面就转换为读取表中的数据,并且处理数据,结束的时候这一行数据就可以删除了(历史不能删除)。
使用Avtiviti的步骤:
- 整合Activiti 将Activiti和业务系统的环境整合到一起(在Maven项目中导入对应的依赖)
- 业务流程建模 使用IDEA中的插件定义业务流程文件-------创建流程图文件(.bpmn)
- 部署业务流程(创建类) 向Activiti中部署流程定义文件(.bpmn)
- 启动流程实例(对象)
- 查询代办任务 业务流程已经交给Activiti管理,所以可以查询数据
- 处理代办任务
- 结束流程(删除该删的数据) 历史记录不会删除
Activiti7以及Spring项目的集成
添加依赖
<properties>
<slf4j.version>1.6.6</slf4j.version>
<log4j.version>1.2.12</log4j.version>
<activiti.version>7.0.0.SR1</activiti.version>
</properties>
<dependencies>
<!-- activiti引擎 -->
<dependency>
<groupId>org.activiti</groupId>
<artifactId>activiti-engine</artifactId>
<version>${activiti.version}</version>
</dependency>
<!-- 整合Spring -->
<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驱动 -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.40</version>
</dependency>
<!-- mybatis -->
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.4.5</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>
<!-- log end -->
<dependency>
<groupId>commons-io</groupId>
<artifactId>commons-io</artifactId>
<version>2.6</version>
</dependency>
<!--数据库连接池-->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.2.4</version>
</dependency>
</dependencies>
配置log4j日志文件
# 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=./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.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="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
<property name="driverClassName" value="com.mysql.jdbc.Driver" />
<property name="url" value="jdbc:mysql:///activiti?useSSL=false" />
<property name="username" value="root" />
<property name="password" value="123456" />
</bean>
<!-- 默认id对应的值 为processEngineConfiguration -->
<bean id="processEngineConfiguration" class="org.activiti.engine.impl.cfg.StandaloneProcessEngineConfiguration">
<property name="dataSource" ref="dataSource"/>
<!--
activiti数据库表处理策略
false(默认值):检查数据库的版本和依赖库的版本,如果不匹配就抛出异常
true:构建流程引擎时,执行检查,如果需要就执行更新。如果表不存在,就创建。
create-drop:构建流程引擎时创建数据库报表,关闭流程引擎时就删除这些表。
drop-create:先删除表再创建表。
create:构建流程引擎时创建数据库表,关闭流程引擎时不删除这些表
-->
<property name="databaseSchemaUpdate" value="true"/>
</bean>
</beans>
流程引擎API
Service总览
Service接口 | 说明 |
---|---|
RepositoryService | Activiti的资源管理接口 |
RuntimeService | Activiti的流程运行管理接口 |
TaskService | Activiti的任务管理接口 |
HistoryService | Activiti的历史管理接口 |
ManagementService | Activiti的引擎管理接口 |
-
RepositoryService,是Activiti的资源管理接口,提供了管理和控制流程发布包和流程定义的操作。使用工作流建模工具设计的业务流程图需要使用此Service将流程定义文件的内容部署到计算机中。
-
RuntimeService,是Activiti的流程运行管理接口,可以从这个接口中获取很多关于流程执行相关的信息。
-
TaskService,是Activiti的任务管理接口,可以从这个接口中获取任务的信息。
-
HistoryService,是Activiti的历史管理类,可以查询历史信息,执行流程时,引擎会包含很多数据(根据配置),比如流程实例启动时间,任务的参与者,完成任务的时间,每个流程实例的执行路径,等等。
-
ManagementService,是Activiti的引擎管理接口,提供了对Activiti流程引擎的管理和维护功能,这些功能不在工作流驱动的应用程序中使用,主要用于Activiti系统的日常维护。
Activiti7 Demo
部署流程定义
- 使用RepositoryService部署流程定义
观察日志,部署流程定义会操作的表
- ACT_GE_PROPERTY 引擎属性表
- ACT_RE_PROCDEF 流程定义表
- ACT_RE_DEPLOYMENT 流程部署表
- ACT_GE_BYTEARRAY 二进制资源表
启动流程实例
- 流程定义部署在Activiti中之后就可以通过工作流管理业务流程了。
- 针对该流程,启动一个流程表示发起一个新的请假申请单,这就相当于Java类和Java对象的关系,类定义好之后需要new创建一个对象使用,当然,也可以new多个对象。
- 对于请假申请流程,张三发起一个请假申请单需要启动一个流程实例,李四发起一个请求申请单也需要启动一个流程实例。
观察日志,启动流程实例会操作的表
- ACT_HI_TASKINST 历史任务表
- ACT_HI_PROCINST 历史流程实例表
- ACT_HI_ACTINST 历史活动信息表
- ACT_HI_IDENTITYLINK 历史身份连接表
- ACT_RU_EXECUTION 运行时执行实例表
- ACT_RU_TASK 运行时任务表
- ACT_RU_IDENTITYLINK 运行时身份连接表
任务查询
- 流程启动后,各个任务的负责人就可以查询自己当前需要处理的任务,查询出来的任务都是该用户的待办任务。
观察日志发现,查询任务会操作如下表.
- ACT_RU_TASK 运行时任务表
- ACT_RE_PROCDEF 流程定义表
任务处理
- 任务负责人查询待办任务,选择任务进行处理,完成任务
观察日志发现,查询任务会操作如下表.
- ACT_GE_PROPERTY 引擎属性表
- ACT_HI_TASKINST 历史任务表
- ACT_HI_ACTINST 历史活动信息表
- ACT_HI_IDENTITYLINK 历史身份连接表
- ACT_RU_TASK 运行时任务表
- ACT_RU_IDENTITYLINK 运行时身份连接表
- ACT_RU_EXECUTION 运行时执行实例表
- ACT_HI_TASKINST 历史任务表
添加审批意见
- 在执行任务之前可以给该任务添加审批意见,会存储在历史表中,我们后续可以审批历史中查看到该意见
观察日志发现,其余操作和任务处理的表是一致的
查看历史审批
- 用户可以查看历史审批记录
出现次数较多的表
表名 |
---|
ACT_HI_TASKINST 历史任务表 |
ACT_RU_TASK 运行时任务表 |
ACT_RE_PROCDEF 流程定义表 |
ACT_RE_DEPLOYMENT 流程部署表 |
ACT_HI_ACTINST 历史活动信息表 |
act_hi_comment 审批记录表 |
ACT_RU_EXECUTION 运行时执行实例表 |
Activiti7进阶
流程定义相关
流程定义查询
- 查询流程相关信息,包含流程定义,流程部署,流程定义版本
@Test
public void testDefinitionQuery(){
//创建ProcessEngine对象
ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();
//获取仓库服务
RepositoryService repositoryService = processEngine.getRepositoryService();
//获取流程定义集合
List<ProcessDefinition> processDefinitionList = repositoryService
.createProcessDefinitionQuery()
.processDefinitionKey("leaveProcess")
.list();
//遍历集合
for (ProcessDefinition definition:processDefinitionList){
System.out.println("流程定义ID:"+definition.getId());
System.out.println("流程定义名称:"+definition.getName());
System.out.println("流程定义key:"+definition.getKey());
System.out.println("流程定义版本:"+definition.getVersion());
System.out.println("流程部署ID:"+definition.getDeploymentId());
System.out.println("====================");
}
}
流程资源下载
-
现在我们的流程资源文件已经上传到数据库了,如果其他用户想要查看这些资源文件,可以从数据库中把资源文件下载到本地。
@Test public void testDownloadResource() throws Exception { //创建ProcessEngine对象 ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine(); //获取仓库服务 RepositoryService repositoryService = processEngine.getRepositoryService(); //获取流程定义集合 List<ProcessDefinition> list = repositoryService .createProcessDefinitionQuery() .processDefinitionKey("leaveProcess") .orderByProcessDefinitionVersion()//按照版本排序 .desc()//降序 .list(); //获取最新那个 ProcessDefinition definition =list.get(0); //获取部署ID String deploymentId = definition.getDeploymentId(); //获取bpmn的输入流 InputStream bpmnInput = repositoryService.getResourceAsStream( deploymentId, definition.getResourceName()); //获取png的输入流 InputStream pngInput = repositoryService.getResourceAsStream( deploymentId, definition.getDiagramResourceName()); //设置bpmn输入 FileOutputStream bpmnOutPut = new FileOutputStream("D:/leave.bpmn"); //设置png输入 FileOutputStream pngOutPut = new FileOutputStream("D:/leave.png"); IOUtils.copy(bpmnInput,bpmnOutPut); IOUtils.copy(pngInput,pngOutPut); }
流程定义删除
- 根据部署Id删除对应的流程定义
@Test
public void testDeleteDeploy(){
//流程部署Id
String deploymentId = "10001";
//创建ProcessEngine对象
ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();
//获取仓库服务
RepositoryService repositoryService = processEngine.getRepositoryService();
//删除流程定义,如果该流程定义已有流程实例启动则删除时出错
repositoryService.deleteDeployment(deploymentId);
//设置true 级联删除流程定义,即使该流程有流程实例启动也可以删除,设置为false非级别删除方式,如果流程
//repositoryService.deleteDeployment(deploymentId,true);
}
说明:
1) 如果该流程定义下没有正在运行的流程,则可以用普通删除。
2) 如果该流程定义下存在已经运行的流程,使用普通删除报错,可用级联删除方法将流程及相关记录全部删除。
3) 项目开发中级联删除操作一般只开放给超级管理员使用.
流程实例相关
什么是流程实例?
用户根据流程定义发起一个流程的时候就是开启一个流程实例
一个流程定义可以开启多个流程实例
BusinessKey(业务标识)
-
启动流程实例的时候指定的businessKey会在对应的act_ru_execution表中存储businesskey
-
BusinessKey,业务表的主键,和流程实例的一一对应
@Test public void testGetBusinessKey(){ //任务负责人 String assignee = "李四"; //创建ProcessEngine对象 ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine(); //获取TaskService TaskService taskService = processEngine.getTaskService(); //获取RuntimeService RuntimeService runtimeService = processEngine.getRuntimeService(); //获取任务集合 List<Task> taskList = taskService.createTaskQuery() .processDefinitionKey("leaveProcess") .taskAssignee(assignee) .list(); //遍历任务列表 for(Task task:taskList){ System.out.println("流程定义id = " + task.getProcessDefinitionId()); System.out.println("流程实例id = " + task.getProcessInstanceId()); System.out.println("任务id = " + task.getId()); System.out.println("任务名称 = " + task.getName()); //根据任务上的流程实例Id查询出对应的流程实例对象,从流程实例对象中获取BusinessKey ProcessInstance instance = runtimeService .createProcessInstanceQuery() .processInstanceId(task.getProcessInstanceId()) .singleResult(); System.out.println("业务key:"+instance.getBusinessKey()); System.out.println("==================="); } }
流程实例的挂起/激活
挂起:
就是指暂停当前流程实例的下的所有操作,不能进行后续操作
操作流程定义为(完全)挂起状态之后该操作定义下的所有流程实例全部暂停,也无法开启新的流程实例
操作流程实例为挂起状态后该流程实例的所有操作暂停
激活:
将挂起状态的流程定义或者流程实例的状态改成激活,也就是可以进行后续操作
任务分配负责人
固定分配
在业务流程建模(bpmn)的时候直接指定对应的任务负责人
UEL表达式分配
Activiti 使用 UEL 表达式, UEL 是 java EE6 规范的一部分, UEL(Unified Expression Language)即 统一表达式语言。
在建模的时候直接指定表达式
监听器分配
- 任务监听器是发生对应的任务相关事件时执行自定义的Java逻辑或表达式。
- 任务相关事件包括:
- Event:
- Create:任务创建后触发。
- Assignment:任务分配后触发。
- Delete:任务完成后触发。
- All:所有事件发生都触发。
- Event:
-
自定义一个任务监听器类,然后此类必须实现org.activiti.engine.delegate.TaskListener接口
package cn.wolfcode; import org.activiti.engine.delegate.DelegateTask; import org.activiti.engine.delegate.TaskListener; /** * Created by wolfcode */ public class AssigneeTaskListener implements TaskListener { public void notify(DelegateTask delegateTask) { if(delegateTask.getName().equals("部门经理审批")){ delegateTask.setAssignee("赵六"); }else if(delegateTask.getName().equals("部门经理审批")){ delegateTask.setAssignee("孙七"); } } }
-
在bpmn文件中配置监听器
- 在实际开发中,一般也不使用监听器分配方式,太麻烦了
流程变量
什么是流程变量?
- 流程变量在Activiti中是一个非常重要的角色,流程运转有时需要靠流程变量,业务系统和Activiti结合时少不了流程变量,流程变量就是Activiti在管理工作流时根据管理需要而设置的变量。
- 比如在请假流程流转时如果请假天数>3天则有总经理审批,否则由人事直接审批,请假天数就可以设置流程变量,在流程流转时使用。
注意:虽然流程变量中可以存储业务数据,可以通过Activiti的API查询流程变量从而实现查询业务数据,但是不建议这么使用,因为业务数据查询由业务系统负责,Activiti设置流程变量是为了流程执行需要而创建的。
流程变量的类型
同基本数据类型类似
多了date,binary,serializable
如果要将实体类型存储到流程变量中,该类必须实现序列化接口(Serializable)
流程变量的作用域
流程变量的作用域范围可以是一个流程实例,一个任务或者一个执行实例
- global 变量: 流程变量的作用域范围的默认值是流程实例,作用域范围最大。
- local 变量: 流程变量的作用域范围如果仅仅针对一个任务或一个执行实例,那么作用域范围没有流程实例大-----------------一般不用
流程变量的使用方法
-
在属性上使用UEL表达式
可以在 assignee 处设置 UEL 表达式,表达式的值为任务的负责人,比如: ${assignee}, assignee 就是一个流程变量名称。
Activiti获取UEL表达式的值,即流程变量assignee的值 ,将assignee的值作为任务的负责人进行任务分配
-
在连线上使用UEL表达式
可以在连线上设置UEL表达式,决定流程走向。
比如:${price<10000} 。price就是一个流程变量名称,uel表达式结果类型为布尔类型。
如果UEL表达式是true,要决定 流程执行走向。
使用global变量控制流程
在连线上添加UEL表达式,并设置条件
注意:
- 如果UEL表达式中流程变量名不存在则报错。
- 如果如果UEL表达式都不符合条件,流程报错。
- 如果连接不设置条件/条件都满足,每个连线都会走.
任务候选人
在流程定义的任务节点中的assignee设置固定的任务负责人,也可以在candidate-users中设置候选人名称(多个)
领取任务:
taskService.claim(taskId,"xxx");
其他操作同之前流程实例的操作步骤
网关
排他网关
用于在流程中决定下一个是谁来执行操作
当流程执行到这个网关的时候判断所有分支的条件,满足条件的分支执行
当两个或多个分支条件都满足时也只会执行其中一条分支执行(默认会找序号小的分支执行)
并行网关
可以把流程分成多条分支,也可以把多条分支汇合到一起
并行网关不会解析条件,即使顺序流中定义了条件也会被忽略
包含网关
排他网关和并行网关的结合体
如:${price<10000} 。price就是一个流程变量名称,uel表达式结果类型为布尔类型。
如果UEL表达式是true,要决定 流程执行走向。
使用global变量控制流程
在连线上添加UEL表达式,并设置条件
注意:
- 如果UEL表达式中流程变量名不存在则报错。
- 如果如果UEL表达式都不符合条件,流程报错。
- 如果连接不设置条件/条件都满足,每个连线都会走.
任务候选人
在流程定义的任务节点中的assignee设置固定的任务负责人,也可以在candidate-users中设置候选人名称(多个)
领取任务:
taskService.claim(taskId,"xxx");
其他操作同之前流程实例的操作步骤
网关
排他网关
用于在流程中决定下一个是谁来执行操作
当流程执行到这个网关的时候判断所有分支的条件,满足条件的分支执行
当两个或多个分支条件都满足时也只会执行其中一条分支执行(默认会找序号小的分支执行)
并行网关
可以把流程分成多条分支,也可以把多条分支汇合到一起
并行网关不会解析条件,即使顺序流中定义了条件也会被忽略
包含网关
排他网关和并行网关的结合体