组任务及网关
组任务
Candidate-users
候选人
在流程定义中在任务结点的 assignee
固定设置任务负责人,在流程定义时将参与者固定设置在.bpmn
文件中,如果临时任务负责人变更则需要修改流程定义,系统可扩展性差。
针对这种情况可以给任务设置多个候选人,可以从候选人中选择参与者来完成任务。
设置任务候选人
在流程图中任务节点的配置中设置 candidate-users
(候选人),多个候选人之间用逗号分开。
查看bpmn
文件:
我们可以看到部门经理的审核人已经设置为 zhangsan,lishi
这样的一组候选人,可以使用activiti:candiateUsers=”用户 1,用户 2,用户 3”
的这种方式来实现设置一组候选人。
办理组任务
- 第一步:查询组任务
指定候选人,查询该候选人当前的待办任务。
候选人不能办理任务。 - 第二步:拾取(
claim
)任务
该组任务的所有候选人都能拾取。将候选人的组任务,变成个人任务。原来候选人就变成了该任务的负责人。- 如果拾取后不想办理该任务
需要将已经拾取的个人任务归还到组里边,将个人任务变成了组任务。
- 如果拾取后不想办理该任务
- 第三步:查询个人任务
查询方式同个人任务部分,根据 assignee 查询用户负责的个人任务。 - 第四步:办理个人任务
用户查询组任务
根据候选人查询组任务
@Test
public void findGroupTaskList() {
// 流程定义key
String processDefinitionKey = "holiday4";
// 任务候选人
String candidateUser = "zhangsan";
// 创建TaskService
TaskService taskService = processEngine.getTaskService();
//查询组任务
List<Task> list = taskService.createTaskQuery()//
.processDefinitionKey(processDefinitionKey)//
.taskCandidateUser(candidateUser)//根据候选人查询
.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());
}
}
用户拾取组任务
候选人员拾取组任务后该任务变为自己的个人任务。
@Test
public void claimTask(){
TaskService taskService = processEngine.getTaskService();
//要拾取的任务id
String taskId = "6302";
//任务候选人id
String userId = "lisi";
//拾取任务
//即使该用户不是候选人也能拾取(建议拾取时校验是否有资格)
//校验该用户有没有拾取任务的资格
Task task = taskService.createTaskQuery()//
.taskId(taskId)
.taskCandidateUser(userId)//根据候选人查询
.singleResult();
if(task!=null){
//第一个参数:拾取的任务id
//第二个参数:任务候选人id
taskService.claim(taskId, userId);
System.out.println("任务拾取成功");
}
}
说明:即使该用户不是候选人也能拾取,建议拾取时校验是否有资格
组任务拾取后,该任务已有负责人,通过候选人将查询不到该任务
用户查询个人待办任务
查询方式同个人任务查询
@Test
public void findPersonalTaskList() {
// 流程定义key
String processDefinitionKey = "holiday4";
// 任务负责人
String assignee = "zhangsan";
// 创建TaskService
TaskService taskService = processEngine.getTaskService();
List<Task> list = taskService.createTaskQuery()
.processDefinitionKey(processDefinitionKey)
//设置任务执行人
.taskAssignee(assignee)
.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());
}
}
用户办理个人任务
同个人任务办理
/**完成任务*/
@Test
public void completeTask(){
//任务ID
String taskId = "12304";
processEngine.getTaskService()//
.complete(taskId);
System.out.println("完成任务:"+taskId);
}
说明:建议完成任务前校验该用户是否是该任务的负责人
归还组任务
如果个人不想办理该组任务,可以归还组任务,归还后该用户不再是该任务的负责人
// 归还组任务,由个人任务变为组任务,还可以进行任务交接
@Test
public void setAssigneeToGroupTask() {
// 查询任务使用TaskService
TaskService taskService = processEngine.getTaskService();
// 当前待办任务
String taskId = "6004";
// 任务负责人
String userId = "zhangsan2";
// 校验userId是否是taskId的负责人,如果是负责人才可以归还组任务
Task task = taskService.createTaskQuery().taskId(taskId)
.taskAssignee(userId).singleResult();
if (task != null) {
// 如果设置为null,归还组任务,该 任务没有负责人
taskService.setAssignee(taskId, null);
}
}
说明:建议归还任务前校验该用户是否是该任务的负责人
也可以通过 setAssignee
方法将任务委托给其它用户负责,注意被委托的用户可以不是候选人(建议不要这样使用)
任务交接
任务交接,任务负责人将任务交给其它候选人办理该任务
@Test
public void setAssigneeToCandidateUser() {
// 查询任务使用TaskService
TaskService taskService = processEngine.getTaskService();
// 当前待办任务
String taskId = "6004";
// 任务负责人
String userId = "zhangsan";
// 将此任务交给其它候选人办理该 任务
String candidateuser = "lisi";
// 校验userId是否是taskId的负责人,如果是负责人才可以归还组任务
Task task = taskService.createTaskQuery().taskId(taskId)
.taskAssignee(userId).singleResult();
if (task != null) {
//交接任务
taskService.setAssignee(taskId, candidateuser);
}
}
数据库表操作
- 任务执行表
SELECT * FROM act_ru_task
记录当前执行的任务,由于该任务当前是组任务,所有assignee
为空,当拾取任务后该字段就是拾取用户的id
- 任务参与者
SELECT * FROM act_ru_identitylink
记录当前参考任务用户或组,当前任务如果设置了候选人,会向该表插入候选人记录,有几个候选就插入几个
若不是候选人显示为,参与者
于 act_ru_identitylink
对应的还有一张历史表 act_hi_identitylink
,向 act_ru_identitylink
插入记录的同时也会向历史表插入记录。任务完成
网关
排他网关
排他网关(也叫异或(XOR
)网关,或叫基于数据的排他网关),用来在流程中实现决策。 当流程执行到这个网关,所有分支都会判断条件是否为true
,如果为 true
则执行该分支。
注意,排他网关只会选择一个为 true 的分支执行。(即使有两个分支条件都为 true,排他网关也会只选择一条分支去执行)
为什么要用排他网关?
不用排他网关也可以实现分支,如下图:
上图中,在连线的 condition
条件上设置分支条件。
缺点:
- 如果条件都不满足,不使用排他网关,流程就结束了(是异常结束)。
- 当
condition
设置为总经理holiday.num > 3
,人事存档holiday.num >=
1时且流程变量设置为holiday.num = 5
,所有条件有成立时,两个分支会同时执行,流程逻辑出错。
如果使用排他网关决定分支的走向,如下:
如果从网关出去的线所有条件都不满足则系统抛出异常。
org.activiti.engine.ActivitiException: No outgoing sequence flow of the
exclusive gateway 'exclusivegateway1' could be selected for continuing
the process
at org.activiti.engine.impl.bpmn.behavior.ExclusiveGatewayActivityBehav
ior.leave(ExclusiveGatewayActivityBehavior.java:85)
说明 :经过排他网关必须要有一条且只有一条分支走。且选择bpmn
文件中Id
较小的路径执行
所有总经理执行。
最终看到整个执行流程如下:
流程定义
测试
在部门经理审核后,走排他网关,从排他网关出来的分支有两条,一条是判断请假天数是否大于 3天,另一条是判断请假天数是否小于等于 3 天。
设置分支条件时,如果所有分支条件都不是 true,报错:
org.activiti.engine.ActivitiException: No outgoing sequence flow of the exclusive gateway
'exclusivegateway1' could be selected for continuing the process
at
org.activiti.engine.impl.bpmn.behavior.ExclusiveGatewayActivityBehavior.leave(ExclusiveGatewayActivit
yBehavior.java:85)
并行网关
并行网关允许将流程分成多条分支,也可以把多条分支汇聚到一起,并行网关的功能是基于进入和外出顺序流的
- fork 分支:
并行后的所有外出顺序流,为每个顺序流都创建一个并发分支。 - join 汇聚:
所有到达并行网关,在此等待的进入分支, 直到所有进入顺序流的分支都到达以后, 流程就会通过汇聚网关。
注意,如果同一个并行网关有多个进入和多个外出顺序流, 它就同时具有分支和汇聚功能。 这时,网关会先汇聚所有进入的顺序流,然后再切分成多个并行分支。
与其他网关的主要区别是,并行网关不会解析条件。 即使顺序流中定义了条件,也会被忽略。(设置条件失效)
说明:
- 财务结算和入库是两个
execution
分支,在act_ru_execution
表有两条记录分别是财务会计和行政考勤,act_ru_execution
还有一条记录表示该流程实例。 - 待财务会计和行政考勤任务全部完成,在汇聚点汇聚,通过
parallelGateway
并行网关。 - 并行网关在业务应用中常用于会签任务,会签任务即多个参与者共同办理的任务。
流程定义
测试
当执行到并行网关数据库跟踪如下
-
当前任务表:
SELECT * FROM act_ru_task
上图中:有两个(多个)任务当前执行。 -
流程实例执行表:
SELECT * FROM act_ru_execution
上图中,说明当前流程实例有多个分支(两个)在运行。 -
对并行任务的执行:
并行任务执行不分前后,由任务的负责人去执行即可。
当完成并任务中一个任务后:已完成的任务在当前任务表act_ru_task
已被删除。 -
在流程实例执行表:
SELECT * FROM act_ru_execution
有中多个分支存在且有并行网关的汇聚结点。
有并行网关的汇聚结点:说明有一个分支已经到汇聚,等待其它的分支到达。 -
当所有分支任务都完成,都到达汇聚结点后:
流程实例执行表:SELECT * FROM act_ru_execution
执行流程实例不存在,说明流程执行结束
整个流程完成 act_hi_actinst
表中记录整个过程
包含网关
包含网关可以看做是排他网关和并行网关的结合体。 和排他网关一样,你可以在外出顺序流上定义条件,包含网关会解析它们。 但是主要的区别是包含网关可以选择多于一条顺序流,这和并行
网关一样。
包含网关的功能是基于进入和外出顺序流的。
- 分支:
所有外出顺序流的条件都会被解析,结果为true
的顺序流会以并行方式继续执行, 会为每个顺序流创建一个分支。 - 汇聚:
所有并行分支到达包含网关,会进入等待状态, 直到每个包含流程token
的进入顺序流的分支都到达。 这是与并行网关的最大不同。换句话说,包含网关只会等待被选中执行了的进入顺序流。 在汇聚之后,流程会穿过包含网关继续执行。
流程定义
员工类型:
通过流程变量 userType
来表示,如果等于 1 表示普通员工,如果等于 2 表示领导
注意:通过包含网关的每个分支的连线上设置condition
条件。
测试
如果包含网关设置的条件中,流程变量不存在,报错;
org.activiti.engine.ActivitiException: Unknown property used in expression: ${userType=='1' ||
userType=='2'}
需要在流程启动时设置流程变量userType
- 当执行到包含网关:
流程实例执行表:SELECT * FROM act_ru_execution
- 第一条记录:包含网关分支。
- 后两条记录:两个分支:常规项体检,抽血化验
- 当前任务表:
ACT_RU_TASK_
上图中,常规项体检,抽血化验都是当前的任务,在并行执行。
如果有一个分支执行到汇聚:
先走到汇聚结点的分支,要等待其它分支走到汇聚。
等所有分支走到汇聚,包含网关就执行完成。
包含网关执行完成,分支和汇聚就从 act_ru_execution
删除。
小结:在分支时,需要判断条件,符合条件的分支,将会执行,符合条件的分支最终才进行汇聚。
总结activiti
开发流程
- 第一步:部署
activiti
的环境。
环境包括:jar
包和数据库(25 张表)
业务系统通过spring
和activiti
整合进行开发。 - 第二步:使用
activiti
提供流程设计器(和idea
或eclipse
集成的designer
)工具进行流程定义
流程定义生成两个文件:.bpmn
和.png
(不是必须的)。 - 第三步;将流程定义文件部署到
activiti
的数据库SELECT * FROM act_re_deployment
#流程定义部署表
一次部署插入一条记录,记录流程定义的部署信息SELECT * FROM act_re_procdef
#流程定义表
一次部署流程定义信息,如果一次部署两个流程定义,插入两条记录- 建议:一次部署只部署一个流程定义,这样
act_re_deployment
和act_re_procdef
一对一关系 - 常用两个方法:单个文件部署和 zip 文件部署。
建议单个文件部署。
- 第四步: 启动一个流程实例
业务系统就可以按照流程定义去执行业务流程,执行前需要启动一个流程实例
根据流程定义来启动一个流程实例。
指定一个流程定义的key
启动一个流程实例,activiti
根据key
找最新版本的流程定义。
指定一个流程定义的id
启动一个流程实例。
启动一个实例需要指定businesskey
(业务标识),businessKey
是activiti
和业务系统整合时桥梁。
比如:请假流程,businessKey
就是请假单 id。
启动一个实例还可以指定流程变量,流程变量是全局变量(生命期是整个流程实例,流程实例结束,变量就消失) - 第五步:查询待办任务
查询个人任务:使用taskService
,根据assignee
查询该用户当前的待办任务。
查询组任务:使用taskService
,根据candidateuser
查询候选用户当前的待办组任务。 - 第六步:办理任务
办理个人任务:调用taskService
的complete
方法完成任务。
如果是组任务,需要先拾取任务,调用taskService
的claim
方法拾取任务,拾取任务之后组任务就变成了个人任务(该任务就有负责人)。 - 网关:
- 排他网关:任务执行之后的分支,经过排他网关分支只有一条有效。
- 并行网关:任务执行后,可以多条分支,多条分支总会汇聚,汇聚完成,并行网关结束。
- 包含网关:是排他网关和并行网关结合体。