有的时候,一个任务节点会存在多个候选人,例如 zhangsan 提交一个任务,这个任务即可以 lisi 处理,又可以 wangwu 处理,那么针对这种多个任务候选人的情况,我们该如何处理?今天一起来看看。
1\. 绘制流程图
首先我们还是使用之前旧的流程图,但是在为 UserTask 设置分配用户的时候,我们设置多个用户,如下图:
设置完成后,我们下载这个流程文件,来看下对应的 XML 文件,内容如下:
<process name="demo01" isExecutable="true">
<documentation>demo01</documentation>
<startEvent flowable:initiator="INITATOR" flowable:formFieldValidation="true"></startEvent>
<userTask flowable:candidateUsers="javaboy,zhangsan,lisi" flowable:formFieldValidation="true"></userTask>
<sequenceFlow sourceRef="startEvent1" targetRef="sid-5F901234-AFF1-480E-9D66-2D196B910BA3"></sequenceFlow>
<endEvent ></endEvent>
<sequenceFlow sourceRef="sid-5F901234-AFF1-480E-9D66-2D196B910BA3" targetRef="sid-D0B9E5BF-8C1A-4F8F-B2C2-F423F5DC556D"></sequenceFlow>
</process>
小伙伴们看到,UserTask 中的 flowable:candidateUsers="javaboy,zhangsan,lisi"
就表示这个 UserTask 由 javaboy、zhangsan 和 lisi 三个用户处理,用户名之间用 ,
隔开。
2\. 查询任务处理人
接下来我们部署并启动上面这个流程,具体如何部署如何启动,这个在之前的文章中松哥已经和大家聊过了,这里不再赘述。
当流程启动成功之后,现在我们很容易想到像之前文章那样,去查询 javaboy 需要处理的 UserTask,如下:
List<Task> list = taskService.createTaskQuery().taskAssignee("javaboy").list();
for (Task task : list) {
logger.info("id:{};name:{};taskDefinitionKey:{}", task.getId(), task.getName(), task.getTaskDefinitionKey());
}
但是我们却发现这个 SQL 执行完成后,查询不到任何数据!为什么呢?我们来分析下。
经过前面几篇文章的介绍,现在小伙伴们都知道了,上面这个方法最终查询的是数据库中的 ACT_RU_TASK
表,查询的 SQL 如下:
那我们就去检查 ACT_RU_TASK
表以及它的 ASSIGNEE_
字段,结果如下:
我们发现 ACT_RU_TASK
表中记录的 ASSIGNEE_
字段值为 null!
为 null 这个其实也好理解,毕竟这个 UserTask 有多个人可以处理,但是只有一个字段,没法储存,肯定有其他存储方式。
好啦,不和大家卖关子了,这种有多个候选人的任务,我们应该按照如下方式来查询:
@Test
void test12() {
List<Task> list = taskService.createTaskQuery().taskCandidateUser("javaboy").list();
for (Task task : list) {
logger.info("id:{};name:{};taskDefinitionKey:{}", task.getId(), task.getName(), task.getTaskDefinitionKey());
}
}
小伙伴们看到,这里应该调用 taskCandidateUser
方法进行处理。那么这个方法查询的是哪张表呢?我们来看下上面方法最终执行的 SQL,如下:
: ==> Preparing: SELECT RES.* from ACT_RU_TASK RES WHERE RES.ASSIGNEE_ is null and exists(select LINK.ID_ from ACT_RU_IDENTITYLINK LINK where LINK.TYPE_ = 'candidate' and LINK.TASK_ID_ = RES.ID_ and ( LINK.USER_ID_ = ? ) ) order by RES.ID_ asc
: ==> Parameters: javaboy(String)
: <== Total: 1
小伙伴们看到,这里的查询涉及到两张表,分别是 ACT_RU_TASK
和 ACT_RU_IDENTITYLINK
,两张表联合查询查出来的,那我们来看看 ACT_RU_IDENTITYLINK
表的内容:
小伙伴们看到,TYPE_
为 candidate 的就表示这个 Task 的候选人,id 为 c5693038-3f42-11ed-b9e2-acde48001122
的 Task 一共有三个候选人,两张表联合查询,才可以查到这个 UserTask 该由谁来处理。
另外一种常见的需求就是,已经知道了要处理的流程实例了,但是不知道应该由谁来处理,此时通过查询 ACT_RU_IDENTITYLINK
表就可以确定一个流程实例都有哪些参与者,如下:
@Test
void test13() {
List<ProcessInstance> list = runtimeService.createProcessInstanceQuery().list();
for (ProcessInstance pi : list) {
List<IdentityLink> identityLinksForProcessInstance = runtimeService.getIdentityLinksForProcessInstance(pi.getId());
for (IdentityLink identityLink : identityLinksForProcessInstance) {
logger.info("ProcessInstanceId:{},UserId:{}",identityLink.getProcessInstanceId(),identityLink.getUserId());
}
}
}
我们来看看上面这个执行的 SQL,如下:
可以看到,其实就是通过查询 ACT_RU_IDENTITYLINK
表获取我们想要的数据。
3\. 认领任务
对于这种有候选人的任务,我们需要先认领,再处理。认领的本质,其实就是给 ACT_RU_TASK
表中,这个 UserTask 记录的 ASSIGNEE_
字段设置上值。
认领任务的方式如下:
@Test
void test12() {
List<Task> list = taskService.createTaskQuery().taskCandidateUser("javaboy").list();
for (Task task : list) {
taskService.claim(task.getId(),"javaboy");
}
}
认领之后,我们再来看 ACT_RU_TASK
表中的数据,如下:
可以看到,此时 ASSIGNEE_
字段就有值了,同时 CLAIM_TIME
字段也记录了任务的认领时间。
再来看看任务认领执行的 SQL,基本上和我们所想的一致。
4\. 处理任务
认领后的任务该如何处理,这个就和我们上篇文章中介绍的方式一致了,如下:
@Test
void test11() {
List<Task> list = taskService.createTaskQuery().taskAssignee("javaboy").list();
for (Task task : list) {
taskService.complete(task.getId());
}
}
具体原理上篇文章中已经介绍过了,这里就不再赘述了。
任务执行完成后,ACT_RU_IDENTITYLINK
表中的记录也会随之删除。
5\. 变量与监听器
前面这种方式设置的任务候选人我们是在绘制流程图的时候直接硬编码的,这显然不是一个好办法。如果能通过变量来传递任务的候选人,就会方便很多。
5.1 候选人变量
我们可以在绘制流程图的时候,用变量代替直接指定候选人,方式如下:
此时,生成的流程 XML 文件中,UserTask 节点的处理人也就变成了下面这个样子:
<process name="demo01" isExecutable="true">
<documentation>demo01</documentation>
<startEvent flowable:formFieldValidation="true"></startEvent>
<userTask flowable:candidateUsers="${userIds}" flowable:formFieldValidation="true"></userTask>
<sequenceFlow sourceRef="startEvent1" targetRef="sid-5F901234-AFF1-480E-9D66-2D196B910BA3"></sequenceFlow>
<endEvent ></endEvent>
<sequenceFlow sourceRef="sid-5F901234-AFF1-480E-9D66-2D196B910BA3" targetRef="sid-D0B9E5BF-8C1A-4F8F-B2C2-F423F5DC556D"></sequenceFlow>
</process>
UserTask 节点中的 flowable:candidateUsers="${userIds}"
就表示流程的处理人由 userIds 变量控制。
好了,接下来我们来启动流程,注意,此时启动流程需要传递 userIds 变量,如下:
@Test
void test01() {
Map<String, Object> userIds = new HashMap<>();
userIds.put("userIds", "javaboy,zhangsan,lisi");
ProcessInstance pi = runtimeService.startProcessInstanceByKey("demo01",userIds);
logger.info("id:{},activityId:{}", pi.getId(), pi.getActivityId());
}
多个用户之间,用英文 ,
隔开。
好了,流程启动成功后,接下来的操作参考 3、4 小节,这里我就不再赘述了。
5.2 监听器
当然,我们也可以通过监听器来为 UserTask 设置多个候选处理人用户,首先我们创建一个监听器如下:
public class MyUserTaskListener implements TaskListener {
@Override
public void notify(DelegateTask delegateTask) {
delegateTask.addCandidateUser("javaboy");
delegateTask.addCandidateUser("zhangsan");
delegateTask.addCandidateUser("lisi");
}
}
然后在绘制流程图的时候,删除掉 UserTask 分配的用户,然后重新为 UserTask 设置一个监听器:
然后设置一个在创建 UserTask 的时候触发的监听器:
然后我们下载这个流程图对应的 XML 文件,如下:
<process name="demo01" isExecutable="true">
<documentation>demo01</documentation>
<startEvent flowable:initiator="INITATOR" flowable:formFieldValidation="true"></startEvent>
<userTask flowable:formFieldValidation="true">
<extensionElements>
<flowable:taskListener event="create" class="org.javaboy.flowableidm.MyUserTaskListener"></flowable:taskListener>
</extensionElements>
</userTask>
<sequenceFlow sourceRef="startEvent1" targetRef="sid-5F901234-AFF1-480E-9D66-2D196B910BA3"></sequenceFlow>
<endEvent ></endEvent>
<sequenceFlow sourceRef="sid-5F901234-AFF1-480E-9D66-2D196B910BA3" targetRef="sid-D0B9E5BF-8C1A-4F8F-B2C2-F423F5DC556D"></sequenceFlow>
</process>
可以看到,在 userTask 节点中,通过 extensionElements 指定了额外的监听器。
好啦,这个流程现在就可以直接启动了,启动时也不需要额外的变量。
流程启动成功后,接下来的操作参考 3、4 小节,这里我就不再赘述了。
6\. 任务回退
当一个任务认领(Claim)之后,但是又不想处理,此时我们可以将任务退回。方式如下:
@Test
void test16() {
List<Task> list = taskService.createTaskQuery().taskAssignee("javaboy").list();
for (Task task : list) {
taskService.setAssignee(task.getId(), null);
}
}
其实思路很简答,就是重新为任务设置处理人,且处理人为 null,这就是将任务回退了,接下来其他人可以重新认领该任务了。
7\. 修改任务候选人
7.1 增加
任务候选人也不是一成不变的,也可以动态修改,当一个流程启动之后,流程已经走到某一个 Task 了,此时我们想要修改该 Task 的候选人,也是可以的,方式如下:
@Test
void test17() {
List<Task> list = taskService.createTaskQuery().taskCandidateUser("javaboy").list();
for (Task task : list) {
taskService.addCandidateUser(task.getId(),"wangwu");
}
}
添加完成后,查看 ACT_RU_IDENTITYLINK
表,我们发现 wangwu 已经添加进来了:
7.2 删除
如果想要删除一个候选人,方式如下:
@Test
void test18() {
List<Task> list = taskService.createTaskQuery().taskCandidateUser("javaboy").list();
for (Task task : list) {
taskService.deleteCandidateUser(task.getId(), "wangwu");
}
}
删除成功之后,ACT_RU_IDENTITYLINK
表中对应的数据也就被清除掉了。
8\. 查询历史数据
如果一个流程执行结束了,我们还想查询这个流程曾经涉及到的参与者,可以通过如下方式查询:
@Test
void test14() {
List<HistoricProcessInstance> list = historyService.createHistoricProcessInstanceQuery().list();
for (HistoricProcessInstance historicProcessInstance : list) {
List<HistoricIdentityLink> links = historyService.getHistoricIdentityLinksForProcessInstance(historicProcessInstance.getId());
for (HistoricIdentityLink link : links) {
logger.info("userId:{},taskId:{},type:{},processInstanceId:{}",link.getUserId(),link.getTaskId(),link.getType(),link.getProcessInstanceId());
}
}
}
这里最终其实就是去 ACT_HI_IDENTITYLINK
表中查询,对应的 SQL 如下:
上面这是查询一个流程的参与人,当然我们也可以查询一个 Task 的候选人与处理人,如下:
@Test
void test15() {
List<HistoricTaskInstance> list = historyService.createHistoricTaskInstanceQuery().list();
for (HistoricTaskInstance historicTaskInstance : list) {
List<HistoricIdentityLink> links = historyService.getHistoricIdentityLinksForTask(historicTaskInstance.getId());
for (HistoricIdentityLink link : links) {
logger.info("userId:{},taskId:{},type:{},processInstanceId:{}", link.getUserId(), link.getTaskId(), link.getType(), link.getProcessInstanceId());
}
}
}
查询对应的 SQL 如下:
和我们所想的基本一致。