流程就是按照一定设计规则执行的程序,例如公司的请假流程,报销流程等等.
如果我们不借用框架的情况下进行流程开发,通常是在数据库表用一个状态的字段来对流程进行控制,比如请假流程,状态1是开始流程,填写了请假单,状态2是部门经理审批,状态3是总经理审批,状态4是人事存档.我们每个用户根据自己的角色筛选对应状态下的表中数据,对表中数据进行修改审批,这样就能完成对流程的控制.
优势:
简单,简介,针对小型的固定流程的项目,我们可以考虑此开发模式
缺点:
1.每一项流程与代码高度耦合,如果我们经常需要修改流程,则需要修改大量的代码.
2.对于复杂流程控制不是很灵活,代码编写难度大.
Activiti
JBoss JBPM 6.5
JFlow 6.0
FixFlow 5.0
activiti的设计逻辑其实也是通过数据库表来控制流程,只不过为了解决流程和代码高度耦合,由BPMI这个组织开发了一套流程执行的模板与符号,其实就是花了一张图,只是这张图的底层是以个xml文件,他主要是用来控制流程的.
所以其实activiti就是两部分组成:
第一,bpmn文件(底层是xml文件)画出流程图.
第二,activiti的jar包,解析出bpmn文件,根据bpmn文件来控制流程的执行.
流程:即设计的bpmn文件
流程部署:即将bpmn文件存放在数据表中.
流程定义:也就是将bpmn流程文件解析出来的全部东西.
流程实例:也就是流程定义的实例对象,例如,请假流程这是流程定义,张三发起请假,这是流程实例.
任务:当前账号或者员工的任务.
节点:当前流程的一个节点.
activiti会连接数据库生成多张数据库表,根据版本的不同,可能会有23张或者24张或者25张表.这些表存放了全部流程相关的东西
为什么我们需要下载安装activiti组件呢?其实我们如果流程的bpmn文件不是我们编写的,是由他人写好拷贝给我们的,我们可以不用在eclipse或者idea上安装activiti组件,我们安装的组件其实就是为了画bpmn图而已.
3.1.1 方式一:在线安装
步骤:
1)help -> Install new Software
2)然后点击 Add
3)我们在Name上 写上 Activiti BPMN 2.0 designer
Location上写上 http://activiti.org/designer/update/ (插件更新地址)
3.1.2方式二:离线安装
步骤:
1)下载离线jar包
接下来有两种方式:
第一种
第二种在 help -> Install new Software
3.1.3验证是否安装成功
查看是否安装成功,在eclipse中,File–>New–>Other–>搜索activiti出现以下界面,安装成功。
3.2.1在线安装
搜索
安装
3.2.2 离线安装
步骤:
1)下载actiBPM.jar包,官网下载即可
2)本地导入插件jar包
3.2.3验证是否安装成功
右键-new,查看是否可以new bpmn file文件
由BPMI(The Business Process Management Initiative)开发了一套标准叫业务流程建模符号(BPMN - Business Process Modeling Notation)。简单点来说就是用来化流程图,画出的流程图底层是xml文件,可以被解析成一个个的节点.
画图:
xml文件
作用:bpmn是用来画流程图的,底层是xml文件,可以被解析成一个个的节点.
开始节点: 流程的开始
结束节点: 流程的结束
任务节点: 中间的任务节点
泳道: 不知
网关: 做接下来的流处理
事件:不知
activiti的架构图
5.1 依赖的导入及配置文件的抒写
1).导入依赖
`
org.activiti
activiti-engine
7.1.0.M1
org.activiti
activiti-spring
7.1.0.M1
org.activiti
activiti-bpmn-model
7.1.0.M1
mysql
mysql-connector-java
5.1.32
com.alibaba
druid
1.1.12
junit
junit
4.12
test
commons-io
commons-io
2.4
org.junit.jupiter
junit-jupiter
RELEASE
compile
org.slf4j
slf4j-nop
1.7.2
2).配置文件的抒写 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"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
<!--如果用processEngine.getDefaultEngine()方法获取默认的processEngine,则必须文件名为activiti.cfg.xml,并且bean的name也要为这个-->
<bean id="processEngineConfiguration" class="org.activiti.engine.impl.cfg.StandaloneProcessEngineConfiguration">
<property name="jdbcDriver" value="com.mysql.jdbc.Driver"></property>
<property name="jdbcUrl" value="jdbc:mysql://localhost:3306/actdb1??serverTimezone=GMT%2B8"></property>
<property name="jdbcUsername" value="root"></property>
<property name="jdbcPassword" value="root"></property>
<property name="databaseSchemaUpdate" value="true"></property>
</bean>
</beans>
5.2 processEngine对象的获取
配置文件:其实就是配置dataSource,配置数据库连接池.因为activiti要操作数据库表
这里是用的mysql数据库.
代码
//方法1 自己加载配置文件
//1.获取configuration对象
ProcessEngineConfiguration configuration = ProcessEngineConfiguration
.createProcessEngineConfigurationFromResource("activiti.cfg.xml");
//2.获取ProcessEngine对象
ProcessEngine engine = configuration.buildProcessEngine();
//方法2,默认读取resources/activiti.cfg.xml配置文件
//1.得到processEngine对象
ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();
5.3 ReposiroryService 对象
主要的方法及功能:
1)部署流程
方法1:classpath部署流程
/**
* classpath部署流程
*/
@Test
public void deploy(){
//1.获取configuration对象
ProcessEngineConfiguration configuration = ProcessEngineConfiguration
.createProcessEngineConfigurationFromResource("activiti.cfg.xml");
//2.获取ProcessEngine对象
ProcessEngine engine = configuration.buildProcessEngine();
//3.部署流程到表中去
org.activiti.engine.repository.Deployment deployment = engine.getRepositoryService()//部署相关的service
.createDeployment() //创建部署对象
.addClasspathResource("diagrams/helloDemo2.bpmn20.xml")
.addClasspathResource("diagrams/helloDemo2.bpmn20.png")
.name("helloDemo2流程")
.deploy();
System.out.println("流程部署id:" + deployment.getId());
System.out.println("流程部署name:" + deployment.getName());
}
方法2: zip方式部署流程,这里的zip包是bpmn文件和png图片合起来的压缩文件.
/**
* zip方式部署流程
*/
@Test
public void zipDeploy(){
ProcessEngine engine = ProcessEngines.getDefaultProcessEngine();
//3.部署流程定义diagrams/请假审批.zip
InputStream inputStream = this.getClass().getClassLoader().getResourceAsStream("diagrams/th5/holiday01.zip");
ZipInputStream zipInputStream = new ZipInputStream(inputStream);//Charset.forName("GBK")编码问题,必须的加上GBK
org.activiti.engine.repository.Deployment zipDeployment = engine.getRepositoryService()
.createDeployment()
.addZipInputStream(zipInputStream)
.name("holiday01")
.deploy();
System.out.println("流程部署id" + zipDeployment.getId());
System.out.println("流程部署name" + zipDeployment.getName());
}
2)查询流程定义
/**
* 查询流程定义相关信息
*/
@Test
public void queryProcessIdentify(){
//1.得到processEngine对象
ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();
//2.得到repositoryService对象
org.activiti.engine.RepositoryService repositoryService = processEngine.getRepositoryService();
//3.查询流程定义
//3.查询部署的流程定义
List<ProcessDefinition> list = repositoryService//这是仓库service对象
.createProcessDefinitionQuery()//这是查询流程定义
//这是条件,类似于sql语句中where
.processDefinitionKey("myProcess_1") //这是通过key查询
//.processDefinitionId() //这是通过id查询
//.processDefinitionName() //这是通过那么查询
//.processDefinitionVersion()// 这是按照版本查询
//.processDefinitionNameLike()//这是模糊查询
//这是结果集
//.singleResult()//这是单个结果
.list();//这是结果集
for (ProcessDefinition pd : list) {
System.out.println(pd.getId());
System.out.println(pd.getName());
System.out.println(pd.getVersion());
System.out.println("-------------");
}
}
3)删除流程定义
/**
* 删除流程定义相关信息
*/
@Test
public void deleteProcessIdentify(){
//1.得到processEngine对象
ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();
//2.得到repositoryService对象
org.activiti.engine.RepositoryService repositoryService = processEngine.getRepositoryService();
//3.删除部署的流程
//注意:第一个参数1是deployment表的id,true表示强制删除,如果为false表示如果当前流程有未完成的,则不能删除流程部署
repositoryService.deleteDeployment("1",true);
}
5.4 RuntimeService 对象
1)启动流程实例
@Test
public void taskAssigneeUEL(){
//1.获取processEngine对象
ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();
//2.获取runtimeService对象
RuntimeService runtimeService = processEngine.getRuntimeService();
//3.准备参数
//这里的参数一般都是前台传过来的值,获取前台只传了一个值,
// 我们查询后台数据库表获取后面的几个值,例如填写该单据的经理等
//这里我们就简化直接运用固定值
HashMap<String, Object> map = new HashMap<>();
map.put("assignee1","张三");
map.put("assignee2","李四");
map.put("assignee3","王五");
map.put("assignee4","赵六");
map.put("money",19999);
//设置参数方法一
//4.启动流程实例,并将参数传入实例中
//参数1:myProcess_1 是流程实例的key值
//参数2:10001 是关联业务的该记录的主键值
//参数3:map 是我们传入本流程实例的每个节点的执行者的参数值
ProcessInstance process1 = runtimeService.startProcessInstanceByKey("holiday01", "1111111", map);
5.5TaskService 对象
作用:查询当前任务,处理任务
/**
* 这里测试当前的流程变量是否能控制流程,验证成功
* 完成任务
*/
@Test
public void completeTask(){
//1.获取processEngine对象
ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();
//2.测试完成任务
TaskService taskService = processEngine.getTaskService();
//3.查询当前名字员工是由有任务
String name= "赵六";
Task task = taskService.createTaskQuery()
.taskAssignee(name)
.processDefinitionKey("holiday01")
.singleResult();
//4.传入holiday.day参数
holiday holiday = new holiday();
holiday.setDay(4);
HashMap<String, Object> map = new HashMap<>();
map.put("holiday",holiday);
//设置参数方法三
//5.验证task是否为空,执行任务,并且传入holiday对象参数,也就是流程中的参数
if(task != null){
taskService.complete(task.getId(),map);//完成任务,并传入参数
//taskService.setVariable("2501","holiday",holiday); //利用task传入参数
//taskService.setVariableLocal("2501","holiday",holiday); //利用task传入参数,这是设置局部变量,节点变量
System.out.println(name + "完成任务!!!");
}else {
System.out.println(name + "当前没有任务");
}
5.6HistoryService 对象
作用:查询历史数据.
/**
* 历史数据点额查询
*/
@Test
public void queryHistory(){
//1.processEngine对象
ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();
//2.获取historyService兑现
org.activiti.engine.HistoryService historyService = processEngine.getHistoryService();
//3.创建活动节点实例历史查询器HistoricActivityInstanceQuery
HistoricActivityInstanceQuery instanceQuery = historyService
.createHistoricActivityInstanceQuery()
.processInstanceId("2501");
//4.获取HistoricActivityInstance历史活动实例对象的集合,并且按照开始时间排序
List<HistoricActivityInstance> lists = instanceQuery
//按照开始时间排序
.orderByHistoricActivityInstanceStartTime().asc().list();
for (HistoricActivityInstance instance : lists) {
System.out.println("ActivityId:" + instance.getActivityId());
System.out.println("ActivityName:" + instance.getActivityName());
System.out.println("Assignee:" + instance.getAssignee());
System.out.println("ProcessDefinitionId:" + instance.getProcessDefinitionId());
System.out.println("ProcessInstanceId:" + instance.getProcessInstanceId());
System.out.println("=============");
}
}
流程变量是为了让我们在运行流程实例时赋值来控制流程的走势.
1)变量类型
2)流程变量的作用域
变量作用域
Global 变量 默认的作用域为当前的流程实例.如果在其他节点设置则会覆盖
Local 变量 作用域为当前节点,在其他节点不可用.
3)定义流程变量的格式
第一种: ${变量名}
例如: ${assignee1}
这个表示什么了一个名为assignee1的变量
第二种: ${类名.属性名}
例如: ${holiday.day > 3}
这个表示什么了一个类holiday,他的属性day要大于3
4)常设置流程变量的地方
节点执行人,用于流程实例启动时赋节点任务执行人的值.
条件变量,用于控制流程的执行
5)流程变量传值
两种方式
第一种:RuntimeService传值,也就是给整个流程实例传值
HashMap<String, Object> map = new HashMap<>();
map.put("assignee1","张三");
map.put("assignee2","李四");
map.put("assignee3","王五");
map.put("assignee4","赵六");
//设置参数方法一
//4.启动流程实例,并将参数传入实例中
//参数1:myProcess_1 是流程实例的key值
//参数2:10001 是关联业务的该记录的主键值
//参数3:map 是我们传入本流程实例的每个节点的执行者的参数值
ProcessInstance process1 = runtimeService.startProcessInstanceByKey("holiday01", "1111111", map);
//设置参数方法二
//设置一个参数的值
//参数1 2501 是流程实例id
//参数2 holiday 是流程变量名
//参数3 holiday 是变量值
//runtimeService.setVariable("2501","holiday",holiday);
第二种:TaskService传值,也就是当前节点传值
String name= "赵六";
Task task = taskService.createTaskQuery()
.taskAssignee(name)
.processDefinitionKey("holiday01")
.singleResult();
//传入holiday.day参数
holiday holiday = new holiday();
holiday.setDay(4);
HashMap<String, Object> map = new HashMap<>();
map.put("holiday",holiday);
//设置参数方法1
//5.验证task是否为空,执行任务,并且传入holiday对象参数,也就是流程中的参数
if(task != null){
taskService.complete(task.getId(),map);//完成任务,并传入参数
//设置参数方法2
//taskService.setVariable("2501","holiday",holiday); //利用task传入参数
//taskService.setVariableLocal("2501","holiday",holiday); //利用task传入参数,这是设置局部变量,节点变量
System.out.println(name + "完成任务!!!");
}else {
System.out.println(name + "当前没有任务");
}
注意:作为流程变量的类,必须序列化
如果该类不序列化,则会报以下错误.
例如上面什么的holiday类做流程变量;
应用场景: 就是我们某个节点的处理人不是一个人而是一个职位的人,相当于我们这个节点的处理交给了多个人处理
1)候选人设置:
候选人处理任务的方式,
由于多个人可以同时处理一个任务,所以需要做并发处理,其实原理就是并发的2pc提交的方式,也就是2阶段提交的方式.
2)处理候选任务的步骤:
3)具体的代码
候选人拾掇了任务由三种处理方式:
1.归还任务
2.把任务交由其他候选人
3.完成任务
@Test
public void CandaditeTask(){
//1.获取引擎对象
ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();
//2.获取taskService对象
TaskService taskService = processEngine.getTaskService();
//3.查询当前候选人任务
String user = "刘备";
Task task = taskService.createTaskQuery()
.processDefinitionKey("holiday02")
.taskCandidateUser(user)
.singleResult();
//4.判断是否有任务
if(task != null) {
//5 确认拾掇任务,参数1为taskid,参数2为当前user
taskService.claim(task.getId(),user);
System.out.println(user + "拾掇任务");
System.out.println("完成任务");
//以下三个是多余拾掇的任务的三种处理方式,我们可以任选其一处理任务.
//6.1 归还组任务
taskService.setAssignee(task.getId(),null);
//6.2 将当前任务交由其他候选人执行
String newName = "曹操";
taskService.addCandidateUser(task.getId(),newName);//添加候选人
taskService.setAssignee(task.getId(),newName);
//6.3 完成任务
taskService.complete(task.getId());
}else {
System.out.println("当前人物没有候选任务");
}
}
网关的分类:
1.排他网关(就是接下来的路线只能走一条)
2.并行网关(如果接下来有多条路,必须走所有条路)
3.包含网关(如果接下来有多条路,可以选择其中的多条路)
1)排他网关
应用场景:就是在一个流程中的分支中只能选择一条走,则用排他网关)
注意:接下来如果有多条线路可以走的话,排他网关制定接下来的只能走一条路,就算两条路都为true,他只会选择走一条路,他会选择步骤id更小的流程走.所以如果我们要是有排他网关的话,一定要注意抒写流程id的先后顺序.
具体bpmn图画图如下.
2)并行网关
应用场景为一个流程需要完成的多项任务,我们需要完成所有的分支任务,但是这些任务不分先后顺序,则我们使用并行网关.
并行网关不会解析条件,也就是并行网关之后的所有完成汇集到一起才算完成.其实有点类似于java并发中的多线程操作中的join方法.如果使用join方法,则两个必须一起结束才能接受下一步的流程.
3)包含网关
应用场景:我们在一个流程中有多个分支,我们会根据当前实例的参数,选择其中的多个分支执行,不止一个分支,也不是全部的分支必须走,这种我们用包含分支.
对应执行的包含网关中的分支就是:
1.与springSecurity高度耦合
2.多了两个api.一个是taskRuntime;另一个是processRuntime,这两个接口的实现类高度耦合SpringSecurity.
3.Resource/processes下的bpmn文件会自动部署.