基于Camunda實現bpmn中各種類型任務
? Camunda Modeler -為流程設置器(建模工具),用來構建我們的流程模型。Camunda Modeler流程繪圖工具,支持三種協議類型流程文件分別為:BPMN、DMN、Form。
? Camunda Modeler下載地址:https://camunda.com/download/modeler/
? 繼 springboot集成Camunda審核流程(二):Camunda Modeler設計器設置BPMN流程 繼續對流程設計器以及bpmn協議相關知識進行記錄擴展。本文章主要記錄 bpmn 協議中的各種類型的任務基于Camunda流程控制引擎的實現方式!
? 在bpmn協議中常用的任務類型主要有:用戶任務(user task)、服務任務(service task)、腳本任務(script task)、發送任務(send task)、接收任務(receice task) …等。
備注:其它功能的使用鋪墊
一、Camunda 控制臺用戶功能
? Camunda流程引擎內置了用戶管理相關的操作,可以通過Camunda內置控制臺創建對應的用戶組、用戶等操作,后續可以直接通過內置的控制臺實現對流程的審核過程!
? 配置依賴見文: springboot集成Camunda審核流程(一):Springboot集成配置
? 可以通過Camunda 控制臺 配置用戶信息、用戶組信息,同時給對應的用戶組分配系統的操作權限等功能。
? 這里就不詳細擴展,需要知道Camunda內置的該功能。后續在測試流程的各種任務時,就可以通過登錄創建的不同用戶賬號對流程進行審核操作(也可以自己寫審核接口來實現不同用戶對節點的審核操作,但是這里Camunda已經內置了類似的審核平臺,就直接使用就不用在寫各種審核、駁回等接口)!從而實現對流程的測試。
? 需要在Camunda控制臺創建若干用戶,后續登錄對應用戶,對對應的任務節點進行審核操作!
二、Camunda Modeler部署流程
? 在Camunda-Modeler 流程設計器中,可以直接將我們設計的流程模板部署到對應的后臺程序中,同時也可以通過流程設計器直接啟動對應流程模板的實例,就無需手動寫接口實現流程模板的部署和流程實例的啟動等功能:
? 1、camunda-Modeler流程設計器中 流程模型的部署功能:
? 2、camunda-Modeler流程設計器中 啟動流程實例的功能
? 這里通過 Start process instance 操作 就可以 直接啟動流程實例,但是需要注意的是,在流程設計器中啟動流程實例時,無法給流程中需要的流程變量賦值,所有當流程中需要給預置的流程變量賦值時,就不能在流程設計器中進行啟動流程實例。
? 需要給流程中流程變量賦初始值時可以通過Camunda控制臺啟動流程實例,或者構建一個簡單的流程實例啟動接口。 eg:
一、用戶任務 user task
? 需要在項目程序中人為處理干預后才能流轉的任務類型
1.1 用戶任務受理人設置
User assignment 欄目中主要設置節點對應審核人的參數。
? 參數的填寫可以設置為固定字符串(eg:“xx” ),表示后續該節點的受理人就由 xx 進行受理審核。也可以通過 UEL表達式 / UEL方法表達式 的方式進行填寫,后續在程序中給對應的 流程變量Key 賦值即可!
-
使用UEL表達式
? 設置節點審核人的方式(eg: ${userID})。
? 當流程實例運行到該節點后,會自動查詢該流程實例的流程變量中 該 userID 所對應的數據,就會自動將該Key對應的value值設置為該節點的審核人。
? 在代碼中需要在實例運行到當前節點之前,對對應的流程變量進行賦值,這一步可以通過流程監聽器來實現。或者是在流程創建初期,通過制單人信息,將對應的審核人確定好之后對對應流程變量進行賦值!
-
使用 UEL方法表達式
? 當使用 UEL方法表達式(eg: ${uelMethodListener.getAssignee(execution)})來確定流程節點審核人時。當流程實例審核到該節點后,會解析到該UEL方法表達式。
? 以示例中的 UEL方法表達式為例,當執行當該節點后,流程引擎就會通過 uelMethodListener對象,調用getAssignee()方法,來獲取對應的節點受理人。其中的 execution 是傳入的節點相關的信息參數。在后臺代碼中的具體封裝如下:
1.2 流程內置表單的使用
? Camunda-bpmn 流程設計器中,可以創建內置的任務表單,當流程執行到相關任務節點時,需要我們人工處理填寫創建表單的任務信息。填寫完成后,對應的表單數據會自動生成為對應流程實例的流程變量。該流程變量數據就可以用來作為后續流程中的分支條件參數等作用!
-
任務表單實現效果
? 設置好任務內置表單后,啟動流程實例時,運行到對應節點,就會觸發內置表單的填寫(這里就使用了Camunda內置控制臺來對任務進行審核操作。
? 上文備注內容中介紹了,創建對應節點審核人的賬號)在Camunda內置控制臺中,登錄對應的審核人通過TskList中就能查詢到該用戶名下對應需要審核的任務節點。
?
-
審核通過任務表單節點后續效果
將任務表單對應審核人登錄到Camunda內置控制臺,對表單內容填寫后,審核通過該節點的效果展示:
1.3 多實例節點設置
? 多實例(會簽/或簽)節點 是指 實現多個用戶對一個 用戶任務節點進行操作的方式。
? 在用戶節點(user task) 的基礎上可設置會簽 /或簽節點 (多實例節點)。當節點設置為多實例節點后,流程實例中會自動生成相關參數的流程變量用來記錄該節點相關數據信息:
nrOfInstances:多實例節點中總共的實例數(實例總數)
nrOfActiviteInstances:當前活動的實例數量,即還沒有完成的實例數量對應串行而言該值始終為1
loopCounter :循環計數器,辦理人在列表中的索引
nrOfCompletedInstances:已經完成的實例數量
loop cardinality:循環基數。可選項。可以直接填整數,表示會簽的人數。
Collection:集合。可選項。會簽人數的集合,通常為list,和loop cardinality二選一。
Element variable:元素變量。選擇Collection時必選,為collection集合每次遍歷的元素。
Completion condition:完成條件。可選。比如設置一個人完成后會簽結束,那么其他人的代辦任務都會消失
-
串行/并行 多實例節點的設置
? 串行 和 并行 多實例節點的區別在與,集合中的受理人在對目標節點進行審核是,是否需要按照順序進行受理審核。
? 當設置為并行(III) 時,多實例集合中的所有受理人,不需要按照相關順序執行,可以隨意順序對多實例節點進行審核操作;當設置為串行(三) 多實例節點時,多實例集合中的審核人,就必須按照一定順序依次一個一個的對目標節點進行審核。
?
-
會簽/或簽 節點的設置
? 會簽節點指 多實例任務節點 的 多實例集合中的所有審核人都審核通過,該節點才算完成。
? 或簽節點指 多實例任務節點 的 多實例集合中的審核只需要一個或者多個審核通過后,該多實例任務就算完成。
? 設置會簽/或簽節點的方式主要是通過 **多實例設置欄目(Multi-instance)**中的 **節點完成條件(Completion condition)**來體現出來,通過上文注釋中的參數加上UEL表達式封裝來設置:
會簽節點:nrOfCompletedInstances == nrOfInstances (完成實例數 == 實例總數)
或簽節點:nrOfCompletedInstances == 1(完成實例數 ==1)
Camunda流程引擎會自動根據設置的多實例節點完成條件來判斷,該多實例節點是否結束后跳轉到下一任務節點。?
-
會簽節點審核人動態設置方式
? 首先在 **多實例設置欄目(Multi-instance)**中的 **多實例集合(Collection)**項中,用 UEL表達式 將多實例集合的Key值設置上(eg: ${leaders} )。
? 然后在通過項目代碼中,動態的為 多實例結合賦值,并傳入對應流程實例中即可 完成對多實例節點的審核人動態設置!
1.4 開始節點Initiator的設置
? 在開始節點中我們可以為 流程發起人Initiator 中設置一個 Key 值,在后續啟動流程時調用接口API 設置發起人信息,就會自動生成一個對應Key值的流程變量作為我們流程發起人。然后在第一個節點就可以直接引用該 Key值,作為流程的第一個節點審核人(制單人)。
-
在bpmn流程的開始節點設置發起人Key值
-
在流程任務的第一個用戶節點(通常為制單人節點),將審核人設置為對應的Key值
后續通過調用Camunda內置APi即可自動設置流程發起人信息,并會作為流程變量存儲到對應的流程數據中。
-
在項目中啟動流程時通過調用內置Api即可完成對流程發起人變量的賦值
二、服務任務 service task
? 服務任務通常是調用業務系統,Camunda中 可以調用 java代碼。
?
? 服務任務 service task 常見的實現方式主要有四種: External外部任務實現、Java class 指定java類實現、Expression 表達式實現、Delegate expression 代理表達式實現。
? 選中不同的實現方式,需要做出對應的處理方法,當流程實例執行到服務任務時,會根所設定的實現方式進行對應的處理。
2.1 外部實現方式 External
? 在service task 服務任務Type選項選用 **外部實現方式 External **時,對應的 Topic中需要自定義一個主題,作為該服務任務的唯一標識。
?
- 外部任務實現方式一
在客戶端系統中(外部系統),會創建外部任務處理的對應類,會實時監聽相關任務。 具體的實現方式如下代碼:
package cn.zhidasifang.flowmanagement.camundaExternalClient.shopping;import cn.hutool.core.util.ObjectUtil;
import lombok.extern.slf4j.Slf4j;
import org.camunda.bpm.client.ExternalTaskClient;
import org.camunda.bpm.engine.variable.Variables;
import org.springframework.stereotype.Component;import javax.annotation.PostConstruct;
import java.util.Map;/*** @ClassName : SubscribeTask* @Description : 業務任務節點 Service-Task 【External 類型的業務任務】* @Author : AD*/@Component
@Slf4j
public class SubscribeTask {/*** 引擎端url 前綴*/private final static String CAMUNDA_BASE_URL = "http://localhost:9991/engine-rest";private ExternalTaskClient client = null;//封裝獲取客戶端的方法private ExternalTaskClient getClient(){if (ObjectUtil.isNull(client)){client = ExternalTaskClient.create().baseUrl(CAMUNDA_BASE_URL) //監聽的camunda客戶端(流程引擎端地址)//long polling timeout.asyncResponseTimeout(10000) //異步響應超時時間(輪詢時間).build();}return client;}/*** Description:外部任務--訂閱到加入購物車的任務節點執行* @param* @return void*/@PostConstructpublic void handleShoppingCart(){getClient().subscribe("shopping_cart") //客戶端訂閱【對應外部任務主題名稱】.processDefinitionKey("Process_shopping") //流程定義Key(ID).lockDuration(2000) //鎖定外部任務時間.handler((externalTask, externalTaskService) -> {log.info("333訂閱到加入購物車任務");//設置流程實例變量Map<String,Object> goodVariable = Variables.createVariables().putValue("toWhere","shanghai China");// 獲取流程實例中已經存在的變量String paramValue = externalTask.getVariable("param");log.info("333shoppingCartId: {}", paramValue);// 完成任務externalTaskService.complete(externalTask,goodVariable);}).open();}
}
- 外部任務實現方式二
除了上述方式外,外部任務的實現方式還可以通過 camunda流程引擎提供的 @ExternalTaskSubscription 注解的方式來實現對應的 外部任務類。
package cn.zhidasifang.flowmanagement.camundaExternalClient.externalTask;import com.sun.org.apache.xpath.internal.operations.Bool;
import org.camunda.bpm.client.spring.annotation.ExternalTaskSubscription;
import org.camunda.bpm.client.task.ExternalTaskHandler;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;import java.util.concurrent.TimeUnit;/*** @ClassName : SelfRepairService* @Description : 外部任務External的實現類Service* @Author : AD*/@Configuration
public class SelfRepairService {@Bean@ExternalTaskSubscription(topicName = "try_self_repair" //外部任務主題名稱,processDefinitionKeyIn = {"Process_external_task"} //流程實例定義Key,lockDuration = 500000) //鎖定外部任務時間public ExternalTaskHandler doSelfRepair(){return (externalTask, externalTaskService) -> {System.out.println("666外部任務進入嘗試自修!");try {System.out.println("666線程睡眠5秒!");TimeUnit.SECONDS.sleep(5);} catch (InterruptedException e) {throw new RuntimeException(e);}Boolean isFree = (Boolean) externalTask.getVariable("isFree");if (isFree){System.out.println("666免費維修");externalTaskService.complete(externalTask);}else {System.out.println("666維修收費-嘗試自修");externalTaskService.handleFailure(externalTask,"自己修理,拋出異常中止后續上門維修!","這里可以打印一次stacktrace",0 //為1會重復重試 為0會拋出異常信息,5000 //重新嘗試時間,該時間段內不會被其它輪詢獲取到該任務);}};}
}
2.2 Java類實現 Java class
? 外部任務在采用 Java class 方式實現時,需要在 Java class欄目中填寫對應的Java類路徑和類名稱。
? 當流程實例運行到對應的服務任務時,Camunda流程引擎會自動調用對應類路徑下的java類。
? 在對應的java類中,需要 實現 org.camunda.bpm.engine.delegate.JavaDelegate 同時重寫 execute(DelegateExecution execution)方法,在流程引擎執行到對應的服務任務時,會自動執行該類中實現的 execute 方法。
? 實例代碼如下:
2.3 表達式實現方式 Expression
? 外部任務中,采用Expression表達式方式實現時,Expression欄目中需要填寫的 就是UEL方法表達式,于前文 1.1中用戶任務受理人設置的實現方式類似。
??
? 當流程實例運行到該服務任務節點時,流程引擎會解析對應的UEL方法表達式 ${telCall.doCall(execution)} 。通過該表達式會調用 BeanName為 telCall的對象,同時調用該對象的doCall( ) 方法。
? 需要注意,在使用 Expression 實現外部任務時,會存在 Result variable 返回值欄目 該欄目中設置對應返回值的Key值,在調用的對應對象方法中,需要返回對應數據。
? 對應的代碼實現部分示例如下:
2.4 代理表達式 Delegate expression
? 通過 Delegate expression 方式來實現的業務類,需要實現 JavaDelegate接口,并重寫 execute 方法。 在Delegate expression欄目中使用UEL表達式將委派類對象填入即可。
?
? 當流程實例執行到該節點時,流程引擎會自動調用對應 委派類對象實現的 execute 方法。
? 委派類的實現代碼如下:
三、腳本任務 Script task
? 腳本任務是一種自動執行的活動。當流程執行到腳本任務時,節點相關的腳本就會自動執行。
? 腳本任務默認的類型有兩種: 外部資源(External resource)、內部腳本(Inline script)
-
內部腳本的方式實現
Script中的內容為:
//通過 execution 獲取到流程實例中的流程變量
var person = execution.getVariable("name");
var originDays = execution.getVariable("originDays");
var leaveDay = execution.getVariable("leaveday");//將設置的腳本執行結果Key直接賦值后會自動生成對應的流程實例變量記錄存儲到流程實例中
leftAnnualDays = originDays-leaveDay;
? 內部腳本的方式會將填寫的腳本內容會被追加到流程BPMN.xml中
-
外部資源的方式實現
外部資源的實現方式 和 內部腳本的區別在于 腳本內容不會追加到流程的BPMN.xml中。
在外部腳本的 Resource欄目中填寫腳本的地址即可。
四、發送任務&接收任務
? 發送任務(Send Task)一般用于發送一個消息;接收任務(Receive Task)一般用來等待發送任務的消息,起到等待作用。一般情況下發送任務和接收任務是成對配合使用的,同時是分開存在兩個獨立的流程模板中的。
-
接收任務 Receive Task
? 接收任務需要在Message欄目下配置,等待接收消息的 全局唯一名稱。接收任務節點一旦啟動之后,就會一直處于等待狀態,知道接收到對應名稱的截至,該接收任務就會結束。
? 起到一個通訊作用,可以當作一個流程模板的觸發器來使用,在接收任務后面繼續添加其它類型的任務,當執行到接收任務處時,一直監聽消息,當接收到消息后,就執行完成,在進行流程的后續操作!
?
? 注:需要先啟動接收任務,處于監聽等待狀態后,發送任務才能成功的啟動或者執行,否則流程引擎將會出現異常!
? 無法關聯消息" MessageName:xxxxx",沒有進程定義或執行與參數匹配 !
org.camunda.bpm.engine.MismatchingMessageCorrelationException: Cannot correlate message ‘Message_receive_task’: No process definition or execution matches the parameters
-
發送任務 Send Task
? 發送任務節點在流程設置器中的配置,Implementation項下可以配置發送任務的實現方式。發送任務的實現方式,在type選項欄下總共有5種:外部任務實現(External)、java類實現(Java class)、表達式實現(Expression)、代理表達式實現(Delegate expression)、Connector實現方式。
? 這5種實現方式與服務任務(service task) 的采用形式一致,下面就以代理表達式(Delegate expression)的方式來實現發送任務。
?
? 在發送任務的實現代碼中,需要指定業務標識BusinessKey 來確保只有對應的流程業務實例才能接收到該消息。 該業務標識指定的是 接收任務Receive Task所在的流程實例的業務標識 BusinessKey。
? 在接收任務所在流程模板中,在啟動流程實例時就可以對BusinessKey進行賦值,每個流程實例的業務標識代碼都是自定義的,原理上是需要設置不同。