在上一章中,我们学习了个人任务分配的相关知识,本章,我们将学习组任务分配的相关知识。
和前面一样,我们新建包cn.demo.grouptask,新建类TaskTest.java以及流程图文件task.bpmn。
定义审批流程
为了方便学习,我们同样定义一个审批流程。不同于个人任务的是,该审批流程的办理人不是一个人,而是好几个人,除此以外,其它方面和个人任务审批流程是一样的。

三种方式指定用户任务
和个人任务一样,组任务也有三种分配方式
- 直接指定办理人,即直接设置用户任务Assignee字段的值
- 使用流程变量,通过流程变量动态指定办理人的值
- 使用实现org.activiti.engine.delegate.TaskListener接口的类方法来动态指定办理人
下面,我们一一讲解。
直接指定办理人
现在,我们先来直接指定办理人,在个人任务中,我们直接指定办理人时,设置的是字段Assignee的值,而现在,若要设置组任务的办理人,则需要设置字段Candidate-user的值,并且办理人之间使用英文逗号分隔。

一切准备好之后,我们开始部署流程定义以及启动流程实例。
部署流程定义
和前面一样,我们按如下部署流程定义
@Test public void deploymentProcessDefinition() { InputStream inputStreamBpmn = this.getClass().getResourceAsStream("task.bpmn"); InputStream inputStreamPng = this.getClass().getResourceAsStream("task.png"); Deployment deploy = processEngine.getRepositoryService() .createDeployment() .name("任务") .addInputStream("task.bpmn", inputStreamBpmn) .addInputStream("task.png",inputStreamPng) .deploy(); System.out.println("部署ID: " + deploy.getId()); System.out.println("部署名称: " + deploy.getName()); }
|
运行代码,输出如下结果
部署ID: 6101
部署名称: 任务
此时,流程定义已经部署好了,接下来就是启动流程实例了。
启动流程实例
@Test public void startProcessInstance() { String processDefinitionKey = "task"; ProcessInstance processInstance = processEngine.getRuntimeService() .startProcessInstanceByKey(processDefinitionKey); System.out.println("流程实例ID: " + processInstance.getId()); System.out.println("流程定义ID: " + processInstance.getProcessDefinitionId()); }
|
运行代码,输出如下结果
流程实例ID: 6201
流程定义ID: task:4:6104
此时,我们查询一下act_ru_task(正在执行的任务表)


可以看到,和个人记录不一样的是,该记录的Assignee字段的内容为空
我们查询另外一张表act_ru_identitylink(任务办理人表,个人任务,组任务)

可以看到,该表USER_ID字段的小A,小B,小C和小D就是我们在组任务审批流程中指定的任务办理人,只是,对于他们每一个人,该表中都存在两条数据
- 当字段TYPE_的值为candidate 时,TASK_ID的值不为空
- 当字段TYPE_的值为participant是,PROC_INST_ID的值不为空
我们查询act_hi_identitylink(历史任务办理人表,个人任务,组任务)

