我們在使用Flowable?工作流引擎的時候,最常用的肯定是任務節點,因為在OA系統、審批系統、辦公自動化系統中核心的處理就是流程的運轉,在流程運轉的時候,可能我們有這樣的一個需求,在一個任務節點的時候,我們需要多個人對這個節點進行審批,比如實際中這樣一個例子,假如是一個部門的投票,這個部門有5個人,那么當5個人都投票的時候大概分為如下幾種:
1.部門所有人都去投票,當所有人都投票完成的時候,這個節點結束,流程運轉到下一個節點。(所有的人都需要投票)
2.部門所有人都去投票,只要有任意2/3的人同意,這個節點結束,流程運轉到下一個節點。(部分人投票只要滿足條件就算完成)。
3.部門中有一個部門經理,只要部門經理投票過了,這個節點結束,流程運轉到下一個節點(一票否決權)。
4.部門中根據職位不同,不同的人都不同的權重,當滿足條件的時候,這個節點結束,流程運轉到下一個節點。比如說所有的人員權重加起來是1,a有0.2的權重,其他的四個人分別是0.1的權重,我們可以配置權重達到0.3就可以走向下一個節點,換言之a的權重是其他人的2倍,那就是a的投票相當于2個人投票。這種需求還是很常見的。
5.部門所有人都去投票,a投票結束到b,b開始投票結束到c,一直如此,串行執行。最終到最后一個人再統計結果,決定流程的運轉。
上面的五種情況,我們可以提取出來一些信息,我們的activiti?工作流引擎,必須支持如下功能,才能滿足上面的需求:
1.任務節點可以配置自定義滿足條件。
2.任務節點必須支持串行、并行。
3.任務節點必須支持可以指定候選人或者候選組。
4.任務節點必須支持可以循環的次數。
5.任務節點必須支持可以自定義權重。
6.任務節點必須支持加簽、減簽。(就是動態的修改任務節點的處理人)
因為實際上的需求可能比上面的幾種情況更加的復雜,上面的6個滿足條件,工作流支持前4個,后面的2個條件是不支持的,所以我們必須要擴展activiti?工作流引擎才能使用5、6等的功能。下面我們將詳細的介紹前四種條件的使用,在掌握基本使用之后,我們在后面的章節中將詳細的介紹,5、6這兩種功能以及可能更加復雜的操作。
?
1.1.2.?串行、并行配置
為了演示如何使用,我們采用由淺入深的使用,結合流程圖、流程定義xml、以及代碼和數據庫的變化來闡釋每一個配置的使用以及含義。
流程的詳細定義如下圖所示:
?
流程的詳細定義xml如下:
<?xml version="1.0" encoding="UTF-8"?>
<definitions xmlns="http://www.omg.org/spec/BPMN/20100524/MODEL" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:flowable="http://flowable.org/bpmn" xmlns:bpmndi="http://www.omg.org/spec/BPMN/20100524/DI" xmlns:omgdc="http://www.omg.org/spec/DD/20100524/DC" xmlns:omgdi="http://www.omg.org/spec/DD/20100524/DI" typeLanguage="http://www.w3.org/2001/XMLSchema" expressionLanguage="http://www.w3.org/1999/XPath" targetNamespace="http://www.flowable.org/processdef">
? <process id="multiInstance" name="multiInstance" isExecutable="true">
? ? <documentation>multiInstance</documentation>
? ? <startEvent id="startEvent1"></startEvent>
? ? <userTask id="A" name="多實例測試" flowable:candidateUsers="shareniu1,shareniu2,shareniu3,shareniu4" xmlns:shareniu="http://www.shareniu.com/" shareniu:shareniuext="" shareniu:shareniuextboolen="false">
? ? ? <multiInstanceLoopCharacteristics isSequential="true">
? ? ? ? <loopCardinality>2</loopCardinality>
? ? ? </multiInstanceLoopCharacteristics>
? ? </userTask>
? ? <sequenceFlow id="sid-6D482A8A-F796-4E2B-AB2C-ABBFD2DA428D" sourceRef="startEvent1" targetRef="A"></sequenceFlow>
? ? <userTask id="shareniu-B" name="shareniu-B" xmlns:shareniu="http://www.shareniu.com/" shareniu:shareniuext="" shareniu:shareniuextboolen="false"></userTask>
? ? <sequenceFlow id="sid-BA8FC337-40DC-493B-805C-F213B7C4A17D" sourceRef="A" targetRef="shareniu-B"></sequenceFlow>
? ? <endEvent id="sid-4AC81F5B-49F8-4135-B68E-1C182D004080"></endEvent>
? ? <sequenceFlow id="sid-DF962BB4-EA4C-4D86-9A91-214EB2EC1A47" sourceRef="shareniu-B" targetRef="sid-4AC81F5B-49F8-4135-B68E-1C182D004080"></sequenceFlow>
? </process>
? <bpmndi:BPMNDiagram id="BPMNDiagram_multiInstance">
? ? <bpmndi:BPMNPlane bpmnElement="multiInstance" id="BPMNPlane_multiInstance">
? ? ? <bpmndi:BPMNShape bpmnElement="startEvent1" id="BPMNShape_startEvent1">
? ? ? ? <omgdc:Bounds height="30.0" width="30.0" x="100.0" y="130.0"></omgdc:Bounds>
? ? ? </bpmndi:BPMNShape>
? ? ? <bpmndi:BPMNShape bpmnElement="A" id="BPMNShape_A">
? ? ? ? <omgdc:Bounds height="80.0" width="100.0" x="165.0" y="105.0"></omgdc:Bounds>
? ? ? </bpmndi:BPMNShape>
? ? ? <bpmndi:BPMNShape bpmnElement="shareniu-B" id="BPMNShape_shareniu-B">
? ? ? ? <omgdc:Bounds height="80.0" width="100.0" x="315.0" y="120.0"></omgdc:Bounds>
? ? ? </bpmndi:BPMNShape>
? ? ? <bpmndi:BPMNShape bpmnElement="sid-4AC81F5B-49F8-4135-B68E-1C182D004080" id="BPMNShape_sid-4AC81F5B-49F8-4135-B68E-1C182D004080">
? ? ? ? <omgdc:Bounds height="28.0" width="28.0" x="442.0" y="146.0"></omgdc:Bounds>
? ? ? </bpmndi:BPMNShape>
? ? ? <bpmndi:BPMNEdge bpmnElement="sid-DF962BB4-EA4C-4D86-9A91-214EB2EC1A47" id="BPMNEdge_sid-DF962BB4-EA4C-4D86-9A91-214EB2EC1A47">
? ? ? ? <omgdi:waypoint x="414.9499999999902" y="160.0"></omgdi:waypoint>
? ? ? ? <omgdi:waypoint x="442.0" y="160.0"></omgdi:waypoint>
? ? ? </bpmndi:BPMNEdge>
? ? ? <bpmndi:BPMNEdge bpmnElement="sid-BA8FC337-40DC-493B-805C-F213B7C4A17D" id="BPMNEdge_sid-BA8FC337-40DC-493B-805C-F213B7C4A17D">
? ? ? ? <omgdi:waypoint x="264.95000000000005" y="145.0"></omgdi:waypoint>
? ? ? ? <omgdi:waypoint x="290.0" y="145.0"></omgdi:waypoint>
? ? ? ? <omgdi:waypoint x="290.0" y="160.0"></omgdi:waypoint>
? ? ? ? <omgdi:waypoint x="314.99999999998477" y="160.0"></omgdi:waypoint>
? ? ? </bpmndi:BPMNEdge>
? ? ? <bpmndi:BPMNEdge bpmnElement="sid-6D482A8A-F796-4E2B-AB2C-ABBFD2DA428D" id="BPMNEdge_sid-6D482A8A-F796-4E2B-AB2C-ABBFD2DA428D">
? ? ? ? <omgdi:waypoint x="129.94999817301806" y="145.0"></omgdi:waypoint>
? ? ? ? <omgdi:waypoint x="165.0" y="145.0"></omgdi:waypoint>
? ? ? </bpmndi:BPMNEdge>
? ? </bpmndi:BPMNPlane>
? </bpmndi:BPMNDiagram>
?
</definitions>
<xml配置文件的部分含義如下:
1.flowable:candidateUsers="shareniu1,shareniu2,shareniu3,shareniu4"?這個節點可以4個人審核。
2.<loopCardinality>2</loopCardinality>?循環2次結束。
3.<multiInstanceLoopCharacteristics isSequential="true">?串行并行的配置。
1.1.2.1.?串行的配置
修改<multiInstanceLoopCharacteristics isSequential="true">?中的isSequential為true是串行,isSequential為false是并行。我們測試串行。下面的代碼展示啟動流程因為是以一個節點所以部署啟動后,直接進入多實例任務。
1.1.2.1.1.?流程的部署
????????@Test
public?void?addBytes() {
byte[]?bytes?= IoUtil.readInputStream(
ProcessengineTest.class.getClassLoader()
.getResourceAsStream("com/shareniu/shareniu_flowable_study/bpmn/ch3/multiInstance.bpmn20.xml"),
"multiInstance.bpmn20.xml");
String?resourceName?=?"multiInstance.bpmn";
Deployment?deployment?=?repositoryService.createDeployment().addBytes(resourceName,?bytes).deploy();
System.out.println(deployment);
}
1.1.2.1.2.?流程的啟動
? ? ????@Test
public?void?startProcessInstanceByKey() {
String?processDefinitionKey?=?"multiInstance";
ProcessInstance?startProcessInstanceByKey?=?runtimeService.startProcessInstanceByKey(processDefinitionKey);
System.out.println(startProcessInstanceByKey);
?
}
我們按照上面的步驟啟動一個流程看一下數據庫的變化。
ACT_RU_TASK表的數據有了如下圖所示:
ACT_RU_IDENTITYLINK表的數據有了如下圖所示:
?
ACT_RU_IDENTITYLINK權限表中,確實把我們設置的人員信息設置進去了,shareniu1,shareniu2,shareniu3,shareniu4現在就有代辦信息了。
ACT_RU_VARIABLE表的數據有了如下圖示:
?
上面重要的變量需要解釋一下,要不然還真不好理解,多任務是怎么運轉的。
1.nrOfInstances ?實例總數。
2.nrOfCompletedInstances ?當前還沒有完成的實例?nr是number單詞縮寫 。
3.loopCounter?已經循環的次數。
4.nrOfActiveInstances 已經完成的實例個數。
?
下面我們結束一個任務看一下,流程走到那個節點了。
1.1.2.1.3.?完成任務????
????????@Test
public?void?complete() {
String?taskId="15011";
taskService.complete(taskId);
?
}
接下來看一下數據庫表的變化。
ACT_RU_VARIABLE表的數據有了如下圖所示:
?
上面我們仔細的發現,可以看到
nrOfCompletedInstances、loopCounter、nrOfActiveInstances都加1了,確實多任務就是參考這幾個值的變化進行判斷的。
因為我們設置了循環2次,所以我們看看ACT_RU_IDENTITYLINK還有一個任務,因為我們是并行處理的。
所以我們在結束新的任務看一下流程是不是真的結束了,如果結束了,那么我們循環次數的配置就是正確的。
1.1.2.1.4.?完成任務
????????@Test
public?void?complete() {
String?taskId="17502";
taskService.complete(taskId);
?
}
下面看一下ACT_RU_TASK,里面沒有任務信息了,所以側面證明循環次數的配置就是正確的。
接下來我們測試并行任務。除了isSequential="false",其他的配置是一樣的。
1.1.2.2.?并行的配置測試
除了isSequential="false",其他的配置跟上面的串行是一樣一樣的。
重新部署測試,部署后我們啟動一個新的流程測試。
ACT_RU_TASK表如下:
?
一次性的有2個任務需要處理,因為我們循環的是2次,所以直接就是2個。
ok串行、并行就講解到這里。
1.1.3.?串行、并行總結
我們配置的是循環2次,看以看到不管是并行還是串行,兩個代辦任務結束之后,流程直接跳轉到下一個狀態,但是
我們并沒有配置結束條件,所以上面的例子,也可以看出來,如果不配置默認的通過條件,則默認條件是1,后面的源碼章節會給大家說明這一點的。
1.1.4.?通過條件的配置
在上面的串行、并行實例中,我們沒有設置通過條件,但是程序按照配置的循環的次數,然后跳轉到了下一個狀態,可以側面印證,如果不配置通過條件則默認值就是1.?
下面的代碼詳細的介紹通過條件的配置,具體的配置代碼如下:
?
<process?id="multiInstance"?name="multiInstance"
isExecutable="true">
<documentation>multiInstance</documentation>
<startEvent?id="startEvent1"></startEvent>
<userTask?id="A"?name="多實例測試"
flowable:candidateUsers="shareniu1,shareniu2,shareniu3,shareniu4">
<multiInstanceLoopCharacteristics
isSequential="true">
<loopCardinality>4</loopCardinality>
<completionCondition>${nrOfCompletedInstances/nrOfInstances >= 0.25}</completionCondition>
</multiInstanceLoopCharacteristics>
</userTask>
<sequenceFlow
id="sid-6D482A8A-F796-4E2B-AB2C-ABBFD2DA428D"?sourceRef="startEvent1"
targetRef="A"></sequenceFlow>
<userTask?id="shareniu-B"?name="shareniu-B"
xmlns:shareniu="http://www.shareniu.com/"?shareniu:shareniuext=""
shareniu:shareniuextboolen="false"></userTask>
<sequenceFlow
id="sid-BA8FC337-40DC-493B-805C-F213B7C4A17D"?sourceRef="A"
targetRef="shareniu-B"></sequenceFlow>
<endEvent?id="sid-4AC81F5B-49F8-4135-B68E-1C182D004080"></endEvent>
<sequenceFlow
id="sid-DF962BB4-EA4C-4D86-9A91-214EB2EC1A47"?sourceRef="shareniu-B"
targetRef="sid-4AC81F5B-49F8-4135-B68E-1C182D004080"></sequenceFlow>
</process>
?
1.1.4.1.?配置描述
<completionCondition>${nrOfCompletedInstances/nrOfInstances >= 0.25}</completionCondition>
nrOfCompletedInstances、nrOfInstances 變量描述上面已經描述了,我們這里設置的條件是大于1/4的人完成任務,任務就結束了。下面我們代碼部署流程,啟動流程后進行測試:
ACT_RU_TASK表如下:
我們隨便結束一個任務,看一下ACT_RU_TASK表變化。
????????@Test
public?void?complete() {
String?taskId="40003";
taskService.complete(taskId);
?
}
執行上面的代碼,我們很神奇的發現,ACT_RU_TASK表中的其他任務沒有了,因為我們配置了4個人,通過條件是1/4,所以任意一個人結束了,流程就結束了。這里我們測試的是并行,串行也是一樣的,讀者可以自行測試驗證。
1.1.5.?動態的配置
上面的幾種方式,我們定義xml的時候,循環的次數是固定寫在xml中的,也就是說我們配置的是循環2次,那么所有的流程實例都是循環2次,這樣就不靈活了,程序當然是靈活了比較好,所以在實際開發中,我們可以使用下面的這種方式操作,使程序更加的靈活。
程序的xml配置如下所示:
?
?
-
<process?id="multiInstance"?name="multiInstance"
isExecutable="true">
<documentation>multiInstance</documentation>
<startEvent?id="startEvent1"></startEvent>
<userTask?id="A"?name="多實例測試"?flowable:assignee="${assignee}">
<multiInstanceLoopCharacteristics
isSequential="false"?flowable:collection="assigneeList"
flowable:elementVariable="assignee">
<completionCondition>${nrOfCompletedInstances/nrOfInstances >= 0.25}
</completionCondition>
</multiInstanceLoopCharacteristics>
</userTask>
<sequenceFlow
id="sid-6D482A8A-F796-4E2B-AB2C-ABBFD2DA428D"?sourceRef="startEvent1"
targetRef="A"></sequenceFlow>
<userTask?id="shareniu-B"?name="shareniu-B"
xmlns:shareniu="http://www.shareniu.com/"?shareniu:shareniuext=""
shareniu:shareniuextboolen="false"></userTask>
<sequenceFlow
id="sid-BA8FC337-40DC-493B-805C-F213B7C4A17D"?sourceRef="A"
targetRef="shareniu-B"></sequenceFlow>
<endEvent?id="sid-4AC81F5B-49F8-4135-B68E-1C182D004080"></endEvent>
<sequenceFlow
id="sid-DF962BB4-EA4C-4D86-9A91-214EB2EC1A47"?sourceRef="shareniu-B"
targetRef="sid-4AC81F5B-49F8-4135-B68E-1C182D004080"></sequenceFlow>
</process>
- ?
?
動態配置如下所示:
<userTask id="usertask1" name="多實例任務"?flowable:assignee="${assignee}">
??????<multiInstanceLoopCharacteristics isSequential="false" activiti:collection="assigneeList" activiti:elementVariable="assignee">
????????<completionCondition>${nrOfCompletedInstances/nrOfInstances >= 0.25}</completionCondition>
??????</multiInstanceLoopCharacteristics>
</userTask>
參數說明:
activiti:assignee="${assignee}"
activiti:elementVariable="assignee"?多實例任務依賴上面的配置${assignee}
activiti:collection="assigneeList"?
三個參數結合決定了,當前節點的處理人來自assigneeList集合,注意這里是集合信息而不是字符串,所以程序的運行時候變量的賦值,如下所示:
????????@Test
public?void?startProcessInstanceByKey() {
Map<String, Object>?vars?=?new?HashMap<>();
String[]?v?= {?"shareniu1",?"shareniu2",?"shareniu3",?"shareniu4"?};
vars.put("assigneeList", Arrays.asList(v));
String?processDefinitionKey?=?"multiInstance";
ProcessInstance?startProcessInstanceByKey?=?runtimeService.startProcessInstanceByKey(processDefinitionKey,
vars);
?
Sys
ok了,測試一下,確實程序如預期的所示,大功告成。
1.1.6.?總結
參數的使用總結
4.flowable:candidateUsers="shareniu1,shareniu2,shareniu3,shareniu4"?這個節點可以4個人審核。
5.<loopCardinality>2</loopCardinality>?循環2次結束。
6.<multiInstanceLoopCharacteristics isSequential="true">?串行并行的配置。
?<completionCondition>${nrOfCompletedInstances/nrOfInstances?>= 0.25}</completionCondition>?完成條件的配置。
這里我們還可以得出一個結論:
如果使用串行方式操作nrOfActiveInstances 變量始終是1,因為并行的時候才會去+1操作。
1.1.7.?遺留點
上面的程序已經解決了常用的問題,關于會簽、加簽、減簽、退簽、權重配置、自定義通過條件配置(條件自定義通過)