@[toc]
上篇文章鬆哥和大家分享了 Flowable 中設置任務處理人的四種方式,不過那四種方式都是針對單個任務處理人,有的時候,一個任務節點會存在多個候選人,例如 zhangsan 提交一個任務,這個任務即可以 lisi 處理,又可以 wangwu 處理,那麼針對這種多個任務候選人的情況,我們該如何處理?今天一起來看看。
1. 繪製流程圖
首先我們還是使用之前舊的流程圖,但是在為 UserTask 設置分配用户的時候,我們設置多個用户,如下圖:
設置完成後,我們下載這個流程文件,來看下對應的 XML 文件,內容如下:
<process id="demo01" name="demo01" isExecutable="true">
<documentation>demo01</documentation>
<startEvent id="startEvent1" flowable:initiator="INITATOR" flowable:formFieldValidation="true"></startEvent>
<userTask id="sid-5F901234-AFF1-480E-9D66-2D196B910BA3" flowable:candidateUsers="javaboy,zhangsan,lisi" flowable:formFieldValidation="true"></userTask>
<sequenceFlow id="sid-71FB3A81-F753-419D-9A0A-2FC6E5361CED" sourceRef="startEvent1" targetRef="sid-5F901234-AFF1-480E-9D66-2D196B910BA3"></sequenceFlow>
<endEvent id="sid-D0B9E5BF-8C1A-4F8F-B2C2-F423F5DC556D"></endEvent>
<sequenceFlow id="sid-DEBE03CD-F247-4EF3-BB67-ABBA94739B0A" 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 id="demo01" name="demo01" isExecutable="true">
<documentation>demo01</documentation>
<startEvent id="startEvent1" flowable:formFieldValidation="true"></startEvent>
<userTask id="sid-5F901234-AFF1-480E-9D66-2D196B910BA3" flowable:candidateUsers="${userIds}" flowable:formFieldValidation="true"></userTask>
<sequenceFlow id="sid-71FB3A81-F753-419D-9A0A-2FC6E5361CED" sourceRef="startEvent1" targetRef="sid-5F901234-AFF1-480E-9D66-2D196B910BA3"></sequenceFlow>
<endEvent id="sid-D0B9E5BF-8C1A-4F8F-B2C2-F423F5DC556D"></endEvent>
<sequenceFlow id="sid-DEBE03CD-F247-4EF3-BB67-ABBA94739B0A" 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 id="demo01" name="demo01" isExecutable="true">
<documentation>demo01</documentation>
<startEvent id="startEvent1" flowable:initiator="INITATOR" flowable:formFieldValidation="true"></startEvent>
<userTask id="sid-5F901234-AFF1-480E-9D66-2D196B910BA3" flowable:formFieldValidation="true">
<extensionElements>
<flowable:taskListener event="create" class="org.javaboy.flowableidm.MyUserTaskListener"></flowable:taskListener>
</extensionElements>
</userTask>
<sequenceFlow id="sid-71FB3A81-F753-419D-9A0A-2FC6E5361CED" sourceRef="startEvent1" targetRef="sid-5F901234-AFF1-480E-9D66-2D196B910BA3"></sequenceFlow>
<endEvent id="sid-D0B9E5BF-8C1A-4F8F-B2C2-F423F5DC556D"></endEvent>
<sequenceFlow id="sid-DEBE03CD-F247-4EF3-BB67-ABBA94739B0A" 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 如下:
和我們所想的基本一致。
好啦,這就是鬆哥今天和大家分享的如何給 Flowable 設置多個任務候選人的方式~
當然,還有其他辦法,下篇文章咱們繼續~