框架,将业务数据和节点的流程进行分离【特定形式进行关联】,实现节点的自动流转的工作流框架.,帮助我们实现流程自动化。
使用情景:也称为工作流请假审批:填写申请–>主管部门–>HR审批
报销审批:填写申请–>经理批准–>总经理–>财务批准
虽然不用框架也能实现该业务需求,流程变得复杂,需要改动时,就需要耗费大量的时间成本
Activiti7框架就是这样开源出来的一套框架,可以做到流程与代码分离,帮助我们实现
一、Activiti7的内部核心机制- 业务流程图需要符合规范
- 业务流程图的本质就是xml文件,里面的数据就是一个个的节点
- 解析业务流程图的过程就是解析xml文件,读取数据的过程
- 读取节点就是把数据存到数据库中,形成一条条的记录
- 后续我们只要对表进行查询就能知道流程走到哪一步 ru_task
- 任务完成提交就是更改数据库中表的记录,再读取下一个节点的数据
- 流程接受后,更改表的记录
制定了一套规范,也就是业务流程图
三、使用Activiti 1. 流程:- 业务流程建模,画业务流程图
- 部署业务流程,定义业务流程,可以理解为定义一个类,关联业务流程图 …bpmn
- 启动业务流程,流程实例,可以理解为创建对象
- 查询任务
- 执行任务
- 结束流程,如果没有下一个结点,就是任务完成
- jdk1.8或以上
- mysql5或以上
- Tomcat8.5
- IDEA
- Activiti 7.0.0.SR1
SE环境,基于maven项目
1. 添加依赖
1.6.6
1.2.12
7.0.0.SR1
org.activiti
activiti-engine
${activiti.version}
org.activiti
activiti-spring
${activiti.version}
org.activiti
activiti-bpmn-model
${activiti.version}
org.activiti
activiti-bpmn-converter
${activiti.version}
org.activiti
activiti-json-converter
${activiti.version}
org.activiti
activiti-bpmn-layout
${activiti.version}
mysql
mysql-connector-java
5.1.40
org.mybatis
mybatis
3.4.5
commons-dbcp
commons-dbcp
1.4
junit
junit
4.12
log4j
log4j
${log4j.version}
org.slf4j
slf4j-api
${slf4j.version}
org.slf4j
slf4j-log4j12
${slf4j.version}
commons-io
commons-io
2.6
com.alibaba
druid
1.2.4
2. 添加日志文件
在resource文件夹中创建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 - %mn
# 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 - %mn
3.添加核心配置文件
在resource文件夹中创建名为 activiti.cfg.xml 的文件,文件名是固定的,不按规则命名会报错
五、数据库表的说明 1. 介绍
Activiti是通过操作数据库中的表单来实现工作流的。不同的环境中,表的数量也是不一样的。
此环境中有25张表。
- 每一种表都对应着一个Service
- 命名规则
- "act_re"开头的表,re表示 Repository,与流程定义有关
- "act_ru"开头的表,,ru表示RunTime,与流程实例,任务处理有关,只保存正在执行中的任务的数据
- "act_hi"开头的表,,hi表示History,记录流程,变量,任务等的历史信息
- "act_ge"开头的表,ge表示General,记录通用数据
-
流程定义—>RepositoryService
-
act_ge_bytearray:用于存储bpmn的数据
-
act_ge_property:用于生产主键等,是内部表,我们一般不需要关注
-
act_re_deployment:部署信息表
-
act_re_procdef:流程定义表
-
-
流程实例—>RunTimeService
- act_ge_property:主键更新
- act_hi_actinst:历史节点表,记录所有开始,任务,结束节点的信息
- act_hi_identitylink:历史身份表,记录所有用户的信息
- act_hi_procinst:历史流程表:记录所有流程实例的信息
- act_hi_taskinst:历史任务表:记录所有任务对象的信息
- act_ru_execution:运行时流程执行实例表
- act_ru_identitylink:运行时用户信息表,
- act_ru_task:运行时任务信息表
-
执行任务—>taskService
- act_hi_actinst:历史节点表,记录所有开始,任务,结束节点的信息
- act_hi_identitylink:历史身份表,记录所有用户的信息
- act_hi_taskinst:历史任务表:记录所有任务对象的信息
- act_ru_execution:运行时流程执行实例表
- act_ru_identitylink:运行时用户信息表,
- act_ru_task:运行时任务信息表
| 接口 | 作用 |
|---|---|
| RepositoryService | 进行资源管理,流程定义 |
| RunTImeService | 进行流程实例 |
| TaskService | 进行任务管理 |
| HistoryService | 进行历史管理 |
| ManagementService | 进行引擎管理 |
bpmn的id和Name是自定义的
给任务节点设置名字,给上执行人,后续执行人可以使用UEL表达式,监听器来设置,也可以通过候选人方式设置。
2. 流程定义@Test
public void test1() {
//1 获取流程引擎对象
ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();
//2 获取仓库Service, RespositoryService
processEngine.getRepositoryService()
.createDeployment()
.addClasspathResource("bpmn/act3.bpmn")
.deploy();
//一个deploye对应一个流程定义
}
3.流程实例
@Test
public void test2() {
processEngine.getRuntimeService()
.startProcessInstanceByKey("bpmn图中id");//自定义的流程名称
}
4.执行任务
@Test
public void test3(){
ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();
TaskService taskService = processEngine.getTaskService();
taskService
.createTaskQuery()
.processDefinitionKey("leaveProcess")//查询条件
.taskAssignee("张三")//查询条件
.list().forEach(task -> {
taskService.addComment(task.getId(), task.getProcessInstanceId(), "不干了");
taskService.complete(task.getId());
});
}
5.查看历史情况
@Test
public void test10() {
ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();
HistoryService historyService = processEngine.getHistoryService();
historyService.createHistoricProcessInstanceQuery()
.processDefinitionKey("leaveProcess")
.list()
.forEach(historicProcessInstance -> {
System.out.println(historicProcessInstance.getId());
});
}
八、Activiti7的进阶
一、流程定义相关
- 查询流程相关信息,包含流程定义,流程部署,流程定义版本
@Test
public void testDefinitionQuery(){
//创建ProcessEngine对象
ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();
//获取仓库服务
RepositoryService repositoryService = processEngine.getRepositoryService();
//获取流程定义集合
List 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("====================");
}
}
2.现在我们的流程资源文件已经上传到数据库了,如果其他用户想要查看这些资源文件,可以从数据库中把资源文件下载到本地。
@Test
public void testDownloadResource() throws Exception {
//创建ProcessEngine对象
ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();
//获取仓库服务
RepositoryService repositoryService = processEngine.getRepositoryService();
//获取流程定义集合
List 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);
}
3.根据部署Id删除对应的流程定义
如果流程定义 没有流程实例可以直接删除
如果流程定义 已近有流程实例,可以通过一个参数 true 进行级联删除
@Test
public void test3(){
ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();
processEngine.getRepositoryService().deleteDeployment("5001",true);
}
二、流程实例相关
框架,将业务数据和节点的流程进行分离【特定形式进行关联】,特定形式指的就是businessKey
businessKey
搞清楚businessKey 作用?
把我们的业务数据和流程绑定起来 , 因为在审核的时候 , 审核人妖看到用户申请信息.
步骤
1 用户在申请的时候填写信息 ,最终把信息保存到业务表中
2 先画出bpmn流程 ,把流程图 部署到activiti当中
3 小陈发起申请, 在activiti 开启流程实例 , 在启动流程时候就会把businessKey 和流程实例绑定起来
4 任务走了部门经理节点 , 这里是一个任务, 我们是可以通过任务拿到流程实例id
5 通过流程实例id拿到流程实例 , 就可以通过流程实例那businessKey
6 根据businessKey 上业务表中取查询数据 , 审核人在根据查询的数据看是否通过
public class Demo {
//流程定义
@Test
public void test1() {
ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();
processEngine.getRepositoryService()
.createDeployment()
.addClasspathResource("bpmn/act3.bpmn")
.deploy();
}
//流程实例+添加businessKey
@Test
public void test2() {
ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();
processEngine.getRuntimeService()
.startProcessInstanceByKey("act3", "2887");
}
//在任务中获取实例id,通过实例id获取实例对象,通过实例对象获取businessKey
@Test
public void test3() {
ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();
String processInstanceId = processEngine.getTaskService()
.createTaskQuery()
.singleResult()
.getProcessInstanceId();
String businessKey = processEngine
.getRuntimeService()
.createProcessInstanceQuery()
.processInstanceId(processInstanceId)
.singleResult()
.getBusinessKey();
System.out.println("businessKey = " + businessKey);
}
}
三、流程定义/实例挂起/激活
例如公司制度改变过程中的流程, 总经理更换过程中的流程,有100个人的流程, 70个人已经完成,30个人流程正好在总经理更换中,就需要挂起.
- 比如我们的业务流程为:
【开始节点】–>【A节点】–>【B节点】–>【C节点】–>【结束节点】
【C节点】的业务逻辑需要和外部接口交互,刚好外部接口出问题了,如果剩下的流程都走到【C节点】,执行【C节点】的业务逻辑,那都会报错,我们就可以把流程挂起,等待外部接口可用之后再重新激活流程.
- 业务流程发生改变,已经发起的流程实例继续按照旧的流程走,如果新发起的流程就按照新的业务流程走.这时候我们就需要挂起流程定义,但是不挂起流程实例.
- 操作流程定义为挂起状态,该操作定义下面的所有的流程实例将全部暂停。
- 流程定义为挂起状态,该流程定义下将不允许启动新的流程实例,同时该流程定义下的所有流程实例将全部挂起暂停执行
public class Demo {
//定义流程
@Test
public void test1() {
ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();
processEngine.getRepositoryService()
.createDeployment()
.addClasspathResource("bpmn/act4.bpmn")
.deploy();
}
//流程实例
@Test
public void test2() {
ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();
processEngine.getRuntimeService()
.startProcessInstanceByKey("act4");
}
//进行流程挂起
@Test
public void test3() {
ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();
ProcessDefinition processDefinition = processEngine.getRepositoryService().
createProcessDefinitionQuery()
.singleResult();
processEngine.getRepositoryService().suspendProcessDefinitionById(processDefinition.getId());
}
//查询流程挂起状态
@Test
public void test4() {
ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();
System.out.println(processEngine.getRepositoryService()
.createProcessDefinitionQuery()
.singleResult().isSuspended());//true
}
//可以完成未完成的任务
@Test
public void test5() {
ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();
Task task = processEngine.getTaskService()
.createTaskQuery()
.singleResult();
processEngine.getTaskService().complete(task.getId());
}
//但不能再流程实例
@Test
public void test6() {
ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();
processEngine.getRuntimeService()
.startProcessInstanceByKey("act4");
}
//重新激活流程定义+测试+执行新实例任务
@Test
public void test7() {
ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();
RepositoryService repositoryService = processEngine.getRepositoryService();
ProcessDefinition processDefinition = repositoryService
.createProcessDefinitionQuery()
.singleResult();
if (processDefinition.isSuspended()) {
System.out.println("to true");
repositoryService.activateProcessDefinitionById(processDefinition.getId());
System.out.println("流程实例");
processEngine.getRuntimeService()
.startProcessInstanceByKey("act4");
System.out.println("执行任务");
TaskService taskService = processEngine.getTaskService();
Task task = taskService.createTaskQuery().taskAssignee("张三").singleResult();
taskService.complete(task.getId());
}
}
//挂起后不能执行任务和进行流程定义
@Test
public void test8() {
ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();
RepositoryService repositoryService = processEngine.getRepositoryService();
ProcessDefinition processDefinition = repositoryService
.createProcessDefinitionQuery()
.singleResult();
repositoryService
.suspendProcessDefinitionById(processDefinition.getId(), true, null);
}
//不能进行流程定义
@Test
public void test9(){
ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();
processEngine.getRuntimeService()
.startProcessInstanceByKey("act4");
}
//不能执行任务
@Test
public void test10(){
ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();
processEngine.getTaskService().complete("10005");
}
//挂起实例
@Test
public void test11(){
ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();
//把原来的挂起的流程定义激活
RepositoryService repositoryService = processEngine.getRepositoryService();
ProcessDefinition processDefinition = repositoryService
.createProcessDefinitionQuery()
.singleResult();
//挂起实例
RuntimeService runtimeService = processEngine.getRuntimeService();
runtimeService.suspendProcessInstanceById("10001");
//检测是否挂起,若已近挂起,就重新激活,并执行任务
TaskService taskService = processEngine.getTaskService();
Task task = taskService.createTaskQuery()
.taskAssignee("张三")
.singleResult();
//获取实例id
String processInstanceId = task.getProcessInstanceId();
//判断是否已近挂起
boolean suspended = runtimeService.createProcessInstanceQuery()
.processInstanceId(processInstanceId)
.singleResult()
.isSuspended();
if (suspended) {
System.out.println("======================");
System.out.println("激活");
runtimeService.activateProcessInstanceById(processInstanceId);
taskService.complete(task.getId());
}
}
}
三、任务分配
指的就是指定处理任务的人,分配方法有三种
1.固定分配 直接把名字写死,在实际开发过程中,此方法不适应。但在测试时使用比其他的方法都方便。
只要在任务结点中的 Assignee属性中设置值
-
使用EUL表达式,是一种占位符,在任务结点中的 Assignee属性中设置${value}。需要把处理人添加到流程实例,就要在流程实例时,添加参数 ,以map的方式添加。
public class UEL { //流程定义 @Test public void test1() { ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine(); processEngine.getRepositoryService() .createDeployment() .addClasspathResource("bpmn/act5.bpmn") .deploy(); } //实例部署 @Test public void test2() { ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine(); Mapmap = new HashMap<>(); map.put("s1", "张三"); map.put("s2", "lisi"); processEngine.getRuntimeService() .startProcessInstanceByKey("act5", map); } //查询候选人 @Test public void test3() { ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine(); Task lisi = processEngine.getTaskService() .createTaskQuery() .taskCandidateOrAssigned("lisi").singleResult(); } //领任务 @Test public void test4(){ ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine(); processEngine.getTaskService().claim("17507","lisi"); } //执行任务 @Test public void test5(){ ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine(); processEngine.getTaskService().complete("17507"); } } -
监听器
任务监听器是发生对应的任务相关事件时执行自定义的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; public class AssigneeTaskListener implements TaskListener { public void notify(DelegateTask delegateTask) { if(delegateTask.getName().equals("部门经理审批")){ delegateTask.setAssignee("赵六"); }else if(delegateTask.getName().equals("部门经理审批")){ delegateTask.setAssignee("孙七"); } } } -
在bpmn文件中配置监听器
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-uzt3J72y-1653831176864)(E:/CodeWolf/20220215/阶段四/07 Activit/讲义/图片/image-20210603114503828.png)]
在实际开发中,一般也不使用监听器分配方式,太麻烦了。
-
在流程中,可以通过变量来控制流程的走向,从而增强业务能力。
- 局部变量(少用)
- 全局变量(多用)
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-GXacHjjK-1653831176865)(C:UsersSKYAppDataRoamingTyporatypora-user-imagesimage-20220529172114148.png)]
3. 使用方法 1. 在流程实例时,添加一个map为参数,Map五、任务候选人,String为变量名,Object为值 4. 特殊情况 1. 当两个情况都满足,在不添加其他操作时,两个任务都会被触发 2. 当两个情况都不满足时,会抛出异常,说找不到出口
1. 在实际运用的过程中,任务的执行人不是固定的,而是从几个人中,拿一个人出来执行便可 2. 在Condidata中,通过添加候选人,可以添加多个,中间使用逗号隔开 3. 在流程实例后,候选人领取任务前,该任务是没有执行人的 4. 候选人执行任务 1. 要使用TaskServicet提供的 claim(String taskId,String userId),进行领取任务 2. 使用 taskSerice.commit(Strinig taskId)执行任务六、网关
-
排他网关
- 当流程走到这个网关是,所有的分支都会进行判断,表达式为true的分支就是流程走向
- 如果分支都为true,就会选择id小的分支
- 当分支都为false时,会报错,说流程找不到出口
-
并行网关
- 需要两个网关,把所有分支都夹起来
- 只有里面的分支全都走完,流程才能到下一个结点
-
包含网关
-
可以看成是排他网关与并行网关的集合体
-
需要两个网关,把所有分支都夹起来
-
使用场景:请假规则
- 请假<3天的副经理批准
- 请假>3天的总经理批准
- 请假无论多少天都需要人事部门批准
-
候选人,需要先使用claim进行领取任务,在执行任务
g为变量名,Object为值
4. 特殊情况
1. 当两个情况都满足,在不添加其他操作时,两个任务都会被触发
2. 当两个情况都不满足时,会抛出异常,说找不到出口
1. 在实际运用的过程中,任务的执行人不是固定的,而是从几个人中,拿一个人出来执行便可 2. 在Condidata中,通过添加候选人,可以添加多个,中间使用逗号隔开 3. 在流程实例后,候选人领取任务前,该任务是没有执行人的 4. 候选人执行任务 1. 要使用TaskServicet提供的 claim(String taskId,String userId),进行领取任务 2. 使用 taskSerice.commit(Strinig taskId)执行任务六、网关
-
排他网关
- 当流程走到这个网关是,所有的分支都会进行判断,表达式为true的分支就是流程走向
- 如果分支都为true,就会选择id小的分支
- 当分支都为false时,会报错,说流程找不到出口
-
并行网关
- 需要两个网关,把所有分支都夹起来
- 只有里面的分支全都走完,流程才能到下一个结点
-
包含网关
-
可以看成是排他网关与并行网关的集合体
-
需要两个网关,把所有分支都夹起来
-
使用场景:请假规则
- 请假<3天的副经理批准
- 请假>3天的总经理批准
- 请假无论多少天都需要人事部门批准
-



