1. 程式人生 > >【Activiti】從入門到放棄——流程定義語言(BPMN)

【Activiti】從入門到放棄——流程定義語言(BPMN)

什麼是BPMN
業務流程建模與標註(Business Process Model and Notation,BPMN) ,描述流程的基本符號,包括這些圖元如何組合成一個業務流程圖(Business Process Diagram)
Eclispse畫出流程,有兩個檔案bpmn檔案和png檔案,其中bpmn檔案又可以叫做流程定義檔案,它需要遵循BPMN語言規範.png:就是一個單純的圖片,沒有任何作用.

流程(process)
bpmn檔案一個流程的根元素。一個流程就代表一個工作流。

順序流(sequenceFlow )
1.什麼是順序流
順序流是連線兩個流程節點的連線,代表一個節點的出口。流程執行完一個節點後,會沿著節點的所有外出順序流繼續執行。 就是說,BPMN 2.0預設的行為就是併發的: 兩個外出順序流會創造兩個單獨的,併發流程分支。
順序流主要由4個屬性組成:
Id: 唯一標示,用來區分不同的順序流
sourceRef:連線的源頭節點ID
targetRef:連線的目標節點ID
name(可選):連線的名稱,不涉及業務,主要用於顯示。多出口原則要設定。
說明:
1)結束節點沒有出口
其他節點有一個或多個出口。如果有一個出口,則代表是一個單線流程;如果有多個出口,則代表是開啟併發流程。

2.分支流程-流程圖
在這裡插入圖片描述

3.公共程式碼抽取

package cn.itsource.activiti.day02;

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.Deployment;
import org.activiti.engine.repository.DeploymentBuilder;
import org.activiti.engine.runtime.ProcessInstance;
import org.activiti.engine.task.Task;

public class BaseBpmn {
	private ProcessEngine processEngine=ProcessEngines.getDefaultProcessEngine();
	//自己類和子類都可能使用:使用protected修飾
	protected RepositoryService repositoryService = processEngine.getRepositoryService();
	protected RuntimeService runtimeService = processEngine.getRuntimeService();
	protected TaskService taskService = processEngine.getTaskService();
	
	/**
	 * 
	 * @param name 部署流程的名字
	 * @param resourceName  載入資源的名字字首
	 * @return
	 */
	protected Deployment deploy(String name, String resourceName) {
		//建立核心
		//獲取服務
		//做事情
		// this.getClass().getClassLoader().getResourceAsStream("LeaveFlow.bpmn");從classpath下面載入
		// this.getClass().getResourceAsStream("/LeaveFlow.bpmn");//從classpath下面載入
		// this.getClass().getResourceAsStream("LeaveFlow.bpmn");//從當前類當前包載入(採納)
		// this.getClass().getResourceAsStream("./LeaveFlow.bpmn");//從當前類當前包載入(採納)
		String resourceNameBpmn=resourceName+".bpmn";
		String resourceNamePng=resourceName+".png";
		DeploymentBuilder deploymentBuilder = repositoryService.createDeployment();
		deploymentBuilder.name(name)
						.addInputStream(resourceNameBpmn, this.getClass().getResourceAsStream(resourceNameBpmn))
						.addInputStream(resourceNamePng, this.getClass().getResourceAsStream(resourceNamePng));
		Deployment deployment = deploymentBuilder.deploy();
		return deployment;
	}


	/**
	 * 
	 * @param processDefinitionKey  啟動流程的定義的key
	 */
	protected ProcessInstance startProcess(String processDefinitionKey) {
		
		return runtimeService.startProcessInstanceByKey(processDefinitionKey);
	}
	
	/**
	 * 在一個流程例項中,一個辦理人只有一個唯一的任務
	 * @param processInstanceId 流程例項id
	 * @param assignee  辦理人
	 * @return
	 */
	protected Task queryPersonalTask(String processInstanceId, String assignee) {
	return 	taskService.createTaskQuery()
		.processInstanceId(processInstanceId)
		.taskAssignee(assignee)
		.singleResult();
	}
}

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());
	}

節點
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("流程已結束!");
		}
	}

閘道器節點
簡單理解有多個分支,但是隻能走一個分支。
在這裡插入圖片描述
在這裡插入圖片描述
配置檔案

<exclusiveGateway id="exclusivegateway1" name="Exclusive Gateway" default="財務"></exclusiveGateway>
<sequenceFlow id="財務" name="小於1000" sourceRef="exclusivegateway1" targetRef="usertask2">
      <conditionExpression xsi:type="tFormalExpression"><![CDATA[${price<1000}]]></conditionExpression>
    </sequenceFlow>
    <userTask id="usertask3" name="部門經理審批"></userTask>
    <sequenceFlow id="部門經理" name="大於1000小於10000" sourceRef="exclusivegateway1" targetRef="usertask3">
      <conditionExpression xsi:type="tFormalExpression"><![CDATA[${price>1000&&price<10000}]]></conditionExpression>
    </sequenceFlow>
    <userTask id="usertask4" name="老闆審批"></userTask>
    <sequenceFlow id="老闆" name="大於10000" sourceRef="exclusivegateway1" targetRef="usertask4">
      <conditionExpression xsi:type="tFormalExpression"><![CDATA[${price>10000}]]></conditionExpression>
    </sequenceFlow>

說明:
1.一個排他閘道器對應一個以上的順序流
2.由排他閘道器流出的順序流都有個conditionExpression元素,在內部維護返回boolean型別的決策結果。
3.決策閘道器只會返回一條結果。當流程執行到排他閘道器時,流程引擎會自動檢索網關出口,從上到下檢索如果發現第一條決策結果為true或者沒有設定條件的(預設為成立),則流出。
4.如果沒有任何一個出口符合條件則丟擲異常。

監聽器
執行監聽
配置檔案:
在這裡插入圖片描述
實現類程式碼如下
在這裡插入圖片描述
執行監聽器配置可以放在以下三個地方,如圖:
在這裡插入圖片描述
啟動流程測試程式碼如下:
在這裡插入圖片描述

任務監聽:
配置檔案:
在這裡插入圖片描述
說明:
1.任務監聽器支援以下屬性:
event(必選):任務監聽器會被呼叫的任務型別。 可能的型別為:
 create:任務建立並設定所有屬性後觸發。
 assignment:任務分配給一些人時觸發。 當流程到達userTask,assignment事件 會在create事件之前發生。 這樣的順序似乎不自然,但是原因很簡單:當獲得create時間時, 我們想獲得任務的所有屬性,包括執行人。
 complete:當任務完成,並尚未從執行資料中刪除時觸發。
class:必須呼叫的代理類。 這個類必須實現org.activiti.engine.delegate.TaskListener 介面。Java程式碼如下:
在這裡插入圖片描述
2.執行測試程式碼得到結果:
流程結束,日誌內容為:[Start start, Receive Task start, Receive Task end, Receive Task take, User Task start, User Task assignment, User Task create, User Task complete, User Task end, End end]
新新增的任務監聽包裹在executionListener監聽的內部,順序為:execution Start–> task Assignment–>task Create–>task Complete–>execution End–>execution take。