在这张表的数据包括了以往的所有历史办理人,对于个人任务办理人来说,其字段TYPE_的值都为participant,而对于组任务办理人来说,其TYPE_值为participant和candidate的记录都存在。
查询任务
现在,我们暂时把目光转移到查询任务上来。我们查询一下小A的任务,查询方法还是使用**taskAssignee(assignee)**方法
@Test public void findMyPersonalTask() { String assignee = "小A"; List<Task> list = processEngine.getTaskService() .createTaskQuery() .taskAssignee(assignee) .orderByTaskCreateTime().asc() .list(); if (list != null && list.size() > 0) { for (Task task : list) { System.out.println("任务ID: " + task.getId()); System.out.println("任务名称: " + task.getName()); System.out.println("任务的创建时间: " + task.getCreateTime()); System.out.println("任务的办理人: " + task.getAssignee()); System.out.println("流程实例ID: " + task.getProcessInstanceId()); System.out.println("执行对象ID: " + task.getExecutionId()); System.out.println("流程定义ID: " + task.getProcessDefinitionId()); System.out.println(""); } } }
|
执行代码,毫无疑问地,由于查询个人任务本质上是查询**act_ru_task(正在执行的任务表),而该表中表示任务办理人ASSIGNEE_**字段的值为空,因此,我们无法查询到任何数据。
此时,我们应该查询某个人的组任务,而不是个人任务。为了查询某个人的组任务,我们使用taskCandidateUser(assignee)方法替换taskAssignee(assignee)
@Test public void findMyGroupTask() { String assignee = "小A"; List<Task> list = processEngine.getTaskService() .createTaskQuery() .taskCandidateUser(assignee) .orderByTaskCreateTime().asc() .list(); if (list != null && list.size() > 0) { for (Task task : list) { System.out.println("任务ID: " + task.getId()); System.out.println("任务名称: " + task.getName()); System.out.println("任务的创建时间: " + task.getCreateTime()); System.out.println("任务的办理人: " + task.getAssignee()); System.out.println("流程实例ID: " + task.getProcessInstanceId()); System.out.println("执行对象ID: " + task.getExecutionId()); System.out.println("流程定义ID: " + task.getProcessDefinitionId()); System.out.println(""); } } }
|
执行代码,我们就得到了小A的组任务详细信息了
任务ID: 6204
任务名称: 审批
任务的创建时间: Mon Oct 29 21:30:55 CST 2018
任务的办理人: null
流程实例ID: 6201
执行对象ID: 6201
流程定义ID: task:4:6104
接着上一节,我们来查询任务办理人,包括历史的还是正在执行的办理人。先查正在执行的任务办理人表
查询正在执行的任务办理人
对于查询正在执行的任务办理人来说,我们自然而然地想到使用taskService的方法来查询,通过getIdentityLinksForTask(taskId)方法,传入taskId,我们可以查询到正在执行的任务办理人信息。
@Test public void findRunPersonTask() { String taskId = "6204"; List<IdentityLink> list = processEngine.getTaskService().getIdentityLinksForTask(taskId); if (list != null && list.size() > 0) { for (IdentityLink identityLink : list) { System.out.println("任务Id:" + identityLink.getTaskId()); System.out.println("办理人类型:" + identityLink.getType()); System.out.println("流程实例Id:" + identityLink.getProcessInstanceId()); System.out.println("用户Id:" + identityLink.getUserId()); System.out.println(); } } }
|
运行代码,可以得到如下结果
任务Id:6204
办理人类型:candidate
流程实例Id:null
用户Id:小A
任务Id:6204
办理人类型:candidate
流程实例Id:null
用户Id:小C
任务Id:6204
办理人类型:candidate
流程实例Id:null
用户Id:小B
任务Id:6204
办理人类型:candidate
流程实例Id:null
用户Id:小D
可以看到,查询到的是TYPE_值为candidate的记录信息
查询历史任务办理人
与查询正在执行的任务办理人不同,对于查询历史任务办理人,我们使用的是historyService服务,我们既可以通过任务Id来查询,也可以通过流程实例Id来查询,这里我们使用流程实例Id来完成查询操作。
@Test public void findHistoryPersonTask() { String processInstanceId = "6201"; List<HistoricIdentityLink> list = processEngine.getHistoryService() .getHistoricIdentityLinksForProcessInstance(processInstanceId); if(list!=null && list.size()>0) { for (HistoricIdentityLink historicIdentityLink : list) { System.out.println("任务Id:" + historicIdentityLink.getTaskId()); System.out.println("办理人类型:" + historicIdentityLink.getType()); System.out.println("流程实例Id:" + historicIdentityLink.getProcessInstanceId()); System.out.println("用户Id:" + historicIdentityLink.getUserId()); System.out.println(); } } }
|
运行代码,得到如下结果
任务Id:null
办理人类型:participant
流程实例Id:6201
用户Id:小A
任务Id:null
办理人类型:participant
流程实例Id:6201
用户Id:小C
任务Id:null
办理人类型:participant
流程实例Id:6201
用户Id:小B
任务Id:null
办理人类型:participant
流程实例Id:6201
用户Id:小D
可以预料到,若我们使用任务Id来查询历史任务办理人,则办理人类型为candidate,同时任务Id将不为空,流程实例Id则相反,其值将为空。
拾取任务
虽然我们定义的是组任务,但是,我们还是需要将任务分配给某个人处理,因此,我们需要使用taskService的claim(taskId,userId)方法来拾取任务(认领任务),即将任务分配给个人。在分配任务时,可以分配组任务给非组任务的成员。在这里,我们指定给大F
@Test public void claim() { String taskId = "6204"; String userId = "大F"; processEngine.getTaskService() .claim(taskId, userId); }
|
若没有异常,我们查询act_ru_task(正在执行的任务表),可以看到ASSIGNEE_字段的值不为空,其值为大F

