在上一遍讲述activiti工作流的流程使用,现在来总结一下activiti的进阶使用
何为进阶呢?
在上一篇中我们子啊定义流程图bpmn的时候节点的任务人是指定了的,是写死的,但是我们知道,在实际的项目开发中一个流程的定义是不可能就给一个人使用的或者是流程的审核环节也不会是固定的。所以我们在流程的定义中应该做到动态的设置节点任务的负责人,而这一点,bpmn流程的定义也是支持了的。
分配任务负责人的方式:
- 固定分配 也就是我们在流程定义是直接在assignee将负责人指定
- 表达式分配 本篇会详细说明
- 监听器分配 关于activiti的监听器我会在下一篇中详细总结
本章重点:表达式分配任务负责人
UEL表达式:
- UEL是java EE6规范的一部分,也叫做同意表达式语言。就像jsp中或者是MyBatis的映射文件中的sql语句的条件#{}一样,是一种占位符,让我们可以在代码中动态的给它赋值。
在activiti中支持两种UEL表达式:UEL-value和UEL-method.
- UEL-value这种方式在{}大括号里我们可以自定义变量名,而这个节点任务的具体负责人就取决与这个变量的值,也就是说只要我们在代码中正确的给这个变量赋值即就成为这个任务的负责人。
UEL-value的另一种方式
- 这里的user相当与activiti的一个变量,user.manager表示通过user的getter方法获取user里面的属性,在代码中我们只需要传入user对象即可,但是这个user对象里必须有对应的属性,不然也就会报错。
UEL-method
- UEL-method这种方法就是对应user对象的getter方法获取值,这跟上面第二种UEL-value的方式类似,只是UEL-method方法直接明了的使用getter取值。
下面用代码来实际演示
定义好流程图,这里我使用UEL-value的第一种方式定义assignee的值
部署流程
@Test
public void repository() {
//1.创建processEngine对象
//ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();
//2.得到RepositoryService实例
RepositoryService repositoryService = processEngine.getRepositoryService();
//3.进行部署
Deployment deployment = repositoryService.createDeployment()//创建deploment对象
.addClasspathResource("holiday.bpmn")//添加资源
.addClasspathResource("holiday.png")//添加ziyuan
.name("假期申请")//流程名字
.deploy();//流程的部署
//4.输出部署的一些信息
System.out.println(deployment.getId());
System.out.println(deployment.getName());
}
部署完之后就启动我们的流程实例
- 这里我们在启动流程步骤去指定流程节点的负责人,assignee1、assignee2 是我在流程定义中在填写申请任务节点中assignee的值为 {assignee1},相应的在审核节点的assignee的值为 ${assignee2}
/**
* 指定执行人部署任务
*/
@Test
public void ActivitiStartInstance1(){
//获取RuntimrService 对象
RuntimeService runtimeService = processEngine.getRuntimeService();
//设置assignee的取值
Map<String,Object> map=new HashMap<>(16);
map.put("assignee1","张三");
map.put("assignee2","李四");
//创建流程实例 流程定义的key 或者别的参数
ProcessInstance myTest = runtimeService.startProcessInstanceByKey("holiday",map);
//输出实例相关信息
System.out.println(myTest.getBusinessKey());
System.out.println(myTest.getDeploymentId());
System.out.println(myTest.getName());
}
- 我们也可以在执行完任务的时候给下一个节点的任务指定负责人,这里执行任务的人是张三,他完成任务的时候可以给下一个节点的去指定审核人为李四。
/**
* 执行
*/
@Test
public void ActicitiTaskQuery1(){
//得到TaskService对象
TaskService taskService = processEngine.getTaskService();
//根据流程定义的key,负责人assignee来实现当前用户的任务列表查询
Map<String,Object> map=new HashMap<>(16);
map.put("assignee2","李四");
Task task1 = taskService.createTaskQuery()
.taskAssignee("张三")
.processDefinitionKey("holiday")
.singleResult();
taskService.complete(task1.getId(),map);
}
以上两种就是在代码中动态的给节点指定负责人
流程变量
何为流程变量呢?
流程变量在activiti中是一个非常重要的角色,流程运转有时需要靠流程变量,在开发中业务和activiti的结合少不了流程变量的。比如在请假流程中,如果请假的天数大于3天则有总经理审批,否则直接到人事审批,而请假的天数就可以说是流程的变量啦;比如在审核中会有一种通过跟不通过的情况,当审核不通过时流程直接结束,当审核通过时流程就往下一步走,所以我们需要一个变量state来区分是通过还是不通过呢,可以设置state=0时就是不通过state=1就是通过,而state就是流程变量;
- 注意:如果是将一个POJO存储到流程变量中,必须实现序列化接口serializable,而且为了防止新增字段无法反序列化,需要生成serialVersionUID。
流程变量的作用域
流程变量的作用域默认是一个流程实例,也可以是一个任务(task)或者一个执行实例(execution),流程实例的作用域范围最大,可以成为global变量,任务和执行实例仅仅是针对一个任务和一个执行实例范围,成为local变量。实例之间的变量是互不影响的,不如张三请假的天数 跟李四请假的天数互不影响。
- global变量中变量名不允许重复,设置相同名称的变量,后设置的值会覆盖前设置的变量值。
- local变量由于在不同的任务或者不同的执行实例中,作用域互不影响,变量名可以设置重复,没有影响。变量名可以跟global变量名相同,没有影响。
流程变量的使用
1.设置流程变量
2.通过UEL表达式使用流程变量
也就是像上面说的 在assignee出设置UEL表示式,表达式的值为任务负责人,
比如${assignee1}, assignee1就是一个流程变量名称
3.我们也可以在流程节点之间的连线设置UEL表达式来决定流程的走向。
如:${holiday<=3} 和 {holiday>3},holiday请假天数就是一个流程变量名,UEL表达式结果类型为布尔类型;如审核状态(1 通过|2 不通过){state==1}和{state==0} state就是一个流程变量,用来决定流程走向。
而流程往UEL为true的方向执行
实例图
下面我以审核状态来演示变量的使用:
- 流程图中,我以state变量来决定流程的走向,当到达老师审批时,通过则state赋值为1,不通过则赋值为0,注意这里我是使用UEL-value的第二种方式定义变量;
对于global流程变量的赋值
- 可以在启动流程时设置
- 可以在任务办理时设置
- 可以在通过当前流程实例赋值
- 可以通过当前任务赋值
由于我们时使用UEL-value的第二种方式定义变量,所以我们需要新建一个apply的类,这个@Data是lombok包下的,可以帮我们自动生成getter和setter方法
@Data
public class Apply implements Serializable{
private String state;
}
首先我们需要部署流程,这一步跟上面的部署一样所以就不贴代码了
- 启动流程
//流程变量
/**
* 指定执行人启动流程
*/
@Test
public void ActivitiStartInstance2(){
//获取RuntimrService 对象
RuntimeService runtimeService = processEngine.getRuntimeService();
//设置assignee的取值
Map<String,Object> map=new HashMap<>(16);
map.put("user1","张三");
map.put("user2","李四");
map.put("admin","王五");
//创建流程实例 流程定义的key 或者别的参数
ProcessInstance myTest = runtimeService.startProcessInstanceByKey("apply",map);
//输出实例相关信息
System.out.println(myTest.getBusinessKey());
System.out.println(myTest.getDeploymentId());
System.out.println(myTest.getName());
}
- 由于我的state变量是需要老师审核的时候去赋值的所有张三完成申请任务的代码就不贴了,直接到老师完成任务节点设置流程变量。
/**
* 执行
*/
@Test
public void ActicitiTaskQuery3(){
//得到TaskService对象
TaskService taskService = processEngine.getTaskService();
//根据流程定义的key,负责人assignee来实现当前用户的任务列表查询
Task task1 = taskService.createTaskQuery()
.taskAssignee("李四")
.processDefinitionKey("apply")
.singleResult();
Map<String,Object> map=new HashMap<>(16);
Apply apply=new Apply();
apply.setState("0");
map.put("apply",apply);
taskService.complete(task1.getId(),map);//任务完成是设置
}
- 任务完成是进行变量的赋值跟这个演示的例子有点出入,但是我只是想给大家演示可以在任务完成时对流程变量赋值的这么一个方法,现在我们利用正确的方法来走我们演示的流程
- 因为我们这个演示流程中,老师时需要先给state变量去赋值才能完成任务的,因为我们的流程需要state变量去判断流程的走向,所有我们需要在老师完成任务之前去赋值,因此我们可以通过当前任务来给流程变量赋值
@Test
public void ActicitiTaskQuery2(){
//得到TaskService对象
TaskService taskService = processEngine.getTaskService();
//根据流程定义的key,负责人assignee来实现当前用户的任务列表查询
Task task1 = taskService.createTaskQuery()
.taskAssignee("李四")
.processDefinitionKey("apply")
.singleResult();
Apply apply=new Apply();
apply.setState("0");
taskService.setVariable(task1.getId(),"apply",apply);
taskService.complete(task1.getId());
}
由于老师的state设置为0也就是不通过,所以这一个流程就结束了。在act_ru_相关的表中已经没有数据了。
注意:
- 如果UEL表达式中的流程变量名不存在则报错。
- 如果UEL表达式中的流程变量值为空null,流程不按UEL表达式去执行,而是流程结束。
- 如果UEL表达式都不符合条件,流程结束
- 如果连线不设置条件,会走flow序号小的那条线,将bpmn文件后缀名改成xml就可以看到flow的序号了。
根据任务给local变量赋值,其它的没有区别,唯一不一样的就是 taskService.setVariableLocal(task1.getId(),"apply",map);
@Test
public void ActicitiTaskQuery3(){
//得到TaskService对象
TaskService taskService = processEngine.getTaskService();
//根据流程定义的key,负责人assignee来实现当前用户的任务列表查询
Task task1 = taskService.createTaskQuery()
.taskAssignee("李四")
.processDefinitionKey("apply")
.singleResult();
Map<String,Object> map=new HashMap<>(16);
Apply apply=new Apply();
apply.setState("0");
map.put("apply",apply);
taskService.setVariableLocal(task1.getId(),"apply",map);
taskService.complete(task1.getId());
}
组任务
组任务:这个任务的负责人可以有多个,也就是设置任务候选人
在流程图中任务节点配置张设置候选人,也可以使用UEL表达式动态设置,多个候选人之间用逗号分开。
上面为部门经理设置了两个候选人zhangsan和lisi
组任务的办理流程
- 第一步:查询组任务
指定候选人,查询改候选人当前的代办任务,此时候选人还不能办理任务。 - 第二步:拾取(claim)任务
改组任务的所有候选人都可以拾取
原理:将候选人的组任务变成个人任务,原来的候选人就变成了该任务的负责人;若拾取后不行办理此任务,需要将已经拾取的个人任务归还到组里边,也就是将个人任务变成组任务,这里有点绕,但是看完下面的介绍你就很容易明白是怎么回事了。 - 第三步:查询个人任务
这一步跟其它无差异 - 第四步:办理个人任务
演示
至于流程定义,部署和启动都是一样的,没有其他的差别,差别主要是在第二步,所以代码就不贴了。
因为候选人的设置是在部门经理审核的节点上,所以填写请假单的任务直接完成,无差异。
分析
在填写请假单完成之后,我们知道任务就会往下一步走,走到了部门经理的环节,所以我们来可以看看部门经理这一环节中在数据库表里的数据情况
在act_hi_actinst表中可以看出按照以前来说每到一个节点节点上的assignee都会有一个负责人的,但是我们可以看出在设置了候选人的部门经理审核这一行数据中assignee是null ,因为这一个任务的的执行人我们不能百分比确定,有可能是zhansan,也有可能是lisi,因此这里是为null的。
下一步就是让候选人来拾取任务了,不然下面的工作就无法开展
首先时候选人查看自已的任务
@Test
public void ActicitiTaskQuery4(){
//得到TaskService对象
TaskService taskService = processEngine.getTaskService();
//根据流程定义的key,负责人assignee来实现当前用户的任务列表查询
List<Task> taskList = taskService.createTaskQuery()
.taskDefinitionKey("zhangsan")//参数 候选人用户
.processDefinitionKey("myProcess_1")//流程定义的key
.list();
//任务列表展示
for (Task task:taskList) {
System.out.println("流程实例id"+task.getProcessDefinitionId());
System.out.println("任务id"+task.getId());
System.out.println("任务名称"+task.getName());
System.out.println("任务负责人"+task.getAssignee());
}
}
从结果可以看出任务负责人还是null空的,这时候还是组任务,到底执行人是谁还未知。
用户拾取组任务
@Test
public void ActicitiTaskQuery5(){
//得到TaskService对象
TaskService taskService = processEngine.getTaskService();
//根据流程定义的key,负责人assignee来实现当前用户的任务列表查询
Task task1 = taskService.createTaskQuery()
.taskCandidateUser("zhangsan")//根据候选人查询
.processDefinitionKey("myProcess_1")
.singleResult();
if (task1!=null){
taskService.claim(task1.getId(),"zhangsan");
}
}
分析:当用户拾取完任务还未执行任务时,数据库表的变化,可以看到部门经理审核这一个节点的执行人变成了zhangsan,原来是空的,待zhangsan拾取完之后执行人就成了zhangsan了,所以看的出来,当其中一个候选人拾取组任务后,这个组任务就变成了个人任务,怎么区分呢?当还是组任务的时候执行人时null不确定的,当候选人拾取任务后这个节点的任务就有了执行人,也就是说这个任务成为了拾取任务的候选人的个人任务了(候选人成为了任务的执行人)。
候选人拾取完任务,变成了真正的负责人了,下一步就是完成任务了
@Test
public void ActicitiTaskQuery6(){
//得到TaskService对象
TaskService taskService = processEngine.getTaskService();
//根据流程定义的key,负责人assignee来实现当前用户的任务列表查询
Task task1 = taskService.createTaskQuery()
.taskAssignee("zhansan")
.processDefinitionKey("myProcess_1")
.singleResult();
if (task1!=null) {
taskService.complete(task1.getId());
}
}
归还组任务
如果个人拾取后不想办理该组任务,可以归还组任务,归还后该用户不再是该任务的负责人,但还是候选人。(个人任务转换组任务),确认自己是组任务的负责人就可以将任务的负责人重新设置为null ,相当与刚才拾取的任务没做,负责人还是null,不就是早期当我们组任务还没有真正被某一个候选人拾取的时候一样为空,也就表示还原到没有用户拾取任务的情况
注意:我们也可以通过setAssignee方法将任务委托给其他用户负责,而且被委托的用户可以不是候选人。
@Test
public void ActicitiTaskQuery6(){
//得到TaskService对象
TaskService taskService = processEngine.getTaskService();
//校验zhangsan也就是用户是否是myProcess_1任务的负责人,如果是负责人才可以归还组任务
Task task1 = taskService.createTaskQuery()
.taskAssignee("zhansan")
.processDefinitionKey("myProcess_1")
.singleResult();
if (task1!=null) {
taskService.setAssignee(task1.getId(),null);
}
}