1. 程式人生 > >Activiti流程定義語言(BPMN)

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方法提交。