此时,我们若查询大F的个人任务,将能够获取到大F个人任务的详细信息
@Test public void findMyPersonTask() { String assignee = "大F"; List<Task> list = processEngine.getTaskService() .createTaskQuery() .taskAssignee(assignee) .orderByTaskCreateTime().asc() .list(); if (list != null && list.size() > 0) { for (Task task : list) { System.out.println("任务ID: " + task.getId()); System.out.println("任务名称: " + task.getName()); System.out.println("任务的创建时间: " + task.getCreateTime()); System.out.println("任务的办理人: " + task.getAssignee()); System.out.println("流程实例ID: " + task.getProcessInstanceId()); System.out.println("执行对象ID: " + task.getExecutionId()); System.out.println("流程定义ID: " + task.getProcessDefinitionId()); System.out.println(""); } } }
|
运行代码,得到如下结果
任务ID: 6204
任务名称: 审批
任务的创建时间: Mon Oct 29 21:30:55 CST 2018
任务的办理人: 大F
流程实例ID: 6201
执行对象ID: 6201
流程定义ID: task:4:6104
可以看到,我们确实能够查询到大F的个人任务信息,但是,使用其他人去查询是获取不到的。
查询一下act_ru_identitylink(正在执行任务办理人表)

再查询一下act_hi_identitylink(历史任务办理人表)

可以看到,两张表中的关于大F的记录都只有一条,且其TYPE_的值为participant,这说明该任务已经成为大F的个人任务。
回退任务
若大F不愿意处理该任务,我们可以通过设置办理人的值为空值回退任务。
@Test public void setAssigneeTask() { String taskId = "6204"; processEngine.getTaskService() .setAssignee(taskId, null); }
|
若我们此时查询**act_ru_task(正在执行的任务表)**,则可以发现ASSIGNEE_字段的值为空,此时我们无法查询到大F的个人任务了,个人任务重新成为组任务,当然,可以这样操作是建立在该任务之前是组任务的基础之上的。
添加组任务成员
除了上述拾取任务外,我们还可以添加和删除组任务成员,下面我们先添加组任务成员大H
@Test public void addGroupUser(){ String taskId = "6204"; String userId = "大H"; processEngine.getTaskService() .addCandidateUser(taskId, userId); }
|
查询**act_ru_identitylink(正在执行的任务办理人表)**,我们看到数据表添加了两条关于大H的记录

删除组任务成员
现在,我们删除组任务成员小B
@Test public void deleteGroupUser(){ String taskId = "6204"; String userId = "小B"; processEngine.getTaskService() .deleteCandidateUser(taskId, userId); }
|
查询act_ru_identitylink(正在执行的任务办理人表)

数据表数据显示,小B还保留了一条TYPE字段值为participant类型的记录。此时,我们查询小B的组任务,无法查到该记录,说明在act_ru_identitylink(正在执行的任务办理人表)中,TYPE_字段的值为candidate的记录将用于查询组任务。
现在,我们让小A来拾取任务
@Test public void claim() { String taskId = "6204"; String userId = "小A"; processEngine.getTaskService() .claim(taskId, userId); }
|
这样,组任务就被重新分配给了个人,于是,我们可以查询到小A的个人任务了。
@Test public void findMyPersonTask() { String assignee = "小A"; List<Task> list = processEngine.getTaskService() .createTaskQuery() .taskAssignee(assignee) .orderByTaskCreateTime().asc() .list(); if (list != null && list.size() > 0) { for (Task task : list) { System.out.println("任务ID: " + task.getId()); System.out.println("任务名称: " + task.getName()); System.out.println("任务的创建时间: " + task.getCreateTime()); System.out.println("任务的办理人: " + task.getAssignee()); System.out.println("流程实例ID: " + task.getProcessInstanceId()); System.out.println("执行对象ID: " + task.getExecutionId()); System.out.println("流程定义ID: " + task.getProcessDefinitionId()); System.out.println(""); } } }
|
运行代码,我们获得小A个人任务的详细信息
任务ID: 6204
任务名称: 审批
任务的创建时间: Mon Oct 29 21:30:55 CST 2018
任务的办理人: 小A
流程实例ID: 6201
执行对象ID: 6201
流程定义ID: task:4:6104
然后,我们完成小A任务
@Test public void completeMyPersonalTask() { String taskId = "6204"; processEngine.getTaskService().complete(taskId); System.out.println("完成任务:任务Id:" + taskId); }
|
若无异常,控制台输出
完成任务:任务Id:6204
通过以上操作步骤,证明直接指定组任务办理人是可行的。
使用流程变量指定办理人
和个人任务直接指定办理人的缺点一样,直接指定组任务办理人也是不够灵活的。因此,我们可以考虑使用流程变量指定组任务办理人。

