我們在使用工作流的時候,常常有“流程退回”、“流程跳轉”、“自由流”、“動態加簽”等這樣的需求。Camunda流程平臺提供了這樣的機制和介面,雖然流程模型定義活動執行順序的序列流,但有時需要靈活地重新啟動活動或取消正在執行的活動,進而可以實現中國特色的流程需求。文字重點講如何使用camunda的API介面實現流程跳轉、流程退回的需求,另外還可能適用的場景有:
- 實現中國特色流程操作,包括:退回申請人、退回上一步、任意退回、流程跳轉、流程撤銷、動態增加活動等;
- 修復必須重複或跳過某些步驟的流程例項;
- 將流程例項從一個版本的流程定義遷移到另一個版本;
- 測試:可以跳過或重複活動,以對單個工藝段進行孤立測試;
比如:雲程低程式碼平臺,可以透過介面配置實現流程操作的特殊需求:
線上體驗系統:http://www.yunchengxc.com
為了執行這樣的操作,流程引擎提供了透過RuntimeService.createProcessInstanceModification(…)或RuntimeService.createModification…輸入的流程例項修改API。這個API允許透過使用fluent生成器在一個呼叫中指定多個修改指令。特別是,可以:
- 在activity活動之前開始執行
- 在離開activity活動的序列流上開始執行
- 取消正在執行的 Activity 例項
- 取消給定activity活動的所有正在執行的例項
- 使用每個指令設定變數
1、透過一個示例介紹如何動態修改流程例項
例如,請考慮以下BPMN流程模型:
該模型顯示了處理貸款申請的簡單流程。讓我們假設一個貸款申請已經到達,貸款申請已經過評估,並且決定拒絕該申請。這意味著流程例項具有以下活動例項狀態:
ProcessInstance
Decline Loan Application
現在,執行任務“拒絕貸款申請”的工作人員認識到評估結果中的錯誤,並得出結論,儘管如此,該申請仍應被接受。雖然這種靈活性不是作為流程的一部分建模的,但流程例項修改允許更正正在執行的流程例項。下面的API呼叫實現了這個技巧::
ProcessInstance processInstance = runtimeService.createProcessInstanceQuery().singleResult();
runtimeService.createProcessInstanceModification(processInstance.getId())
.startBeforeActivity("acceptLoanApplication")
.cancelAllForActivity("declineLoanApplication")
.execute();
此命令首先在活動Accept Loan Application之前開始執行,直到達到等待狀態(在本例中為使用者任務的建立)。之後,它將取消活動“拒絕貸款申請”的執行例項。在工作人員的任務列表中,“拒絕”任務已被刪除,並顯示“接受”任務。生成的活動例項狀態為:
ProcessInstance
Accept Loan Application
假設在批准應用程式時必須存在一個名為approver的變數。這可以透過如下擴充套件修改請求來實現:
ProcessInstance processInstance = runtimeService.createProcessInstanceQuery().singleResult();
runtimeService.createProcessInstanceModification(processInstance.getId())
.startBeforeActivity("acceptLoanApplication")
.setVariable("approver", "joe")
.cancelAllForActivity("declineLoanApplication")
.execute();
新增的setVariable呼叫確保在啟動活動之前提交指定的變數。
現在來看一些更復雜的案例。假設申請再次不正常,並且“拒絕貸款申請”活動處於活動狀態。現在,工人意識到評估流程是錯誤的,並希望完全重新啟動。以下修改說明表示執行此任務的修改請求:
可以啟動子流程活動:
ProcessInstance processInstance = runtimeService.createProcessInstanceQuery().singleResult();
runtimeService.createProcessInstanceModification(processInstance.getId())
.cancelAllForActivity("declineLoanApplication")
.startBeforeActivity("assessCreditWorthiness")
.startBeforeActivity("registerApplication")
.execute();
要從子流程的 start 事件開始,請執行以下操作:
ProcessInstance processInstance = runtimeService.createProcessInstanceQuery().singleResult();
runtimeService.createProcessInstanceModification(processInstance.getId())
.cancelAllForActivity("declineLoanApplication")
.startBeforeActivity("subProcessStartEvent")
.execute();
要啟動子流程本身,請執行以下操作:
ProcessInstance processInstance = runtimeService.createProcessInstanceQuery().singleResult();
runtimeService.createProcessInstanceModification(processInstance.getId())
.cancelAllForActivity("declineLoanApplication")
.startBeforeActivity("evaluateLoanApplication")
.execute();
要啟動流程的 start 事件,請執行以下操作:
ProcessInstance processInstance = runtimeService.createProcessInstanceQuery().singleResult();
runtimeService.createProcessInstanceModification(processInstance.getId())
.cancelAllForActivity("declineLoanApplication")
.startBeforeActivity("processStartEvent")
.execute();
1.1、流程跳轉的實現方法
流程例項執行流程中,經常遇到特殊業務需求,需要跳過某些流程活動節點。為此,您可以啟動一個帶有修改的流程例項,並將令牌直接放在該流程例項中。
假設您想跳過子流程 Evaluate Loan Application 並測試閘道器 Application OK? 使用您的流程變數,您可以使用以下命令啟動流程例項
ProcessInstance processInstance = runtimeService.createProcessInstanceByKey("Loan_Application")
.startBeforeActivity("application_OK")
.setVariable("approved", true)
.execute();
此方法也可以用在JUnit測試中,可以跳過前面流程部分,僅僅關注要測試的流程活動。
2、操作語義
以下各節指定了流程例項修改的確切語義,應閱讀這些語義,以便了解不同情況下的修改效果。如果沒有另行說明,以下示例將參考以下流程模型進行說明:
2.1、修改指令型別
Fluent 流程例項修改生成器提供以下方法介面:
- startBeforeActivity(String activityId)
- startBeforeActivity(String activityId, String ancestorActivityInstanceId)
- startAfterActivity(String activityId)
- startAfterActivity(String activityId, String ancestorActivityInstanceId)
- startTransition(String transitionId)
- startTransition(String transition, String ancestorActivityInstanceId)
- cancelActivityInstance(String activityInstanceId)
- cancelTransitionInstance(String transitionInstanceId)
- cancelAllForActivity(String activityId)
2.1.1、在活動開始之前開始
ProcessInstanceModificationBuilder#startBeforeActivity(String activityId)
ProcessInstanceModificationBuilder#startBeforeActivity(String activityId, String ancestorActivityInstanceId)
透過startBeforeActivity在活動之前啟動意味著在進入活動之前就開始執行。該指令尊重asyncBefore標誌,這意味著如果活動是asyncBeBefore,則將建立作業。通常,此指令從指定的活動開始執行流程模型,直到達到等待狀態。有關等待狀態的詳細資訊,請參閱“流程中的事務”文件。
2.1.2、活動後開始
ProcessInstanceModificationBuilder#startAfterActivity(String activityId)
ProcessInstanceModificationBuilder#startAfterActivity(String activityId, String ancestorActivityInstanceId)
透過startAfterActivity在活動之後啟動意味著在活動的單個傳出序列流上開始執行。該指令不考慮給定活動的asyncAfter標誌。如果有多個傳出序列流,或者根本沒有,則指令失敗。如果成功,此指令將從序列流開始執行流程模型,直到達到等待狀態。
2.1.3、開始流轉
ProcessInstanceModificationBuilder#startTransition(String transitionId)
ProcessInstanceModificationBuilder#startTransition(String transition, String ancestorActivityInstanceId)
透過startTransition啟動轉換轉換為在給定的序列流上開始執行。當存在多個傳出序列流時,這可以與startAfterActivity一起使用。如果成功,此指令將從序列流開始執行流程模型,直到達到等待狀態。
2.1.4、取消活動例項
ProcessInstanceModificationBuilder#cancelActivityInstance(String activityInstanceId)
cancelActivityInstance可以取消特定的活動例項。這可以是葉活動例項,例如使用者任務的例項,也可以是層次結構中更高範圍的例項,例如子流程的例項。請參閱有關活動例項的詳細資訊—如何檢索流程例項的活動例項。
2.1.5、取消轉換例項
ProcessInstanceModificationBuilder#cancelTransitionInstance(String activityInstanceId)
轉換例項表示即將以非同步延續的形式進入/離開活動的執行流。已建立但尚未執行的非同步延續作業表示為轉換例項。cancelTransitionInstance可以取消這些例項。請參閱有關活動和轉換例項的詳細資訊—如何檢索流程例項的轉換例項。
2.1.6、取消活動的所有活動例項
ProcessInstanceModificationBuilder#cancelAllForActivity(String activityId)
為了方便起見,還可以透過指令cancelAllForActivity取消給定活動的所有活動和轉換例項。
2.2、提供或設定變數
對於每個例項化指令(即startBeforeActivity、startAfterActivity或startTransition),都可以提交流程變數。API提供了方法:
- setVariable(String name, Object value)
- setVariables(Map<String, Object> variables)
- setVariableLocal(String name, Object value)
- setVariablesLocal(Map<String, Object> variables)
變數是在建立例項化所需的作用域之後、指定元素的實際執行開始之前設定的。這意味著,在流程引擎歷史記錄中,這些變數看起來不像是在執行startBefore和startAfter指令的指定活動期間設定的。在即將執行指令(即進入活動等)的執行上設定區域性變數。
2.3、基於活動例項的 API
流程例項修改API基於活動例項。流程例項的活動例項樹可以透過以下方法進行檢索:
ProcessInstance processInstance = ...;
ActivityInstance activityInstance = runtimeService.getActivityInstance(processInstance.getId());
ActivityInstance是一個遞迴資料結構,上面方法呼叫返回的活動例項表示流程例項。ActivityInstance物件的ID可用於取消特定例項或用於例項化期間的祖先選擇。
介面ActivityInstance具有方法getChildActivityInstances和getChildTransitionInstances以在活動例項樹中進行深入搜尋。例如,假設活動“評估信用價值”和“註冊申請”處於活動狀態。然後活動例項樹如下所示:
ProcessInstance
Evaluate Loan Application
Assess Credit Worthiness
Register Application Request
在程式碼中,可以按如下方式檢索 Assess 和 Register 活動例項:
ProcessInstance processInstance = ...;
ActivityInstance activityInstance = runtimeService.getActivityInstance(processInstance.getId());
ActivityInstance subProcessInstance = activityInstance.getChildActivityInstances()[0];
ActivityInstance[] leafActivityInstances = subProcessInstance.getChildActivityInstances();// leafActivityInstances has two elements; one for each activity
還可以直接檢索給定活動的所有活動例項:
ProcessInstance processInstance = ...;
ActivityInstance activityInstance = runtimeService.getActivityInstance(processInstance.getId());
ActivityInstance assessCreditWorthinessInstances = activityInstance.getActivityInstances("assessCreditWorthiness")[0];
與活動例項相比,轉換例項並不表示活動的活動,而是表示即將進入或即將離開的活動。當存在非同步延續的作業但尚未執行時,就會出現這種情況。對於活動例項,可以使用getChildTransitionInstances方法檢索子轉換例項,轉換例項的API與活動例項的類似。
2.4、巢狀例項化
假設上面示例流程的一個流程例項,其中活動“拒絕貸款申請”處於活動狀態。現在,我們提交指示,以便在活動評估信用價值之前開始。應用此指令時,流程引擎確保例項化所有尚未啟用的父作用域。在這種情況下,在啟動活動之前,流程引擎會例項化Evaluate Loan Application子流程。在活動例項樹之前的位置
ProcessInstance
Decline Loan Application
現在是
ProcessInstance
Decline Loan Application
Evaluate Loan Application
Assess Credit Worthiness
除了例項化這些父作用域,引擎還確保在這些作用域中註冊事件訂閱和作業。例如,考慮以下流程:
啟動活動“評估信用價值”還會註冊訊息邊界事件“收到的取消通知”的事件訂閱,以便可以透過這種方式取消子流程。
2.5、例項化的祖先選擇
預設情況下,啟動活動會例項化所有尚未例項化的父作用域。當活動例項樹如下時:
ProcessInstance
Decline Loan Application
然後,啟動“評估信用價值”(Assess Credit Worthiness)會在以下更新的樹中生成:
ProcessInstance
Decline Loan Application
Evaluate Loan Application
Assess Credit Worthiness
子流程範圍也已例項化。現在假設子流程已經例項化,如下圖所示:
ProcessInstance
Evaluate Loan Application
Assess Credit Worthiness
再次啟動 Assess Credit Worthiness 將在現有子流程例項的上下文中啟動它,因此生成的樹為:
ProcessInstance
Evaluate Loan Application
Assess Credit Worthiness
Assess Credit Worthiness
如果您想避免這種行為,而是想第二次例項化子流程,則可以使用方法startBeforeActivity(String activityId,String ancestorActivityInstanceId)來提供祖先活動例項的id-類似的方法用於在活動之後啟動和啟動轉換。引數ancestorActivityInstanceId採用當前活動的活動例項的id,該活動例項屬於要啟動的活動的祖先活動。如果一個活動包含要啟動的活動(直接或間接與其間的其他活動一起),那麼它就是一個有效的祖先。
對於給定的祖先活動例項id,祖先活動和要啟動的活動之間的所有作用域都將被例項化,而不管它們是否已經被例項化。在該示例中,以下程式碼以流程例項(作為根活動例項)作為祖先啟動活動“評估信用價值”:
ProcessInstance processInstance = ...;
ActivityInstance activityInstanceTree = runtimeService.getActivityInstance(processInstance.getId());
runtimeService.createProcessInstanceModification(activityInstanceTree.getId())
.startBeforeActivity("assessCreditWorthiness", processInstance.getId())
.execute();
然後,生成的活動例項樹如下所示:
ProcessInstance
Evaluate Loan Application
Assess Credit Worthiness
Evaluate Loan Application
Assess Credit Worthiness
第二次啟動了子流程。
2.6、取消傳播
取消活動例項將傳播到不包含其他活動例項的父活動例項。此行為可確保流程例項不會處於毫無意義的執行狀態。這意味著,當單個活動在子流程中處於活動狀態並且該活動例項被取消時,子流程也會被取消。請考慮以下活動例項樹:
ProcessInstance
Decline Loan Application
Evaluate Loan Application
Assess Credit Worthiness
取消“評估信用價值”的活動例項後,樹為:
ProcessInstance
Decline Loan Application
如果所有指令都已執行,並且沒有活動活動例項,則整個流程例項將被取消。在上面的示例中,如果兩個活動例項都被取消,一個是評估信用價值,另一個是拒絕貸款申請。
但是,只有在執行完所有指令後,才會取消流程例項。這意味著,如果流程例項在兩條指令之間沒有活動活動例項,則不會立即取消流程例項。例如,假設活動“拒絕貸款申請”處於活動狀態。活動例項樹為:
ProcessInstance
Decline Loan Application
儘管在執行取消指令後,流程例項沒有活動活動例項,但以下修改操作將成功:
ProcessInstance processInstance = ...;
runtimeService.createProcessInstanceModification(processInstance.getId())
.cancelAllForActivity("declineLoanApplication")
.startBeforeActivity("acceptLoanApplication")
.execute();
2.7、指令執行順序
修改指令始終按提交順序執行。因此,以不同的順序執行相同的指令可能會有所不同。請考慮以下活動例項樹:
ProcessInstance
Evaluate Loan Application
Assess Credit Worthiness
假設您的任務是取消“評估信用價值”的例項並啟動活動“註冊應用程式”。這兩條指令有兩種排序方式:要麼先執行取消,要麼先執行例項化。在前一種情況下,程式碼如下所示:
ProcessInstance processInstance = ...;
runtimeService.createProcessInstanceModification(processInstance.getId())
.cancelAllForActivity("assesCreditWorthiness")
.startBeforeActivity("registerApplication")
.execute();
由於取消傳播,子流程例項在執行取消指令時被取消,僅在執行例項化指令時重新例項化。這意味著,在執行修改後,“評估貸款申請”子流程將出現一個不同的例項。與上一個例項關聯的任何實體都已被刪除,例如變數或事件訂閱。
相比之下,請考慮首先執行例項化的情況:
ProcessInstance processInstance = ...;
runtimeService.createProcessInstanceModification(processInstance.getId())
.startBeforeActivity("registerApplication")
.cancelAllForActivity("assesCreditWorthiness")
.execute();
由於例項化期間的預設祖先選擇,以及在這種情況下取消不會傳播到子流程例項這一事實,因此子流程例項在修改後與之前相同。將保留相關實體(如變數和事件訂閱)。
2.8、使用中斷/取消語義開始活動
流程例項修改尊重要啟動的活動的任何中斷或取消語義。特別是,啟動中斷邊界事件或中斷事件子流程將取消/中斷在中定義的活動。請考慮以下流程:
假設活動“評估信用價值”當前處於活動狀態。事件子流程可以使用以下程式碼啟動:
ProcessInstance processInstance = ...;
runtimeService.createProcessInstanceModification(processInstance.getId())
.startBeforeActivity("cancelEvaluation")
.execute();
由於“取消評估”子流程的啟動事件正在中斷,因此它將取消“評估信用價值”的執行例項。當事件子流程的啟動事件透過以下方式啟動時,也會發生同樣的情況:
ProcessInstance processInstance = ...;
runtimeService.createProcessInstanceModification(processInstance.getId())
.startBeforeActivity("eventSubProcessStartEvent")
.execute();
但是,當位於事件子流程中的活動直接啟動時,不會執行中斷。請考慮以下程式碼:
ProcessInstance processInstance = ...;
runtimeService.createProcessInstanceModification(processInstance.getId())
.startBeforeActivity("notifyAccountant")
.execute();
生成的活動例項樹將為:
ProcessInstance
Evaluate Loan Application
Assess Credit Worthiness
Cancel Evaluation
Notify Accountant
2.9、修改多例項活動例項
修改也適用於多例項活動。我們在下文中區分了多例項主體和內部活動。內部活動是實際活動,具有流程模型中宣告的ID。多例項主體是圍繞此活動的一個範圍,在流程模型中不作為不同的元素表示。對於id為anActivityId的活動,多例項主體按照約定具有id為anActivity id#multiInstanceBody。
有了這種區別,就可以啟動整個多例項主體,也可以為正在執行的並行多例項活動啟動單個內部活動例項。考慮以下流程模型:
假設多例項活動處於活動狀態,並且有三個例項:
ProcessInstance
Contact Customer - Multi-Instance Body
Contact Customer
Contact Customer
Contact Customer
以下修改將在同一多例項正文活動中啟動“聯絡客戶”活動的第四個例項:
ProcessInstance processInstance = ...;
runtimeService.createProcessInstanceModification(processInstance.getId())
.startBeforeActivity("contactCustomer")
.execute();
生成的活動例項樹為:
ProcessInstance
Contact Customer - Multi-Instance Body
Contact Customer
Contact Customer
Contact Customer
Contact Customer
流程引擎確保正確更新與多例項相關的變數nrOfInstances、nrOfActiveInstances和loopCounter。如果基於集合配置多例項活動,則在執行指令時不考慮該集合,並且不會為附加例項填充集合元素變數。這樣的行為可以透過使用方法#setVariableLocal向集合元素變數提供例項化指令來實現。
現在考慮以下請求:
ProcessInstance processInstance = ...;
runtimeService.createProcessInstanceModification(processInstance.getId())
.startBeforeActivity("contactCustomer#multiInstanceBody")
.execute();
這將再次啟動整個多例項正文,從而導致以下活動例項樹:
ProcessInstance
Contact Customer - Multi-Instance Body
Contact Customer
Contact Customer
Contact Customer
Contact Customer
Contact Customer - Multi-Instance Body
Contact Customer
Contact Customer
Contact Customer
2.10、流程例項的非同步修改
可以非同步執行單個流程例項的修改。修改指令與同步修改相同,Fluent Builder 的語法如下:
Batch modificationBatch = runtimeService.createProcessInstanceModification(processInstanceId)
.cancelActivityInstance("exampleActivityId:1")
.startBeforeActivity("exampleActivityId:2")
.executeAsync();
這將建立一個非同步執行的修改批處理。 在執行單個流程例項的非同步修改時,不支援提供變數。
2.11、修改多個流程例項
當有多個流程例項滿足特定條件時,可以使用RuntimeService.createModification(…)一次修改它們。此方法允許指定應修改的流程例項的修改指令和ID。流程例項必須屬於給定的流程定義。
fluent修改生成器提供了以下待提交的說明:
- startBeforeActivity(String activityId)
- startAfterActivity(String activityId)
- startTransition(String transitionId)
- cancelAllForActivity(String activityId)
可以透過提供一組流程例項 ID 或提供流程例項查詢來選擇流程例項進行修改。 也可以同時指定流程例項 ID 列表和查詢。然後,要修改的流程例項將是結果集的並集。
ProcessInstanceQuery processInstanceQuery = runtimeService.createProcessInstanceQuery();
runtimeService.createModification("exampleProcessDefinitionId")
.cancelAllForActivity("exampleActivityId:1")
.startBeforeActivity("exampleActivityId:2")
.processInstanceIds(processInstanceQuery)
.processInstanceIds("processInstanceId:1", "processInstanceId:2")
.execute();
可以同步或非同步執行多個流程例項的修改。
同步執行示例:
runtimeService.createModification("exampleProcessDefinitionId")
.cancelAllForActivity("exampleActivityId:1")
.startBeforeActivity("exampleActivityId:2")
.processInstanceIds("processInstanceId:1", "processInstanceId:2")
.execute();
非同步執行示例:
Batch batch = runtimeService.createModification("exampleProcessDefinitionId")
.cancelAllForActivity("exampleActivityId:1")
.startBeforeActivity("exampleActivityId:2")
.processInstanceIds("processInstanceId:1", "processInstanceId:2", "processInstanceId:100")
.executeAsync();
2.12、跳過偵聽器和輸入/輸出呼叫
可以跳過執行和任務偵聽器的呼叫,以及執行修改的事務的輸入/輸出對映。當在無法訪問相關流程應用程式部署及其包含的類的系統上執行修改時,這可能非常有用。可以使用修改生成器的execute方法(boolean skipCustomListeners,boolean skipIoMappings)跳過Listener和ioMapping呼叫。
使用註釋選項可以出於稽核原因傳遞任意文字註釋。
runtimeService.createProcessInstanceModification(processInstanceId)
.cancelAllForActivity("declineLoanApplication")
.startBeforeActivity("processStartEvent")
.annotation("Modified to resolve an error.")
.execute();
它將在使用者操作日誌中顯示,用於執行的修改。
2.13、健全性檢查
流程例項修改是一個非常強大的工具,允許隨意啟動和取消活動。因此,很容易建立正常流程執行無法到達的情況。假設以下流程模型:
假設活動“拒絕貸款審批”處於活動狀態。經過修改後,可以開始活動“評估信用價值”。在該活動完成後,執行被困在加入的並行閘道器,因為沒有令牌會到達其他傳入序列流,從而啟用並行閘道器。這是流程例項無法繼續執行的最明顯情況之一,當然還有許多其他情況,具體取決於具體的流程模型。
流程引擎無法檢測到造成這種情況的修改。此API的使用者有權進行修改,使流程例項不會處於不需要的狀態。然而,流程例項修改也是修復這些情況的工具。
更詳細請參考camunda官方文件:https://docs.camunda.org/manual/7.19/user-guide/process-engine/process-instance-modification/
流程會籤實現思路和原理:https://blog.csdn.net/wxz258/article/details/118055189