Activiti基本使用2
目录
3.1、什么是流程实例
3.2、让实际业务与activiti表关联(BusinessKey)
3.3、挂起,激活流程实例
3.3.1、全部流程实例挂起
3.3.2、单个流程实例挂起
4、任务分配
4.1、固定分配
4.2、表达式分配
4.2.1、UEL-value
4.2.2、UEL-method 方式
4.3、监听器分配
5、流程变量
5.1、什么是流程变量
5.2、流程变量的作用域
5.2.1、globa变量
5.2.2、local变量
5.3、流程变量的使用方法
5.4、设置globa变量
5.4.1、启动流程时设置变量
5.4.2、在任务办理时设置流程变量
5.4.3、通过当前流程实例设置
5.5、设置Local变量
6、任务组
6.1、Candidate-users候选人
6.2、组任务办理流程
6.3、关键代码
6.3.1、部署及启动
6.3.2、查询组任务
6.3.2、拾取组任务
6.3.3、 查询个人待办任务
6.3.4、 办理个人任务
6.3.5、归还组任务
6.3.6、 任务交接
7、网关
7.1、排他网关
7.2、并行网关
7.3、包含网关
3、流程实例
基本操作参考:Activiti的基本使用_Relievedz的博客-CSDN博客
3.1、什么是流程实例
流程定义ProcessDefinition和流程实例ProcessInstance是Activiti重要的概念,类似于Java类和Java实例的关系
启动一个流程实例表示开始一次业务流程的运行,比如员工请假流程部署完成,如果张三要请假就可以启动一个流程实例,如果李四要请假也启动一个流程实例,两个流程的执行互相不影响,就好比定义一个 java 类,实例化两个对象一样,部署的流程就好比 java 类,启动一个流程实例就好比 new 一个 java 对象
3.2、让实际业务与activiti表关联(BusinessKey)
比如我们填写一个请假单,一定会有一个请假单的唯一标识,我们通常使用这个标识来关联activiti,这个标识在activiti中称为businesskey
BusinessKey:业务标识,通常为业务的主键,业务标识和流程标识一一对应,业务标识来源于业务系统,存储业务标识就是根据业务标识来关联查询业务系统的数据
举例:请假流程启动一个流程实例,就可以将请假单的id作为业务标识存储到activiti中,将来查询activiti的流程实例信息就可以获取请假单的id从而关联查询业务系统数据库得到请假单信息
/*** 启动流程实例,添加businessKey*/ @Test public void startUpProcessAddBusinessKey(){String businessKey = "1";// 启动流程实例,指定业务标识businessKey,也就是请假申请单idProcessInstance processInstance = runtimeService.startProcessInstanceByKey("qingjia",businessKey);// 输出System.out.println("业务id:"+processInstance.getBusinessKey()); }
3.3、挂起,激活流程实例
某些情况可能由于流程变更需要将当前运行的流程暂停而不是直接删除,流程暂停后将不会执行;
3.3.1、全部流程实例挂起
操作流程定义为挂起状态,该流程定义下面的所有流程实例全部暂停: 流程定义为挂起状态,该流程定义将不允许启动新的流程实例,同时该流程定义下所有的流程实例将全部挂起暂停执行
//全部流程实例挂起 @Test public void suspendProcessInstanceAll() {//1 获取流程定义的对象ProcessDefinition qingjia = repositoryService.createProcessDefinitionQuery().processDefinitionKey("qingjia").singleResult();//2 调用流程定义对象的方法判断当前状态:挂起 激活boolean suspended = qingjia.isSuspended();//3 判断如果挂起,实现激活if(suspended) {//第一个参数 流程定义id//第二个参数 是否激活 true//第三个参数 时间点repositoryService.activateProcessDefinitionById(qingjia.getId(),true,null);System.out.println(qingjia.getId()+"激活了");} else {//如果激活,实现挂起repositoryService.suspendProcessDefinitionById(qingjia.getId(),true,null);System.out.println(qingjia.getId()+"挂起");} }
3.3.2、单个流程实例挂起
操作流程实例对象,针对单个流程执行挂起操作,某个流程实例挂起则此流程不在继续执行,完成该流程实例的当前任务将报异常;
//单个流程实例挂起 @Test public void SingleSuspendProcessInstance() {String processInstanceId = "b98e42b3-df27-11ed-b184-005056c00001";ProcessInstance processInstance = runtimeService.createProcessInstanceQuery().processInstanceId(processInstanceId).singleResult();boolean suspended = processInstance.isSuspended();if (suspended) {//激活runtimeService.activateProcessInstanceById(processInstanceId);System.out.println(processInstanceId + "激活");} else {runtimeService.suspendProcessInstanceById(processInstanceId);System.out.println(processInstanceId + "挂起");} }
4、任务分配
任务分配有三种方式
-
固定分配
-
UEL表达式分配
-
监听器分配
4.1、固定分配
在前面进行业务流程建模时指定固定的任务负责人,如:Assignee:zhangsan/lisi
4.2、表达式分配
activiti使用UEL表达式,UEL是java EE6规范的一部分,UEL即统一表达式语言,activiti支持两个UEL表达式:UEL-value和UEL-method。
4.2.1、UEL-value
新建:一个加班流程
如图:
assignee1 这个变量是 activiti 的一个流程变量
我们启动流程实例,这个启动实例的方法跟之前的方法基本一致,唯一的不同是在启动时,添加了一个参数
@Test public void deployProcess01() {// 流程部署Deployment deploy = repositoryService.createDeployment().addClasspathResource("process/jiaban.bpmn20.xml").name("加班申请流程").deploy();System.out.println(deploy.getId());System.out.println(deploy.getName()); }/*** 启动流程实例*/ @Test public void startUpProcess01() {Map<String, Object> map = new HashMap<>();map.put("assignee1","lucy");map.put("assignee2","mary");//创建流程实例,我们需要知道流程定义的keyProcessInstance processInstance =runtimeService.startProcessInstanceByKey("jiaban", map);//输出实例的相关信息System.out.println("流程定义id:" + processInstance.getProcessDefinitionId());System.out.println("流程实例id:" + processInstance.getId()); }
4.2.2、UEL-method 方式
如图:
userBean 是 spring 容器中的一个 bean,表示调用该 bean 的 getUsername(int id)方法。
经理审批:${userBean.getUsername(1)}
人事审批:${userBean.getUsername(2)}
package com.atguigu.auth.activiti;import org.springframework.stereotype.Component;/*** @program: guigu-oa-perent* @description: userBean 是 spring 容器中的一个 bean,表示调用该 bean 的 getUsername(int id)方法。* @author: Mr.Zhang* @create: 2023-04-20 11:42**/ @Component public class UserBean {public String getUsername(int id) {if(id == 1) {return "lilei";}if(id == 2) {return "wyz";}return "admin";} }
部署与启动
/ //uel-method @Test public void deployProcess01() {Deployment deployment = repositoryService.createDeployment().addClasspathResource("process/jiaban01.bpmn20.xml").name("加班申请流程01").deploy();System.out.println(deployment.getId());System.out.println(deployment.getName()); }@Test public void startProcessInstance01() {ProcessInstance processInstance = runtimeService.startProcessInstanceByKey("jiaban01");System.out.println(processInstance.getProcessDefinitionId());System.out.println(processInstance.getId()); }
启动流程实例,就会调用bean方法,参数为:1,经理审批后,接着调用bean方法,参数为:2
4.3、监听器分配
使用监听器的方式来指定负责人,那么在流程设计时就不需要指定assignee。
任务监听器是发生对应的任务相关事件时执行自定义 java 逻辑 或表达式
Event的选项包含:
Create:任务创建后触发 Assignment:任务分配后触发 Delete:任务完成后触发 All:所有事件发生都触发
定义任务监听类,且类必须实现 org.activiti.engine.delegate.TaskListener 接口
package com.atguigu.auth.activiti;import org.activiti.engine.delegate.DelegateTask; import org.activiti.engine.delegate.TaskListener;/*** @program: guigu-oa-perent* @description: 定义任务监听类* @author: Mr.Zhang* @create: 2023-04-21 08:55**/ public class MyTaskListener implements TaskListener {@Overridepublic void notify(DelegateTask task) {if(task.getName().equals("经理审批")){//分配任务task.setAssignee("jack");} else if (task.getName().equals("人事审批")) {task.setAssignee("tom");}} }
配置监听器
经理审批与人事审批,都设置一样的监听即可
部署与测试
// //监听器分配任务 @Test public void deployProcess02() {Deployment deployment = repositoryService.createDeployment().addClasspathResource("process/jiaban02.bpmn20.xml").name("加班申请流程02").deploy();System.out.println(deployment.getId());System.out.println(deployment.getName()); }@Test public void startProcessInstance02() {ProcessInstance processInstance = runtimeService.startProcessInstanceByKey("jiaban02");System.out.println(processInstance.getProcessDefinitionId());System.out.println(processInstance.getId()); }
启动流程实例,就会调用MyTaskListener监听方法
5、流程变量
5.1、什么是流程变量
流程变量在 activiti 中是一个非常重要的角色,流程运转有时需要靠流程变量,业务系统和 activiti 结合时少不了流程变量,流程变量就是 activiti 在管理工作流时根据管理需要而设置的变量。 比如:在请假申请流程流转时如果请假天数大于2 天则由总经理审核,否则由部门经理直接审核, 请假天 数就可以设置为流程变量,在流程流转时使用。
5.2、流程变量的作用域
流程变量的作用可以是一个流程实例,但也可以是一个任务(task)或是一个执行实例
5.2.1、globa变量
流程变量的默认作用域是流程实例。当一个流程变量的作用域为流程实例时,可以称为 global 变量
global 变量中变量名不允许重复,设置相同名称的变量,后设置的值会覆盖前设置的变量值。
5.2.2、local变量
任务和执行实例仅仅是针对一个任务和一个执行实例范围,范围没有流程实例大, 称为 local 变量。
Local 变量由于在不同的任务或不同的执行实例中,作用域互不影响,变量名可以相同没有影响。Local 变量名也可以和 global 变量名相同,没有影响。
5.3、流程变量的使用方法
通过UEL表达式使用流程变量
1、之前我们也使用过UEL表达式来设置任务处理人,例如${assignee1},activiti获取UEL表达式的值,即流程变量assignee1的值,将值作为任务的负责人进行任务分配 2、我们也可以在任务和任务间的连线上使用UEL表达式,决定流程走向 比如${ day > 2 }和${day <= 2},day就是一个流程变量名称,UEL表达式的执行结果是布尔类型
5.4、设置globa变量
5.4.1、启动流程时设置变量
在启动流程时设置流程变量,变量的作用域是整个流程实例。 通过 Map<key,value> 设置流程变量,map 中可以设置多个变量,这个 key 就是流程变量的名字
这是前面的实例代码
@Test public void startUpProcess() {Map<String, Object> variables = new HashMap<>();variables.put("assignee1", "zhangsan");variables.put("assignee2", "lisi");//创建流程实例,我们需要知道流程定义的keyProcessInstance processInstance = runtimeService.startProcessInstanceByKey("qingjia", variables);//输出实例的相关信息System.out.println("流程定义id:" + processInstance.getProcessDefinitionId());System.out.println("流程实例id:" + processInstance.getId()); }
5.4.2、在任务办理时设置流程变量
它的作用域是整个流程实例 ,如果设置的流程变量的 key 在流程实例中已存在相同的名字则后设置的变量替换前边设置的变量。
模拟实例代码:
@Test public void completTask() {Task task = taskService.createTaskQuery().taskAssignee("zhangsan") //要查询的负责人.singleResult();//返回一条 Map<String, Object> variables = new HashMap<>();variables.put("assignee2", "zhao");//完成任务,参数:任务idtaskService.complete(task.getId(), variables); }
5.4.3、通过当前流程实例设置
通过流程实例 id 设置全局变量, 它的作用域是整个流程实例 ,该流程实例必须未执行完成。
模拟实例代码:
@Test public void processInstanceIdSetVariables() {Map<String, Object> variables = new HashMap<>();variables.put("assignee2", "wang");runtimeService.setVariables("1c347a90-82c6-11ed-96ca-7c57581a7819", variables); }
5.5、设置Local变量
local 流程变量的作用域只在当前任务节点下可用
任务办理时设置local流程变量,当前运行的流程实例只能在该任务结束前使用,任务结束该变量无法在当前流程实例使用
模拟实例代码:
@Test public void completLocalTask() {Task task = taskService.createTaskQuery().taskAssignee("zhangsan") //要查询的负责人.singleResult();//返回一条 // 设置local变量,作用域为该任务taskService.setVariableLocal(task.getId(),"assignee2","li");// 查看local变量System.out.println(taskService.getVariableLocal(task.getId(), "assignee2"));//完成任务,参数:任务idtaskService.complete(task.getId()); }
6、任务组
6.1、Candidate-users候选人
1、需求 在流程定义中在任务结点的assignee固定设置任务负责人,在流程定义时将参与者固定设置在.bpmn文件中,如果要临时变更任务负责人则需要修改流程定义,系统扩展性很差,针对这种情况,我们可以给任务设置多个候选人,从候选人中选择参与者来完成任务
2、设置任务候选人
6.2、组任务办理流程
第一步:查询组任务
指定候选人,查询该候选人当前的待办任务 候选人不能办理任务 第二步:拾取(claim)任务
该组任务的所有候选人都能拾取 将候选人的组任务,变成个人任务,原来的候选人就变成了该任务的负责人 如果拾取后不想办理该任务 需要将已经拾取 第三步:查询个人任务
查询方式同个人任务部分,根据assignee查询用户负责的个人任务 第四步:办理个人任务
6.3、关键代码
6.3.1、部署及启动
@Test public void deployProcess04() {// 流程部署Deployment deploy = repositoryService.createDeployment().addClasspathResource("process/jiaban04.bpmn20.xml").name("请假申请流程").deploy();System.out.println(deploy.getId());System.out.println(deploy.getName()); ProcessInstance processInstance = runtimeService.startProcessInstanceByKey("jiaban04");System.out.println(processInstance.getId()); }
6.3.2、查询组任务
@Test public void findGroupTaskList() {//查询组任务List<Task> list = taskService.createTaskQuery().taskCandidateUser("zhangsan01")//根据候选人查询.list();for (Task task : list) {System.out.println("----------------------------");System.out.println("流程实例id:" + task.getProcessInstanceId());System.out.println("任务id:" + task.getId());System.out.println("任务负责人:" + task.getAssignee());System.out.println("任务名称:" + task.getName());} }
6.3.2、拾取组任务
@Test public void claimTask(){//拾取任务,即使该用户不是候选人也能拾取(建议拾取时校验是否有资格)//校验该用户有没有拾取任务的资格Task task = taskService.createTaskQuery().taskCandidateUser("zhangsan01")//根据候选人查询.singleResult();if(task!=null){//拾取任务taskService.claim(taskId, "zhangsan01");System.out.println("任务拾取成功");} }
张三01拾取任务了,张三02就不能拾取了
6.3.3、 查询个人待办任务
查询方式同个人任务查询
@Test public void findGroupPendingTaskList() {//任务负责人String assignee = "zhangsan01";List<Task> list = taskService.createTaskQuery().taskAssignee(assignee)//只查询该任务负责人的任务.list();for (Task task : list) {System.out.println("流程实例id:" + task.getProcessInstanceId());System.out.println("任务id:" + task.getId());System.out.println("任务负责人:" + task.getAssignee());System.out.println("任务名称:" + task.getName());} }
6.3.4、 办理个人任务
同个人任务办理
@Test public void completGroupTask() {Task task = taskService.createTaskQuery().taskAssignee("zhangsan01") //要查询的负责人.singleResult();//返回一条taskService.complete(task.getId()); }
6.3.5、归还组任务
如果个人不想办理该组任务,可以归还组任务,归还后该用户不再是该任务的负责人
@Test public void assigneeToGroupTask() {String taskId = "d96c3f28-825e-11ed-95b4-7c57581a7819";// 任务负责人String userId = "zhangsan01";// 校验userId是否是taskId的负责人,如果是负责人才可以归还组任务Task task = taskService.createTaskQuery().taskId(taskId).taskAssignee(userId).singleResult();if (task != null) {// 如果设置为null,归还组任务,该 任务没有负责人taskService.setAssignee(taskId, null);} }
6.3.6、 任务交接
任务交接,任务负责人将任务交给其它候选人办理该任务
@Test public void assigneeToCandidateUser() {// 当前待办任务String taskId = "d96c3f28-825e-11ed-95b4-7c57581a7819";// 校验zhangsan01是否是taskId的负责人,如果是负责人才可以归还组任务Task task = taskService.createTaskQuery().taskId(taskId).taskAssignee("zhangsan01").singleResult();if (task != null) {// 将此任务交给其它候选人zhangsan02办理该 任务taskService.setAssignee(taskId, "zhangsan02");} }
7、网关
网关用来控制流程的流向,通常会和流程变量一起使用。
7.1、排他网关
-
排他网关:只有一条路径会被选择
当你的流程出现这样的场景:请假申请,两天以内,部门经理审批流程就结束了,两天以上需要总经理直接审批,这个时候就需要排他网关
7.2、并行网关
-
并(平)行网关:所有路径会被同时选择
当出现这样的场景:请假申请开始,需要部门经理和总经理都审批,两者没有前后需要两个人全部审批才能进入下个节点人事审批。这个时候就需要并行网关
与排他网关的主要区别是,并行网关不会解析条件。 即使顺序流中定义了条件,也会被忽略。
7.3、包含网关
包容网关:可以同时执行多条线路,也可以在网关上设置条件,可以看做是排他网关和并行网关的结合体。 当出现这样的场景:请假申请大于等于2天需要由部门总经理审批,小于2天由部门经理审批,请假申请必须经过人事经理审批。这个时候就需要包含网关