部署流程定义和启动流程实例
和前面一样,我们重新部署流程定义
部署ID: 6801
部署名称: 任务
部署流程定义以后,我们启动流程实例,并在启动流程实例的同时,设置组任务的办理人,和前一节一样,各个办理人之间使用逗号分隔。
@Test public void startProcessInstance() { String processDefinitionKey = "task"; Map<String,Object> map = new HashMap<String,Object>(); map.put("userIDs", "大大,中中,小小"); ProcessInstance processInstance = processEngine.getRuntimeService() .startProcessInstanceByKey(processDefinitionKey,map); System.out.println("流程实例ID: " + processInstance.getId()); System.out.println("流程定义ID: " + processInstance.getProcessDefinitionId()); }
|
运行代码,输出如下结果
流程实例ID: 6901
流程定义ID: task:5:6804
查询任务
可以预见,查询**act_ru_identitylink(正在执行的任务办理人表)**,大大,中中,小小分别有两条记录,只是两条记录办理人类型不一样而已。现在,我们来查询大大的组任务
@Test public void findMyGroupTask() { String assignee = "大大"; List<Task> list = processEngine.getTaskService() .createTaskQuery() .taskCandidateUser(assignee) .orderByTaskCreateTime().asc() .list(); if (list != null && list.size() > 0) { for (Task task : list) { System.out.println("任务ID: " + task.getId()); System.out.println("任务名称: " + task.getName()); System.out.println("任务的创建时间: " + task.getCreateTime()); System.out.println("任务的办理人: " + task.getAssignee()); System.out.println("流程实例ID: " + task.getProcessInstanceId()); System.out.println("执行对象ID: " + task.getExecutionId()); System.out.println("流程定义ID: " + task.getProcessDefinitionId()); System.out.println(""); } } }
|
运行代码,得到如下结果
任务ID: 6905
任务名称: 审批
任务的创建时间: Tue Oct 30 21:15:04 CST 2018
任务的办理人: null
流程实例ID: 6901
执行对象ID: 6901
流程定义ID: task:5:6804
拾取任务
从上面的信息中,我们得到了任务Id,接下来,我们让大大来拾取任务。
@Test public void claim() { String taskId = "6905"; String userId = "大大"; processEngine.getTaskService() .claim(taskId, userId); }
|
完成任务
当大大完成拾取任务后,我们就可以完成该任务了。
@Test public void completeMyPersonalTask() { String taskId = "6905"; processEngine.getTaskService().complete(taskId); System.out.println("完成任务:任务Id:" + taskId); }
|
运行代码,没有异常。这样,我们就完成该审批流程,实践证明使用流程变量指定组任务办理人也是可以的。
使用类指定办理人
前面已经介绍了两种方式,现在,我们就介绍最后一种,使用类指定办理人。
为了更好地说明如何使用类指定办理人,我们新建包cn.demo.groupbyclass,并新建类TaskTest,流程图文件task.bpmn
既然是使用类指定办理人,那么我们就不再指定Candidate user,因此,该字段值为空,不需要任何值,只是需要一个实现org.activiti.engine.delegate.TaskListener的类,该类添加了郭靖、黄蓉这两个成员。
package cn.demo.groupbyclass;
import org.activiti.engine.delegate.DelegateTask; import org.activiti.engine.delegate.TaskListener;
public class TaskListenerImpl implements TaskListener {
@Override public void notify(DelegateTask arg0) { arg0.addCandidateUser("郭靖"); arg0.addCandidateUser("黄蓉"); }
}
|
和个人任务使用类指定办理人一样,我们同样需要设置审批用户任务的监听类

