Activiti流程定義語言(BPMN)
3.1什麼是BPMN
業務流程建模與標註(Business Process Model and Notation,BPMN) ,描述流程的基本符號,包括這些圖元如何組合成一個業務流程圖(Business Process Diagram)
Eclispse畫出流程,有兩個檔案bpmn檔案和png檔案,其中bpmn檔案又可以叫做流程定義檔案,它需要遵循BPMN語言規範.png:就是一個單純的圖片,沒有任何作用.
3.2.流程(process)
bpmn檔案一個流程的根元素。一個流程就代表一個工作流。
3.3.順序流(sequenceFlow )
3.3.1.什麼是順序流
順序流是連線兩個流程節點的連線,代表一個節點的出口。流程執行完一個節點後,會沿著節點的所有外出順序流繼續執行。 就是說,BPMN 2.0預設的行為就是併發的: 兩個外出順序流會創造兩個單獨的,併發流程分支。
順序流主要由4個屬性組成:
Id: 唯一標示,用來區分不同的順序流
sourceRef:連線的源頭節點ID
targetRef:連線的目標節點ID
name(可選):連線的名稱,不涉及業務,主要用於顯示。多出口原則要設定。
說明:
1)結束節點沒有出口
其他節點有一個或多個出口。如果有一個出口,則代表是一個單線流程;如果有多個出口,則代表是開啟併發流程。
3.3.2.分支流程-流程圖
3.3.3.公共程式碼抽取
package base; import java.io.InputStream; import java.util.List; import java.util.Map; import org.activiti.engine.ProcessEngine; import org.activiti.engine.ProcessEngines; import org.activiti.engine.RepositoryService; import org.activiti.engine.RuntimeService; import org.activiti.engine.TaskService; import org.activiti.engine.repository.DeploymentBuilder; import org.activiti.engine.runtime.Execution; import org.activiti.engine.runtime.ExecutionQuery; import org.activiti.engine.runtime.ProcessInstance; import org.activiti.engine.runtime.ProcessInstanceQuery; import org.activiti.engine.task.Task; import org.activiti.engine.task.TaskQuery; public class BaseBpmn { // 獲取核心物件 服務大管家 protected ProcessEngine defaultProcessEngine = ProcessEngines.getDefaultProcessEngine(); // 獲取執行的service服務 protected RuntimeService runtimeService = defaultProcessEngine.getRuntimeService(); // 獲取TaskService protected TaskService taskService = defaultProcessEngine.getTaskService(); /** * 部署檔案 * @param resource * @param name */ public void deploy(String resource,String name){ // 獲取核心物件 服務大管家 // 獲取服務 RepositoryService repositoryService = defaultProcessEngine.getRepositoryService(); // 部署物件 DeploymentBuilder createDeployment = repositoryService.createDeployment(); // 上傳資源 InputStream inputStream = this.getClass().getResourceAsStream(resource + ".bpmn"); InputStream inputStream2 = this.getClass().getResourceAsStream(resource + ".png"); createDeployment.addInputStream(resource + ".bpmn", inputStream); createDeployment.addInputStream(resource + ".png", inputStream2); createDeployment.name(name); // 部署 createDeployment.deploy(); } /** * 啟動例項帶流程變數 * @param key * @param variables * @return */ public ProcessInstance start(String key,Map<String, Object> variables){ //設定變數 ProcessInstance startProcessInstanceByKey = runtimeService.startProcessInstanceByKey(key,variables); return startProcessInstanceByKey; } /** * 啟動例項不帶流程變數 * @param key * @param variables * @return */ public ProcessInstance start(String key){ //設定變數 ProcessInstance startProcessInstanceByKey = runtimeService.startProcessInstanceByKey(key); return startProcessInstanceByKey; } /** * 完成任務,帶流程變數,變數流轉到下一個節點 * @param taskId */ public void compleTask(String taskId,Map<String, Object> variables){ taskService.complete(taskId, variables); } /** * 完成任務,不帶帶流程變數 * @param taskId */ public void compleTask(String taskId){ taskService.complete(taskId); } /** * 完成任務,例項id 和人的名稱查詢資訊 * @param id * @param name */ public void compleTaskByPIdAndName(String id,String name){ //獲取查詢物件 TaskQuery createTaskQuery = taskService.createTaskQuery(); //獲取任務id Task task = createTaskQuery.processInstanceId(id).taskAssignee(name).singleResult(); //完成任務 taskService.complete(task.getId()); } /** * 完成任務,例項id 和人的名稱 帶引數查詢資訊 * @param id * @param name */ public void compleTaskByPIdAndName(String id,String name,Map<String, Object> variables){ //獲取查詢物件 TaskQuery createTaskQuery = taskService.createTaskQuery(); //獲取任務id Task task = createTaskQuery.processInstanceId(id).taskAssignee(name).singleResult(); //完成任務 taskService.complete(task.getId(),variables); } /** * 根據名稱查詢當前任務 * @param name * @return */ public List<Task> queryTaskList(String name){ //獲取查詢物件 TaskQuery createTaskQuery = taskService.createTaskQuery(); //設定查詢條件 createTaskQuery.taskAssignee(name); //查詢列表 List<Task> list = createTaskQuery.list(); return list; } /** * 根據名稱查詢當前任務 * @param name * @return */ public Task queryTask(String name,String id ){ //獲取查詢物件 TaskQuery createTaskQuery = taskService.createTaskQuery(); //設定查詢條件 createTaskQuery.taskAssignee(name); createTaskQuery.processInstanceId(id); //查詢列表 Task task = createTaskQuery.singleResult(); return task; } /** * 查詢當前指定物件(當前活動節點) * @param id * @param actid * @return */ public Execution queryExcution(String id,String actid){ //獲取查詢物件 ExecutionQuery createExecutionQuery = runtimeService.createExecutionQuery(); //設定查詢條件 Execution execution = createExecutionQuery.processInstanceId(id).activityId(actid).singleResult(); return execution; } public void isEnd(String id){ //獲取查詢物件 ProcessInstanceQuery createProcessInstanceQuery = runtimeService.createProcessInstanceQuery(); //根據id查詢 ProcessInstance singleResult = createProcessInstanceQuery.processInstanceId(id).singleResult(); //判斷singleResult if(singleResult!=null){ System.out.println("流程未結束或不存在"); }else{ System.out.println("流程結束"); } } }
3.3.4.分支流程-測試程式碼 輔助程式碼:
@Test
public void deployTest() throws Exception {
Deployment deployment = deploy("報銷申請","SequesceFlowTest");
System.out.println("deploymentId:"+deployment.getId());
}
@Test
public void startProcessTest() throws Exception {
String processDefinitionKey="SequesceFlowTest";
ProcessInstance processInstance = startProcess(processDefinitionKey);
System.out.println("ProcessInstanceId:"+processInstance.getId());//2501
}
測試駁回:
//測試駁回
/**
* ①:先完成報銷申請,
* ②:走到審批的時候,設定一個flag的流程變數為flase,駁回
* ③:回到①,在完成報銷申請
* ④:審批人又得到審批審批任務
* @throws Exception
*/
@Test
public void notPassTest() throws Exception {
//①:先完成報銷申請,
String processInstanceId="2501";
String assignee="小明";
Task applyTask = queryPersonalTask(processInstanceId,assignee);
System.out.println("獲取申請任務:"+applyTask);
//先完成報銷申請
taskService.complete(applyTask.getId());
assignee="小剛";
Task approveTask = queryPersonalTask(processInstanceId,assignee);
System.out.println("獲取審批任務:"+applyTask);
// ②:走到審批的時候,設定一個flag的流程變數為flase
taskService.setVariable(approveTask.getId(),"flag", "false");
//駁回
taskService.complete(approveTask.getId());
//④:審批人又得到審批審批任務
assignee="小明";
applyTask = queryPersonalTask(processInstanceId,assignee);
System.out.println("再次獲取申請任務:"+applyTask);
}
測試通過:
/**
* 通過
* @throws Exception
*/
@Test
public void passTest() throws Exception {
String processInstanceId="2501";
String assignee="小剛";
Task approveTask = queryPersonalTask(processInstanceId,assignee);
System.out.println("獲取審批任務:"+approveTask);
// ②:走到審批的時候,設定一個flag的流程變數為flase
taskService.setVariable(approveTask.getId(),"flag", "true");
//通過
taskService.complete(approveTask.getId());
}
3.4.節點
開始節點
結束節點:加一個執行監聽器,修改流程的狀態
任務節點
閘道器節點
監聽器節點
3.4.1.開始事件節點(startEvent)
開始事件對應節點
3.4.2.結束事件節點(endEvent)
結束事件對應節點
3.4.3.任務節點 (Task) 3.4.3.1接收任務節點 (receiveTask)
接收任務是一個簡單任務,它會等待對應訊息的到達。 當前,官方只實現了這個任務的java語義。 當流程達到接收任務,流程狀態會儲存到資料庫中。
在任務建立後,意味著流程會進入等待狀態, 直到引擎接收了一個特定的訊息, 這會觸發流程穿過接收任務繼續執行。(短跑比賽一樣,預備,訊號觸發跑)
圖形標記:
接收任務顯示為一個任務(圓角矩形),右上角有一個訊息小標記。 訊息是白色的(黑色圖標表示傳送語義):
1)流程圖
銷售經理統計當前的營業額,然後簡訊傳送給老闆
2)測試程式碼
公共程式碼增加:
/**
* 通過流程例項Id獲取流程例項
* @param processInstanceId
* @return
*/
protected ProcessInstance queryProcessInstanceById(String processInstanceId) {
return runtimeService.createProcessInstanceQuery()
.processInstanceId(processInstanceId)
.singleResult();
}
/**
* 在一次流程中通過活動id查詢唯一執行物件
* * @param pid
* @param calcTotalPriceActivityId
* @return
*/
protected Execution queryExecution(String pid, String calcTotalPriceActivityId) {
return runtimeService.createExecutionQuery()
.processInstanceId(pid)
.activityId(calcTotalPriceActivityId)
.singleResult();
}
第一個節點:今日銷售額計算
// 第一個節點:計算今日銷售額
@Test
public void testCalcTotalPrice() throws Exception {
// 0 查詢到"計算今日銷售額"的執行物件
String pid = "2501";
String calcTotalPriceActivityId = "當天營業額Id";
Execution calcTotalPriceExecution = queryExecution(pid, calcTotalPriceActivityId);
System.out.println("獲取當天營業額的執行物件!" + calcTotalPriceExecution.getActivityId());
// 1 計算今日銷售額
double totalPrice = 666666.66666d;
System.out.println("計算今日銷售額為:" + totalPrice);
// 2 把銷售額放入流程變數,共享給下一個節點
Map<String, Object> processVariables = new HashMap<>();
processVariables.put("totalPrice", totalPrice);
// 3 發訊息觸發"計算今日銷售額"執行物件往下走
System.out.println("設定流程變數,並讓它往下走!");
runtimeService.signal(calcTotalPriceExecution.getId(), processVariables);
// 4 可以獲取下一個節點對應的執行對
String sendMsgActivityId = "簡訊傳送給老闆Id";
Execution sendMsgExecution = queryExecution(pid, sendMsgActivityId);
System.out.println("獲取到第二個節點:" + sendMsgExecution.getActivityId());
if (sendMsgExecution != null) {
System.out.println("第一個節點已經處理完畢!");
}
}
第二個節點:簡訊傳送給老闆
@Test
public void testSendMsg() throws Exception {
String pid = "2501";
String sendMsgActivityId = "簡訊傳送給老闆Id";
Execution sendMsgExecution = queryExecution(pid, sendMsgActivityId);
// 1 從流程變數種獲取銷售額
String executionId = sendMsgExecution.getId();
Double totalPrice = runtimeService.getVariable(executionId, "totalPrice", Double.class);
System.out.println("從流程變數中獲取今日銷售額:" + totalPrice);
// 2 把銷售額傳送給老闆
System.out.println("傳送簡訊給老闆:今日收穫不錯,營業額為" + totalPrice);
// 3 讓流程繼續往下走
runtimeService.signal(executionId);
// 4 判斷流程結束
ProcessInstance processInstance = queryProcessInstanceById(pid);
if (processInstance == null) {
System.out.println("流程已結束!");
}
}
3.4.3.2使用者任務節點 (userTask)
使用者任務用來設定必須由人員完成的工作。 當流程執行到使用者任務,會建立一個新任務, 並把這個新任務加入到分配人或群組的任務列表中。
圖形標記
使用者任務顯示成一個普通任務(圓角矩形),左上角有一個小使用者圖示。
任務分配
使用者任務的辦理都需要人工的參與。使用者任務可以分為兩大類。私有任務(待辦任務)和公有任務(可接任務)。
1)私有任務
私有任務即有直接分配給指定使用者的任務。只有一個使用者可以成為任務 的執行者。 在activiti中,使用者叫做執行者。 擁有執行者的使用者任務 (即私有任務)對使用者是不可見的。
辦理者指定分三種方式:
1)寫死
2)流程變數
3)監聽器
公共程式碼增加:
/**
* 通過流程定義key啟動流程並且設定流程變數
* @param processDefinitionKey
* @return
*/
protected ProcessInstance startProcess(String processDefinitionKey,Map<String, Object> variables) {
return runtimeService.startProcessInstanceByKey(processDefinitionKey, variables);
}
/**
* 私有任務測試
* 辦理者指定:
* 1)寫死,設計流程時寫死辦理者(不用)
* 缺點:這個流程只能由寫死哪個人申請
* 2)流程變數設定--第一個節點
* ①設計流程時要從流程變數中獲取辦理者
* ②流程執行時要設定對應流程變數-啟動時設定
* 問題:如果第二個-n個節點通過在啟動流程時設定變數來指定辦理者,
* 第一個辦理長時間沒有辦理,在這期間後面節點辦理者離職了,走下去再離職人員手上還會掛任務.
* 方案1:下一個任務辦理時再指定下一個任務辦理者.不好,業務邏輯耦合
* 方案2:使用監聽器設定下一個節點啟動時指定人,不需要耦合.(採納)
* 3)監聽器--第二個-n個節點
* ①寫一個類實現監聽器類-TaskListener
* ②把寫好類繫結到對應節點
* ③實現監聽器邏輯
* @author Administrator
*
*/
public class PersonalTaskTest extends BaseBpmnTest {
//寫死
@Test
public void test1() {
//部署
deploy("私有任務測試1", "PersonalTaskTest");
//啟動
String processDefinitionKey = "PersonalTaskTest";
ProcessInstance pi = startProcess(processDefinitionKey );
//獲取寫死辦理的任務
String assignee = "小鑫鑫";
Task task = queryPersonalTask(pi.getId(), assignee );
System.out.println("id:"+task.getId()
+",name:"+task.getName()+",assignee:"+task.getAssignee());
}
@Test
public void test2() {
//部署
//deploy("私有任務測試2", "PersonalTaskTest2");
//啟動時要設定第一個節點的辦理者
String processDefinitionKey = "PersonalTaskTest2";
Map<String, Object> variables = new HashMap<>();
//第一個節點辦理者
String assignee = "曉鑫鑫";//當前登入使用者名稱稱
variables.put("apply", assignee);
ProcessInstance pi = startProcess(processDefinitionKey,variables );
System.out.println("pid:"+pi.getId());
//獲取寫死辦理的任務
Task task = queryPersonalTask(pi.getId(), assignee );
System.out.println("id:"+task.getId()
+",name:"+task.getName()+",assignee:"+task.getAssignee());
}
//要把第一個節點走下去
/**
* 使用流程變數設定
* @throws Exception
*/
@Test
public void test3Pre() throws Exception {
//部署
//deploy("私有任務測試3", "PersonalTaskTest3");
//啟動時要設定第一個節點的辦理者
String processDefinitionKey = "PersonalTaskTest3";
Map<String, Object> variables = new HashMap<>();
//第一個節點辦理者
String assignee = "逗比鑫";//當前登入使用者名稱稱
variables.put("apply", assignee);
ProcessInstance pi = startProcess(processDefinitionKey,variables );
System.out.println("pid:"+pi.getId());
//獲取寫死辦理的任務
Task task = queryPersonalTask(pi.getId(), assignee );
System.out.println("id:"+task.getId()
+",name:"+task.getName()+",assignee:"+task.getAssignee());
taskService.complete(task.getId());
}
//上一個任務完成設定下一個任務的辦理者,沒法做,沒有辦法直接獲取下一個任務並設定
//當前任務建立時,再指定辦理者
@Test
public void test3() throws Exception {
String assignee = "逗比鑫"+"審批人";
String processInstanceId = "5001";
Task task = queryPersonalTask(processInstanceId, assignee );
System.out.println("id:"+task.getId()
+",name:"+task.getName()+",assignee:"+task.getAssignee());
//完成第二個任務
taskService.complete(task.getId());
//判斷流程是否結束
ProcessInstance pi = queryProcessInstanceById(processInstanceId);
if (pi == null) {
System.out.println("流程結束");
}
}
}
在下一個節點建立時,增加這個監聽器:
import org.activiti.engine.delegate.DelegateTask;
import org.activiti.engine.delegate.TaskListener;
/**
* ①寫一個類實現監聽器類-TaskListener
* ②把寫好類繫結到對應節點
* ③實現監聽器邏輯
* @author Administrator
*
*/
public class ManagerSettingListener implements TaskListener{
@Override
public void notify(DelegateTask delegateTask) {
System.out.println(delegateTask.getName());
//1 獲取申請人
String apply = delegateTask.getVariable("apply", String.class);
//2 通過申請人獲取審批人 通過申請人獲取部門經理
String manager = apply+"審批人";
//3 設定辦理者
delegateTask.setAssignee(manager);
//delegateTask.addCandidateUser(userId);
//delegateTask.addCandidateGroup(groupId);
}
}
2)公共任務
有的使用者任務在指派時無法確定具體的辦理者,這時任務也可以加入到人員的候選任務列表中,然後讓這些人員選擇性認領和辦理任務。
公有任務的分配可以分為指定候選使用者和候選組兩種。
每一種都要三種指定方式
1)寫死(不用的)
2)流程變數指定第一個節點
3)任務監聽器指定第2-n節點
a)把任務新增到一批使用者的候選任務列表中,使用candidateUsers 屬 性,XML內容如下:
<userTaskid="theTask"name="my task"activiti:candidateUsers="sirius,kermit"/>
candidateUsers屬性內為使用者的ID,多個使用者ID之間使用(半形)逗號間隔。
b)把任務新增到一個或多個候選組下,這時任務對組下的所有使用者可 見,首先得保證每個組下面有使用者,通過IdentityService物件建立使用者 和組,然後把使用者新增到對應的組下,java程式碼如下:
上述兩個雖然都可以統稱為任務節點,但是還是有本質區別:
1.receiveTask主要代表機器自動執行的,userTask代表人工干預的。
2.receiveTask任務產生後會在act_ru_execution表中新增一條記錄, 而userTask產生後會在act_ru_execution和act_ru_task(主要記錄任 務的釋出時間,辦理人等資訊)中各產生一條記錄。
receiveTask任務提交方式使用RuntimeService的signal方法提交, userTask任務提交方式使用TaskService的complete方法提交。