部署流程定义和启动流程实例
一切准备好之后,同样地,我们部署一个新流程定义
部署ID: 7101
部署名称: 任务
然后,我们启动流程实例
@Test public void startProcessInstance() { String processDefinitionKey = "task"; ProcessInstance processInstance = processEngine.getRuntimeService() .startProcessInstanceByKey(processDefinitionKey); System.out.println("流程实例ID: " + processInstance.getId()); System.out.println("流程定义ID: " + processInstance.getProcessDefinitionId()); }
|
运行代码,得到如下结果
流程实例ID: 7201
流程定义ID: task:6:7104
这样,我们的组任务成员就包含了郭靖,黄蓉两人了。接下来,我们将组任务分配给郭靖,并由其去完成任务。
查询任务
我们来查询一下郭靖的组任务
@Test public void findMyGroupTask() { String assignee = "郭靖"; List<Task> list = processEngine.getTaskService() .createTaskQuery() .taskCandidateUser(assignee) .orderByTaskCreateTime().asc() .list(); if (list != null && list.size() > 0) { for (Task task : list) { System.out.println("任务ID: " + task.getId()); System.out.println("任务名称: " + task.getName()); System.out.println("任务的创建时间: " + task.getCreateTime()); System.out.println("任务的办理人: " + task.getAssignee()); System.out.println("流程实例ID: " + task.getProcessInstanceId()); System.out.println("执行对象ID: " + task.getExecutionId()); System.out.println("流程定义ID: " + task.getProcessDefinitionId()); System.out.println(""); } } }
|
运行代码,得到如下结果
任务ID: 7204
任务名称: 审批
任务的创建时间: Tue Oct 30 21:57:28 CST 2018
任务的办理人: null
流程实例ID: 7201
执行对象ID: 7201
流程定义ID: task:6:7104
拾取任务
通过上面的信息,我们使用任务Id让郭靖拾取任务
@Test public void claim() { String taskId = "7204"; String userId = "郭靖"; processEngine.getTaskService() .claim(taskId, userId); }
|
若无异常,则表示任务拾取成功,组任务分配给了郭靖,为保险起见,我们查询一下郭靖的个人任务
@Test public void findMyPersonTask() { String assignee = "郭靖"; List<Task> list = processEngine.getTaskService() .createTaskQuery() .taskAssignee(assignee) .orderByTaskCreateTime().asc() .list(); if (list != null && list.size() > 0) { for (Task task : list) { System.out.println("任务ID: " + task.getId()); System.out.println("任务名称: " + task.getName()); System.out.println("任务的创建时间: " + task.getCreateTime()); System.out.println("任务的办理人: " + task.getAssignee()); System.out.println("流程实例ID: " + task.getProcessInstanceId()); System.out.println("执行对象ID: " + task.getExecutionId()); System.out.println("流程定义ID: " + task.getProcessDefinitionId()); System.out.println(""); } } }
|
运行代码,输出如下信息
任务ID: 7204
任务名称: 审批
任务的创建时间: Tue Oct 30 21:57:28 CST 2018
任务的办理人: 郭靖
流程实例ID: 7201
执行对象ID: 7201
流程定义ID: task:6:7104
输出不为空,证明郭靖确实拾取了任务。
完成任务
最后,我们来完成任务
@Test public void completeMyPersonalTask() { String taskId = "7204"; processEngine.getTaskService().complete(taskId); System.out.println("完成任务:任务Id:" + taskId); }
|
总结
组任务三种指定办理人的方式
- 直接指定办理人,即直接设置用户任务Assignee字段的值
- 使用流程变量,通过流程变量动态指定办理人的值
- 使用实现org.activiti.engine.delegate.TaskListener接口的类方法来动态指定办理人的值 ,即使用delegateTask.addCandidateUser(userId)添加组任务办理人
认领(拾取)和回退组任务
组任务要进行处理,必须分配给特定的办理人(认领任务),此时,可以使用**processEngine.getTaskService().claim(taskId, userId)**方法分配任务给特定办理人。
注意:该特定办理人既可以是组任务成员中的人,也可以不是组任务成员的人
当然,我们也可以使用**processEngine.getTaskService(). setAssignee(taskId, null)**方法回退组任务,前提是该任务之前是组任务。
添加或删除组任务成员
在组任务中,我们既可以使用**processEngine.getTaskService().addCandidateUser(taskId, userId)方法向组任务添加人员,也可以使用processEngine.getTaskService().addCandidateUser(taskId, userId)**从组任务中删除人员。
任务办理人相关的表
**act_ru_identitylink(正在执行的任务办理人表)**表存放任务的办理人,包括个人任务和组任务,表示正在执行的任务
**act_hi_identitylink(历史任务办理人表)**表存放任务的办理人,包括个人任务和组任务,表示历史任务
区别在于:
如果是个人任务,TYPE_的类型为participant(参与者)
如果是组任务,TYPE_的类型包括candidate(候选者)和participant(参与者)两种