1. 程式人生 > 實用技巧 >activiti學習筆記一

activiti學習筆記一

activiti學習筆記

在講activiti之前我們必須先了解一下什麼是工作流,什麼是工作流引擎。

在我們的日常工作中,我們會碰到很多流程化的東西,什麼是流程化呢,其實通俗來講就是有一系列固定的步驟的事情。例如我們應聘職位,這會有一個流程,先筆試、一輪部門主管面、二輪經理面、三輪總經理面、四輪HR面。不管你是誰來面試,都是要經理這個步驟。

工作流其實也就是一個工作的流程,就像我們上面說的面試流程。當然我們生活不止這點點例子。只要你開啟萬孩具憎的釘釘,裡面就有個稽核模組,裡面一大堆都是你在工作中會遇到的流程,什麼請假流程、調薪流程、報銷流程、調崗流程,一大堆。

那什麼是工作流引擎呢?引擎是一個帶動一個東西不斷運動的工具,好像汽車引擎、飛機引擎、遊戲引擎。很明顯,工作流引擎就是帶動工作流不斷運動的工具,再簡單點說那就是不斷推動我們的工作的流程的一個工具。

明白了吧,工作流引擎就是一個工具,他的作用就僅僅是推動你的流程不斷運轉。

activiti就是一個工作流引擎,工作流引擎除了這個還有挺多的,但是其他我都沒瞭解。

activiti能幫我們做什麼?

前面我說過,工作流引擎就是推動你的流程不同運轉的工具。我們的真實世界中的流程多種多樣,成百上千,而且各不相同,甚至同一件事不同的人也有不同的流程。

那我們的流程各不相同,工作流引擎憑什麼能幫我們推動流程運轉呢?其實很簡單,儘管我們各種流程都不盡相同,但其本質、其核心的一點肯定是不變的:流程發起----任務查詢----任務完成----下一個任務----任務查詢----任務完成----下一個任務-----結束。

不管什麼流程,都是按照以上步驟不斷進行的,變得只是我們的業務內容。

假如我們此時有個請假流程,員工填寫請假申請單----》單子放部門老大桌面----》部門老大在桌面翻單子看----》部門老大籤個名----》部門老大順手把單子扔給經理-----》經理撿起單子看看-----》經理同意也簽名了-----》經理讓行政過來拿單子----》行政將請假申請入檔----》請假成功。

套進我們的核心過程裡面:

如果我們又有一個離職流程,員工填寫離職通知書----》悄咪咪拿給人事----》人事看了申請覺得可以----》人事拿給部門老大瞧瞧-----》老大看看很驚訝-----》但老大還是簽字同意----》你滾蛋了-----》結束。

再套進我們的核心過程:

上面兩個例子,即便我們流程繁多,但步驟無非就那5個:發起、查詢、轉下一個任務、完成任務、結束。

而工作流說做的也就是控制這5個步驟。讓我們只需要關心我們各個任務的自己的業務資料就行了,至於流程怎麼走,任務怎麼查詢、任務怎麼進入下一個環節,工作流幫我們管理。

注意啊,這並不是說我們就什麼都不用管了,我們還是要呼叫工作流給我們的簡單的api介面去完成這5個步驟。可能你會有點蒙,但是不要緊。下面我再給你們分析下我們不用工作流和用工作流具體怎麼實現你就會更清晰了。

如果我們不用工作流,我們要怎麼實現呢?

我們先拿請假申請裡做例子。

員工填寫請假申請單----》單子放部門老大桌面----》部門老大在桌面翻單子看----》部門老大籤個名----》部門老大順手把單子扔給經理-----》經理撿起單子看看-----》經理同意也簽名了-----》經理讓行政過來拿單子----》行政將請假申請入檔----》請假成功

要完成請假申請,我們大概需要三張表

第一步:員工填寫請假申請單:

  這裡我們要有一個頁面,給人填寫表單,然後在【請假申請記錄表】裡面增加一條記錄

第二步:部門老大查詢:

  這裡我們要有一個頁面,給部門老大查詢他的要審批的單子,查詢那些 “當前申請環節” == “部門老大稽核” 的記錄,查出剛剛張三申請的單子

第三步:部門老大審批:

  這裡我們要有一個頁面給部門老大審批,部門老大同意了,提交了他的稽核記錄,然後在【稽核記錄表】裡面增加一條記錄

  同時也將請假申請記錄的當前申請環節改成 “經理稽核”

第四步:經理查詢:

  這裡要有一個頁面,可以和第二步共用一個頁面,但是查詢邏輯不同,查詢那些 “當前申請環節” == “經理稽核” 的記錄,查出張三申請的單子

第五步:經理審批:

  這裡可以用第三步部門老大審批的頁面。經理同意請假,提交了他的稽核記錄,然後在【稽核記錄表】裡面增加一條記錄

  同時也將請假申請記錄的當前申請環節改成 “行政存檔”

第六步:行政查詢:

  這裡要有一個頁面,可以和第二步共用一個頁面,但是查詢邏輯不同,查詢那些 “當前申請環節” == “行政存檔” 的記錄,查出張三申請的單子

第七步:行政存檔:

  這裡要有一個頁面,給行政進行請假申請的存檔,行政存檔後,【請假入檔記錄表】增加一條記錄

  同時也將請假申請記錄的當前申請環節改成 “請假通過”

到目前為止你是不是覺得一切都很簡單,就算不用工作流引擎也一樣so easy。當初我在學activiti時也是這樣子想的,明明自己寫也很簡單啊,為什麼還要用工作流,沒事找事麼。

先別急,我們繼續往下看,等加多一個流程的時候,我們就能感受到為什麼要加工作流引擎了。

第二個離職流程:

員工填寫離職通知書----》悄咪咪拿給人事----》人事看了申請覺得可以----》人事拿給部門老大瞧瞧-----》老大看看很驚訝-----》但老大還是簽字同意----》你滾蛋了-----》結束。

可能會有人覺得我這個例子不太合理,離職哪裡需要申請的,但往往世界就是這麼不合理的,流程往往都會有奇葩的。

那麼我們要完成這個離職流程,大概需要這4張表

第一步:員工填寫離職通知書:

  這裡我們要有一個頁面,給人填寫表單,然後在【離職申請記錄表】裡面增加一條記錄。注意,這裡這個功能和之前的請假流程完全不一樣,所以需要一個新的功能,無法複用。

第二步:人事查詢:

  這裡我們要有一個頁面,給人事查詢離職申請,查詢那些 “當前環節” == “人事稽核” 的記錄,查出張三申請的單子。注意,這裡和之前的請假流程完全不一樣,用的表都不同,所以需要一個新的功能,無法複用。

第三步:人事審批:

  這裡我們要有一個頁面,給人事進行審批,填寫審批意見。然後在【稽核記錄表】裡面增加一條記錄。注意,這裡和之前的請假流程完全不同,用到的表都不一樣,所以需要一個新的功能,無法複用。

  同時也將離職申請記錄的當前環節改成 “部門領導交接”。

第四步:部門領導查詢:

  這裡我們要有一個頁面,給部門領導查詢離職申請,查詢那些 “當前環節” == “部門領導交接” 的記錄,查出張三申請的單子。這裡可以和第二步人事查詢的頁面複用

第五步:部門領導交接:

  這裡我們要有一個頁面,給部門領導提交交接記錄,【離職交接記錄表】增加一條記錄。這裡無法和前面的頁面複用。

  同時也將離職申請記錄的當前環節改成 “待歸檔”。

第六步:人事查詢:

  這裡我們要有一個頁面,給人事查詢離職申請,查詢那些 “當前環節” == “待歸檔” 的記錄,查出張三申請的單子。這裡可以和第二步人事查詢共用同一個頁面。

第七步:人事查詢:

  這裡我們要有一個頁面,給人事進行離職記錄歸檔,沒有頁面可以複用,【離職記錄表】增加一條記錄。

  同時也將離職申請記錄的當前環節改成 “已完成”。

到目前為止,我們兩個流程,大致有9個頁面:請假單填寫、請假單查詢、請假單審批、請假單歸檔、離職申請單填寫、離職單查詢、離職單審批、交接記錄填寫、離職歸檔。

而且每增加一個流程,我們都要把整個流程都實現一遍。甚至說我改變流程,增加一個節點,減少一個節點,任務節點按條件分支等等,不僅要實現每個任務節點的功能,還要自己實現節點流轉控制的功能。

而我們用了activiti工作流後會怎樣呢?

先弄請假申請。還是要三張表,但是要注意,此時請假申請記錄表沒有當前環節了,因為流程環節不歸我們業務管了,有activiti管理了。

第一步:畫好流程圖,設定好各種引數

第二步:寫一個頁面,填寫請假申請單,【請假申請記錄表】增加一條記錄,同時呼叫activiti啟動一個流程例項

第三步:寫一個頁面,呼叫activiti的介面查詢部門老大要完成的任務。activiti會自動管理任務的責任人,在查詢任務時,只需傳入當前使用者的標識或所在使用者組的標識,就可以查詢到任務責任人為自己,或任務責任人組中有自己的一系列任務。這是因為activiti會從我們的流程圖中讀取到這個任務節點由誰處理。

這裡需要注意,activiti可以幫我們找到當前使用者的所有待處理任務,但是任務仍然需要我們自己和業務關聯上,這樣才具有真正的實際意義,回顯在頁面上才知道什麼意思,不然就只是一個單純的任務節點。

第四步:寫一個頁面,部門老大稽核通過,【稽核記錄表】增加一條記錄,呼叫activiti的介面完成這一節點。

第五步:經理查詢自己的任務,這裡無需重寫頁面,不管誰查任務都只需要傳入自己的使用者標識或使用者組標識就可以。

第六步:因為內容和第四步一樣,也可以直接複用頁面和功能,【稽核記錄表】增加一條記錄,呼叫activiti的介面完成這一節點。

這裡說下為什麼可以複用,因為部門老大和經理的什麼操作都是相似的,不同的地方在於填寫的稽核意見,以及當前環節,稽核意見是頁面輸入的,當前環節是去activiti獲取的。

第七步:人事查詢,這裡和部門老大查詢、經理查詢一樣,直接複用

第八步:人事歸檔,這裡的處理和上面都不同,所以頁面要新的

【請假入檔記錄表】增加一條記錄

看上去是不是還是和以前一眼多步驟,但是仔細看下,就會發現,我們直接操作的表,其實只有我們的業務相關的表,請假申請記錄的狀態也不需要了,連查詢任務我們要只是簡單呼叫activiti的介面。整個流程運轉我們都沒有碰過。甚至乎查詢任務我們都一直在複用

我們再加個離職申請流程看看

這裡我們還是要四個表,不過離職申請記錄表的當前環節不需要我們自己管了

第一步:畫一個離職流程圖,這裡流程圖是很重要的,activiti就是靠他知道你都有哪些步驟,每個步驟有誰來執行

第一步:員工填寫離職通知書:

  這裡我們要有一個頁面,給人填寫表單,然後在【離職申請記錄表】裡面增加一條記錄。注意,這裡這個功能和之前的請假流程完全不一樣,所以需要一個新的功能,無法複用。最後呼叫activiti的介面啟動一個流程例項。

第二步:人事查詢,這裡我們也是直接通過activiti來查詢當前使用者的任務,完全可以用請假流程的查詢。你可以參考下釘釘裡面的待我稽核,就是一大堆自己的流程任務放在一個頁面

第三步:人事提交稽核,這裡我們需要一個新的頁面,雖然都是稽核,但資料不同,那就得新頁面,然後【稽核記錄表】增加一條記錄,同時呼叫activiti完成當前節點任務。

第四步:部門老大查詢,同理,和上面共用查詢頁面。

第五步:部門老大填寫交接記錄單,這裡要一個新的頁面,【離職交接記錄表】增加一條記錄,同時呼叫activiti完成當前節點任務。

第六步:人事查詢,同理,和上面共用查詢頁面。

第七步:人事存檔,這裡要一個新頁面,【離職記錄表】增加一條記錄,同時呼叫activiti完成當前節點任務。

現在再讓我們來總結下用了activiti後我們需要做哪些工作:

共9個步驟:畫流程圖、填寫請假申請單、查詢頁面,請假稽核頁面,請假入檔頁面,填寫離職申請單、離職稽核頁面、離職交接填寫頁面,離職歸檔頁面

而我們以前也是9個步驟:請假單填寫、請假單查詢、請假單審批、請假單歸檔、離職申請單填寫、離職單查詢、離職單審批、交接記錄填寫、離職歸檔。

其實這是必然的,因為activiti不幫我們處理頁面。任務工作流引擎都不會幫我們處理頁面。

但是我們用了工作流引擎後,我們主要處理的其實是我們的業務資料,查詢功能幾乎統一沒有變化。流程怎麼走也不需要我們管了。就算以後你請假不需要經理稽核了,我們也不需要改程式碼了,只要改了流程圖就行,畢竟我們沒有直接控制任務怎麼走。

相比以前,如果砍了個步驟,你是不是得修改部門老大稽核完後的程式碼,讓他直接進入行政歸檔環節。

很多部落格、教學視訊都會說,用了工作流後,不管流程怎麼變,你都不需要改程式碼,這其實是錯誤的,因為我們的流程充滿了業務,改了流程環節,必定牽扯到業務,所以肯定會進行程式碼上的修改的。特別是你新增了步驟,修改了業務資料,這些都是必須要改程式碼的。當然,如果你每個步驟都是進行同樣的操作,只是資料的值不同,就像請假的兩次稽核,還真是可以做到不管怎麼變流程,都不用改程式碼。

也許你看到這裡還是會覺得很難理解,那接下來我們看下activiti的實現原理,可能你就能理解了。

首先我們準備下activiti的環境。

1、資料庫,先建一個資料庫,我自己用mysql,你們用啥自己看著辦。

2、建立一個maven專案,加入依賴

 <properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<java.version>1.8</java.version>
<maven.compiler.source>1.8</maven.compiler.source>
<maven.compiler.target>1.8</maven.compiler.target>
<activiti.version>7.1.0.M2</activiti.version>
<slf4j.version>1.7.30</slf4j.version>
</properties>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
</dependency>
<!-- activiti的一堆依賴 -->
<dependency>
<groupId>commons-io</groupId>
<artifactId>commons-io</artifactId>
<version>2.6</version>
</dependency>
<dependency>
<groupId>org.activiti</groupId>
<artifactId>activiti-engine</artifactId>
<version>${activiti.version}</version>
</dependency>
<dependency>
<groupId>org.activiti</groupId>
<artifactId>activiti-spring</artifactId>
<version>${activiti.version}</version>
</dependency>
<dependency>
<groupId>org.activiti</groupId>
<artifactId>activiti-bpmn-model</artifactId>
<version>${activiti.version}</version>
</dependency>
<dependency>
<groupId>org.activiti</groupId>
<artifactId>activiti-bpmn-converter</artifactId>
<version>${activiti.version}</version>
</dependency>
<dependency>
<groupId>org.activiti</groupId>
<artifactId>activiti-json-converter</artifactId>
<version>${activiti.version}</version>
</dependency>
<dependency>
<groupId>org.activiti</groupId>
<artifactId>activiti-bpmn-layout</artifactId>
<version>${activiti.version}</version>
<scope>test</scope>
</dependency> <!-- activiti連線我們資料庫的jdbc驅動 -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.18</version>
</dependency>
<!-- 日誌,最好加上 -->
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<version>1.2.17</version>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>${slf4j.version}</version>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-log4j12</artifactId>
<version>${slf4j.version}</version>
<scope>test</scope>
</dependency>
<!-- activiti是用mybatis來操作資料庫的,不加也行,反正activiti自帶依賴 -->
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.5.4</version>
</dependency>
<!-- 資料庫連線池,你們用別的也許,這個隨便 -->
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-dbcp2</artifactId>
<version>2.7.0</version>
</dependency>

3、activiti配置檔案activiti.cfg.xml

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd"> <!-- 配置資料來源 -->
<bean id="dataSource" class="org.apache.commons.dbcp2.BasicDataSource">
<property name="driverClassName" value="com.mysql.cj.jdbc.Driver"/>
<property name="url" value="jdbc:mysql://localhost:3306/activiti_demo?serverTimezone=GMT&amp;useUnicode=true&amp;characterEncoding=utf8"/>
<property name="username" value="root"/>
<property name="password" value="root"/>
</bean> <!-- activiti單獨執行的processEngine配置,使用單獨啟動方式,這個processEngineConfiguration名字不能亂改,預設bean的名字是他 -->
<bean id="processEngineConfiguration" class="org.activiti.engine.impl.cfg.StandaloneProcessEngineConfiguration">
<!--資料來源-->
<property name="dataSource" ref="dataSource"></property>
<!--是否生成表結構-->
<property name="databaseSchemaUpdate" value="true"></property>
</bean>
</beans>

4、測試一下activiti有沒有啟動

package activiti01;

import org.activiti.engine.ProcessEngine;
import org.activiti.engine.ProcessEngineConfiguration;
import org.activiti.engine.ProcessEngines; /**
* 生成25張表
* */
public class ActivitiGeneratorTable { public static void main(String[] args) {
ActivitiGeneratorTable activitiGeneratorTable = new ActivitiGeneratorTable();
activitiGeneratorTable.testGenTable1();
} /***
* 測試activiti25張表的生成
* 方法一:
* */
public void testGenTable1(){
// 1、建立一個流程引擎配置類ProcessEngineConfiguration
/**
* 如果processEngineConfiguration的bean名字(activiti.cfg.xml中)改了,要將新名字傳入,作為第二個引數
* */
ProcessEngineConfiguration configuration = ProcessEngineConfiguration.createProcessEngineConfigurationFromResource("activiti.cfg.xml");
// 2、建立一個流程引擎類
ProcessEngine processEngine = configuration.buildProcessEngine();
// 3、輸出processEngine
System.out.println(processEngine);
} /***
* 測試activiti25張表的生成
* 方法二:
* */
public void testGenTable2(){
/**
* 這種方法要求activiti的配置檔案要再classpath直接路徑下,同時檔名為"activiti.cfg.xml",而且流程引擎的bean名字為“processEngineConfiguration”(activiti.cfg.xml中)
* 三者缺一不可
* */
ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();
//輸出processEngine
System.out.println(processEngine);
}
}

如果沒問題,那你的資料庫裡面應該會有25張act開頭的表

關於各個表的作用,有個部落格講的挺好的 https://blog.csdn.net/hj7jay/article/details/51302829 但是這裡的表和我們生成的表會有點差異,這是activiti版本的問題,我用的是activiti7的版本,但核心表都一樣

大體分成4類表:

act_ru_*:存放執行時記錄的表,裡面的資料只會在流程例項執行時才存在,一旦流程例項結束了,相關記錄就會被清除。包括流程例項、使用者任務、變數等

act_re_*:流程定義表,主要存放流程定義資訊,我們部署流程後,便有一個流程定義了

act_ge_*:資源表,主要存放一些流程定義的檔案資源,流程變數的序列化內容。

act_hi_*:歷史表,主要是存放一些歷史記錄,包括歷史任務、歷史流程例項、歷史流程變數、歷史任務的參與者資訊表等等。

先說兩個比較重要的概念:流程定義、流程例項

流程定義:也就是我們畫好的bpmn檔案,bpmn檔案其實就是一個以.bpmn結尾的xml格式的檔案,定義了這個流程的各個任務怎麼走,有什麼屬性等等。當我們部署流程後,會影響ct_ge_bytearray、act_re_deployment、act_re_procdef三張表。相當於我們的Java類。

流程例項:流程定義只是一個模板,而我們每次啟動流程,都會以流程定義作為模板,建立一個流程例項,這個流程例項就相當於java類的例項,不同流程例項間是獨立的,不影響的。

activiti有幾個很重要的類,我們玩activiti基本都是圍繞這幾個類來玩的:

新版的activiti裡面,FormService和IdentityService已經沒了。

說了這麼多廢話,我們還是回到剛剛的程式碼來看一下吧。

我們的配置檔案上有這麼一句:

 <!--是否生成表結構-->
<property name="databaseSchemaUpdate" value="true"></property>

我們在獲取ProcessEngine 的時候就會去判斷這個更新策略,然後幫我們建立表。

環境有了,我們再看看怎麼畫bpmn檔案吧,前面說過,bpmn檔案其實就是.bpmn字尾的一個XML格式的檔案。我們一般用eclipse或者idea的外掛來畫。當然也有其他工具。

eclipse或者idea怎麼裝外掛就自己百度吧,不過我idea搜不到外掛,所以我就用eclipse了。

每個控制元件元素,甚至每一條線,都是可以設定屬性的。

如果我們用記事本,或者其他文字編輯器開啟,就可以發現其就是xml

我把這個xml放出來,你們可以在直接複製,檔案儲存字尾.bpmn就行

<?xml version="1.0" encoding="UTF-8"?>
<definitions xmlns="http://www.omg.org/spec/BPMN/20100524/MODEL" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:activiti="http://activiti.org/bpmn" xmlns:bpmndi="http://www.omg.org/spec/BPMN/20100524/DI" xmlns:omgdc="http://www.omg.org/spec/DD/20100524/DC" xmlns:omgdi="http://www.omg.org/spec/DD/20100524/DI" typeLanguage="http://www.w3.org/2001/XMLSchema" expressionLanguage="http://www.w3.org/1999/XPath" targetNamespace="http://www.activiti.org/test">
<process id="holiday" name="請假流程" isExecutable="true">
<startEvent id="startevent1" name="Start"></startEvent>
<userTask id="usertask1" name="填寫請假申請" activiti:assignee="zhangsan"></userTask>
<sequenceFlow id="flow1" sourceRef="startevent1" targetRef="usertask1"></sequenceFlow>
<userTask id="usertask2" name="部門經理稽核" activiti:assignee="lisi"></userTask>
<sequenceFlow id="flow2" sourceRef="usertask1" targetRef="usertask2"></sequenceFlow>
<userTask id="usertask3" name="總經理稽核" activiti:assignee="wangwu"></userTask>
<sequenceFlow id="flow3" sourceRef="usertask2" targetRef="usertask3"></sequenceFlow>
<endEvent id="endevent2" name="End"></endEvent>
<sequenceFlow id="flow6" sourceRef="usertask3" targetRef="endevent2"></sequenceFlow>
</process>
<bpmndi:BPMNDiagram id="BPMNDiagram_holiday">
<bpmndi:BPMNPlane bpmnElement="holiday" id="BPMNPlane_holiday">
<bpmndi:BPMNShape bpmnElement="startevent1" id="BPMNShape_startevent1">
<omgdc:Bounds height="35.0" width="35.0" x="220.0" y="220.0"></omgdc:Bounds>
</bpmndi:BPMNShape>
<bpmndi:BPMNShape bpmnElement="usertask1" id="BPMNShape_usertask1">
<omgdc:Bounds height="55.0" width="105.0" x="350.0" y="210.0"></omgdc:Bounds>
</bpmndi:BPMNShape>
<bpmndi:BPMNShape bpmnElement="usertask2" id="BPMNShape_usertask2">
<omgdc:Bounds height="55.0" width="105.0" x="500.0" y="210.0"></omgdc:Bounds>
</bpmndi:BPMNShape>
<bpmndi:BPMNShape bpmnElement="usertask3" id="BPMNShape_usertask3">
<omgdc:Bounds height="55.0" width="105.0" x="650.0" y="210.0"></omgdc:Bounds>
</bpmndi:BPMNShape>
<bpmndi:BPMNShape bpmnElement="endevent2" id="BPMNShape_endevent2">
<omgdc:Bounds height="35.0" width="35.0" x="830.0" y="220.0"></omgdc:Bounds>
</bpmndi:BPMNShape>
<bpmndi:BPMNEdge bpmnElement="flow1" id="BPMNEdge_flow1">
<omgdi:waypoint x="255.0" y="237.0"></omgdi:waypoint>
<omgdi:waypoint x="350.0" y="237.0"></omgdi:waypoint>
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge bpmnElement="flow2" id="BPMNEdge_flow2">
<omgdi:waypoint x="455.0" y="237.0"></omgdi:waypoint>
<omgdi:waypoint x="500.0" y="237.0"></omgdi:waypoint>
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge bpmnElement="flow3" id="BPMNEdge_flow3">
<omgdi:waypoint x="605.0" y="237.0"></omgdi:waypoint>
<omgdi:waypoint x="650.0" y="237.0"></omgdi:waypoint>
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge bpmnElement="flow6" id="BPMNEdge_flow6">
<omgdi:waypoint x="755.0" y="237.0"></omgdi:waypoint>
<omgdi:waypoint x="830.0" y="237.0"></omgdi:waypoint>
</bpmndi:BPMNEdge>
</bpmndi:BPMNPlane>
</bpmndi:BPMNDiagram>
</definitions>

好了,我們再來看部署流程要怎麼做:

package activiti01;

import org.activiti.engine.ProcessEngine;
import org.activiti.engine.ProcessEngines;
import org.activiti.engine.RepositoryService;
import org.activiti.engine.repository.Deployment; import java.io.InputStream;
import java.util.zip.ZipInputStream; /**
* 流程部署
* 影響了三張表:
* act_ge_bytearray
* act_re_deployment
* act_re_procdef
* */
public class ActivitiDeployment {
public static void main(String[] args) {
deployment1();
} /**
* 方法一:
* 直接部署bpmn檔案
* */
private static void deployment1() {
// 1、建立ProcessEngine流程引擎物件
ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine(); // 2、得到Repositoryervice例項
RepositoryService repositoryService = processEngine.getRepositoryService(); // 3、進行部署,bpmn檔案是一定要的,圖片檔案可以沒有,流程key相同的話,會使用最新部署的流程定義
Deployment deployment = repositoryService.createDeployment()
.addClasspathResource("bpmn/holiday.bpmn")
.addClasspathResource("bpmn/holiday.png")
.name("請假申請流程")
.deploy(); // 4、輸出部署的資訊
System.out.println(deployment.getName());
System.out.println(deployment.getId());
} /**
* 方法二:
* 部署zip壓縮包
* */
private static void deployment2() {
// 1、建立ProcessEngine流程引擎物件
ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine(); // 2、得到Repositoryervice例項
RepositoryService repositoryService = processEngine.getRepositoryService(); // 3、獲取壓縮檔案
InputStream inputStream = ActivitiDeployment.class.getClassLoader().getResourceAsStream("bpmn/holiday.zip"); // 4、建立一個ZipInputStream流
ZipInputStream zipInputStream = new ZipInputStream(inputStream); // 3、進行部署,bpmn檔案是一定要的,圖片檔案可以沒有,流程key相同的話,會使用最新部署的流程定義
Deployment deployment = repositoryService.createDeployment()
.addZipInputStream(zipInputStream)
.name("請假申請流程")
.deploy(); // 4、輸出部署的資訊
System.out.println(deployment.getName());
System.out.println(deployment.getId());
}
}

有兩種部署方式,自己選一種就好。

另外說一下,bpmn檔案是必須存在,圖片檔案是可以沒有的,畢竟bpmn就是我們流程定義的主要的內容,沒有了還怎麼玩呢。

部署流程會影響三張表act_ge_bytearray、act_re_deployment、act_re_procdef

我們來看看這三張表都有啥東西

其實我們將bpmn檔案部署上去後,後面我們每次啟動流程例項,activiti都會讀取我們的bpmn檔案,去獲取流程檔案,並且解析,這樣才能知道下一步要幹嘛。

我們剛剛部署了一個流程,那我們接下來看看怎麼啟動這個流程例項:

package activiti01;

import org.activiti.engine.ProcessEngine;
import org.activiti.engine.ProcessEngines;
import org.activiti.engine.RuntimeService;
import org.activiti.engine.runtime.ProcessInstance; /**
* 啟動流程例項
* 影響的表:
* act_hi_actinst 歷史活動資訊,包括當前任務
* act_hi_identitylink 歷史任務參與者資訊,包括當前任務的參與者
* act_hi_procinst 歷史流程例項
* act_hi_taskinst 歷史任務例項,包括當前任務
* act_ru_execution 流程執行時的活動資訊
* act_ru_identitylink 當前流程例項的任務參與者資訊
* act_ru_task 當前任務節點的資訊
* */
public class ActivitiStartInstance {
public static void main(String[] args) {
// 1、得到processEngine物件
ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();
// 2、得到RuntimeService物件
RuntimeService runtimeService = processEngine.getRuntimeService();
// 3、得到流程例項,需要直到流程定義的key,也就是流程process檔案的Id,可以在bpmn裡面檢視,也可以在資料庫act_re_procdef找到該流程的key
ProcessInstance processInstance = runtimeService.startProcessInstanceByKey("holiday");
// 4、輸出相關資訊
System.out.println("流程部署id"+processInstance.getDeploymentId());
System.out.println("流程例項id"+processInstance.getProcessInstanceId());
System.out.println("活動id"+processInstance.getActivityId());
}
}

   前面介紹bpmn流程圖的時候,我就跟你們說過流程檔案的id很重要,後面我們要找流程,基本都是根據這個id(activiti裡面稱之為key)來查詢的。

我們在資料庫表中也可以找到:

你可以自己對比下你自己的流程檔案。

我們還是看看資料庫的資訊有什麼變化先:

啟動一個流程時,act_ru_execution會有兩條記錄,一條是流程例項記錄,資料基本不會變。另一條是流程例項執行記錄,會記錄當前流程例項走到哪個節點。所以我們會在上圖看到兩條記錄,我們獲取到的流程例項都是第一條記錄,也就是PROC_INST_ID_的那條記錄

這裡歷史任務表,他會記錄流程例項所經歷過的所有任務,也包括自己當前正在執行的任務。同樣,所有歷史表也都是這樣的,都會記錄以前執行過的 + 當前正在執行的任務

這個歷史任務表和act_ru_task相對應

這個表和act_ru_execution相對應

這個表和act_ru_identitylink相對應

這個歷史表記錄了流程經歷過的每一個節點,和我們前面act_hi_taskinst、act_ru_task記錄的不太一樣

這三個表關注的重點不同:

act_ru_task:這個表是流程推進比較關鍵的一個表,他會記錄我們這個流程例項究竟走到哪一步了,他只會記錄當前正要執行的任務節點,未開始的節點不會出現,已經結束的節點會被刪除。因為這個機制,所以當我們想知道流程走到哪了,只需要查這個表就行了。

act_hi_taskinst:這個表只記錄我們已經執行過的任務和當前正要執行的任務。他只關心任務節點,至於其他的流程節點,如開始節點、結束節點、閘道器節點等等,他都不管。如果你只想知道流程都走過哪些任務,什麼時候執行,那查這個表就夠了。

act_hi_actinst:這個表記錄的比較詳細,他包括act_hi_taskinst的記錄,以及流程節點的執行記錄。如果我們想知道這個流程完整的執行情況,就需要查這個表。

啟動了流程,那就來查一下使用者的任務吧

package activiti01;

import org.activiti.engine.ProcessEngine;
import org.activiti.engine.ProcessEngines;
import org.activiti.engine.TaskService;
import org.activiti.engine.task.Task; import java.util.List; /**
* 查詢當前使用者的任務列表
* */
public class ActivitiTaskQuery {
public static void main(String[] args) {
// 1、得到processEngine物件
ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();
// 2、得到TaskService物件
TaskService taskService = processEngine.getTaskService();
// 3、用流程定義的key和負責人assignee來實現當前使用者的任務列表查詢
List<Task> taskList = taskService.createTaskQuery()
// .processDefinitionKey("holiday4")
.taskAssignee("zhangsan")
.list();
// 4、任務列表查詢
for (Task task : taskList) {
System.err.println("流程例項id ==> "+task.getProcessInstanceId());
System.err.println("任務定義key ==> "+task.getTaskDefinitionKey());
System.err.println("任務id ==> "+task.getId());
System.err.println("任務處理人 ==> "+task.getAssignee());
System.err.println("任務名 ==> "+task.getName());
}
}
}
TaskService 提供了很多查詢任務的api,可以很方便的進行查詢
給你們看看上面那段taskService的查詢sql

不知道你們還記不記得我前面跟你們說過,activiti查某個人的任務,只需要用自己的使用者標識去調activiti的api就行了,不需要為每個流程都寫一套查詢方法。看到這裡你們應該明白了吧。

當然,如果你有追求一點,也可以為每個流程都進行一下個性化查詢調整,反正activiti的api執行挺多的查詢條件。

任務查完了,那就來完成任務吧

package activiti01;

import org.activiti.engine.ProcessEngine;
import org.activiti.engine.ProcessEngines;
import org.activiti.engine.TaskService;
import org.activiti.engine.task.Task; import java.util.List; /**
* 處理使用者任務
* 影響的表:
* act_hi_actinst
* act_hi_identitylink
* act_hi_taskinst
* act_ru_identitylink
* act_ru_task
* 如果流程結束了,ru的表裡面資料將會被全部清掉
* */
public class ActivitiTackComplete {
public static void main(String[] args) {
// 1、得到processEngine物件
ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();
// 2、得到TaskService物件
TaskService taskService = processEngine.getTaskService();
// 3、結合任務查詢,將查詢到的任務進行處理
List<Task> taskList = taskService.createTaskQuery()
// .processDefinitionKey("holiday")
.taskAssignee("zhangsan")
.list();
// 4、完成任務
for (Task task : taskList) {
taskService.complete(task.getId());
System.err.println(task.getName());
}
}
}

我們再來看看控制檯列印的sql,我把多餘的都刪了

 --- starting CompleteTaskCmd --------------------------------------------------------
==> Preparing: select * from ACT_RU_TASK where ID_ = ?
==> Parameters: 2505(String)
<== Total: 1
==> Preparing: select * from ACT_RE_PROCDEF where ID_ = ?
==> Parameters: holiday:1:4(String)
<== Total: 1
==> Preparing: select * from ACT_RE_DEPLOYMENT where ID_ = ?
==> Parameters: 1(String)
<== Total: 1
Processing deployment 請假申請流程
==> Preparing: select * from ACT_GE_BYTEARRAY where DEPLOYMENT_ID_ = ? order by NAME_ asc
==> Parameters: 1(String)
<== Total: 2
Processing BPMN resource bpmn/holiday.bpmn
Parsing process holiday
==> Preparing: select * from ACT_RE_PROCDEF where DEPLOYMENT_ID_ = ? and KEY_ = ? and (TENANT_ID_ = '' or TENANT_ID_ is null)
==> Parameters: 1(String), holiday(String)
<== Total: 1 --- starting GetProcessDefinitionInfoCmd --------------------------------------------------------
==> Preparing: select * from ACT_PROCDEF_INFO where PROC_DEF_ID_ = ?
==> Parameters: holiday:1:4(String)
<== Total: 0
--- GetProcessDefinitionInfoCmd finished -------------------------------------------------------- ==> Preparing: select * from ACT_RU_TASK where PARENT_TASK_ID_ = ?
==> Parameters: 2505(String)
<== Total: 0
==> Preparing: select * from ACT_RU_IDENTITYLINK where TASK_ID_ = ?
==> Parameters: 2505(String)
<== Total: 0
==> Preparing: select * from ACT_RU_VARIABLE where TASK_ID_ = ?
==> Parameters: 2505(String)
<== Total: 0
Current history level: AUDIT, level required: AUDIT
==> Preparing: select * from ACT_HI_TASKINST where ID_ = ?
==> Parameters: 2505(String)
<== Total: 1
==> Preparing: select E.*, S.PROC_INST_ID_ AS PARENT_PROC_INST_ID_ from ACT_RU_EXECUTION E LEFT OUTER JOIN ACT_RU_EXECUTION S ON E.SUPER_EXEC_ = S.ID_ where E.ID_ = ?
==> Parameters: 2502(String)
<== Total: 1
==> Preparing: select distinct T.* from ACT_RU_TASK T where T.EXECUTION_ID_ = ?
==> Parameters: 2502(String)
<== Total: 1
==> Preparing: select * from ACT_HI_ACTINST RES where EXECUTION_ID_ = ? and ACT_ID_ = ? and END_TIME_ is null
==> Parameters: 2502(String), usertask1(String)
<== Total: 1 --- starting GetNextIdBlockCmd --------------------------------------------------------
==> Preparing: select * from ACT_GE_PROPERTY where NAME_ = ?
==> Parameters: next.dbid(String)
<== Total: 1
==> Preparing: update ACT_GE_PROPERTY SET REV_ = ?, VALUE_ = ? where NAME_ = ? and REV_ = ?
==> Parameters: 4(Integer), 7501(String), next.dbid(String), 3(Integer)
<== Updates: 1
--- GetNextIdBlockCmd finished -------------------------------------------------------- ==> Preparing: select E.*, S.PROC_INST_ID_ AS PARENT_PROC_INST_ID_ from ACT_RU_EXECUTION E LEFT OUTER JOIN ACT_RU_EXECUTION S ON E.SUPER_EXEC_ = S.ID_ where E.ID_ = ?
==> Parameters: 2501(String)
<== Total: 1
==> Preparing: select * from ACT_RU_IDENTITYLINK where PROC_INST_ID_ = ?
==> Parameters: 2501(String)
<== Total: 1 inserting: org.activit[email protected]ae202c6
==> Preparing: insert into ACT_HI_TASKINST ( ID_, PROC_DEF_ID_, PROC_INST_ID_, EXECUTION_ID_, NAME_, PARENT_TASK_ID_, DESCRIPTION_, OWNER_, ASSIGNEE_, START_TIME_, CLAIM_TIME_, END_TIME_, DURATION_, DELETE_REASON_, TASK_DEF_KEY_, FORM_KEY_, PRIORITY_, DUE_DATE_, CATEGORY_, TENANT_ID_ ) values ( ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ? )
==> Parameters: 5002(String), holiday:1:4(String), 2501(String), 2502(String), 部門經理稽核(String), null, null, null, lisi(String), 2020-06-25 13:38:49.636(Timestamp), null, null, null, null, usertask2(String), null, 50(Integer), null, null, (String)
<== Updates: 1 inserting: HistoricActivityInstanceEntity[id=5001, activityId=usertask2, activityName=部門經理稽核]
==> Preparing: insert into ACT_HI_ACTINST ( ID_, PROC_DEF_ID_, PROC_INST_ID_, EXECUTION_ID_, ACT_ID_, TASK_ID_, CALL_PROC_INST_ID_, ACT_NAME_, ACT_TYPE_, ASSIGNEE_, START_TIME_, END_TIME_, DURATION_, DELETE_REASON_, TENANT_ID_ ) values ( ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ? )
==> Parameters: 5001(String), holiday:1:4(String), 2501(String), 2502(String), usertask2(String), 5002(String), null, 部門經理稽核(String), userTask(String), lisi(String), 2020-06-25 13:38:49.615(Timestamp), null, null, null, (String)
<== Updates: 1 inserting: org.activit[email protected]2a869a16
==> Preparing: insert into ACT_HI_IDENTITYLINK (ID_, TYPE_, USER_ID_, GROUP_ID_, TASK_ID_, PROC_INST_ID_) values (?, ?, ?, ?, ?, ?)
==> Parameters: 5003(String), participant(String), lisi(String), null, null, 2501(String)
<== Updates: 1 inserting: Task[id=5002, name=部門經理稽核]
==> Preparing: insert into ACT_RU_TASK (ID_, REV_, NAME_, PARENT_TASK_ID_, DESCRIPTION_, PRIORITY_, CREATE_TIME_, OWNER_, ASSIGNEE_, DELEGATION_, EXECUTION_ID_, PROC_INST_ID_, PROC_DEF_ID_, TASK_DEF_KEY_, DUE_DATE_, CATEGORY_, SUSPENSION_STATE_, TENANT_ID_, FORM_KEY_, CLAIM_TIME_) values (?, 1, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ? )
==> Parameters: 5002(String), 部門經理稽核(String), null, null, 50(Integer), 2020-06-25 13:38:49.615(Timestamp), null, lisi(String), null, 2502(String), 2501(String), holiday:1:4(String), usertask2(String), null, null, 1(Integer), (String), null, null
<== Updates: 1 inserting: IdentityLinkEntity[id=5003, type=participant, userId=lisi, processInstanceId=2501]
==> Preparing: insert into ACT_RU_IDENTITYLINK (ID_, REV_, TYPE_, USER_ID_, GROUP_ID_, TASK_ID_, PROC_INST_ID_, PROC_DEF_ID_) values (?, 1, ?, ?, ?, ?, ?, ?)
==> Parameters: 5003(String), participant(String), lisi(String), null, null, 2501(String), null
<== Updates: 1 updating: Execution[ id '2502' ] - activity 'usertask2 - parent '2501'
==> Preparing: update ACT_RU_EXECUTION set REV_ = ?, BUSINESS_KEY_ = ?, PROC_DEF_ID_ = ?, ACT_ID_ = ?, IS_ACTIVE_ = ?, IS_CONCURRENT_ = ?, IS_SCOPE_ = ?, IS_EVENT_SCOPE_ = ?, IS_MI_ROOT_ = ?, PARENT_ID_ = ?, SUPER_EXEC_ = ?, ROOT_PROC_INST_ID_ = ?, SUSPENSION_STATE_ = ?, NAME_ = ?, IS_COUNT_ENABLED_ = ?, EVT_SUBSCR_COUNT_ = ?, TASK_COUNT_ = ?, JOB_COUNT_ = ?, TIMER_JOB_COUNT_ = ?, SUSP_JOB_COUNT_ = ?, DEADLETTER_JOB_COUNT_ = ?, VAR_COUNT_ = ?, ID_LINK_COUNT_ = ? where ID_ = ? and REV_ = ?
==> Parameters: 2(Integer), null, holiday:1:4(String), usertask2(String), true(Boolean), false(Boolean), false(Boolean), false(Boolean), false(Boolean), 2501(String), null, 2501(String), 1(Integer), null, false(Boolean), 0(Integer), 0(Integer), 0(Integer), 0(Integer), 0(Integer), 0(Integer), 0(Integer), 0(Integer), 2502(String), 1(Integer)
<== Updates: 1 updating: HistoricActivityInstanceEntity[id=2504, activityId=usertask1, activityName=填寫請假申請]
==> Preparing: update ACT_HI_ACTINST set EXECUTION_ID_ = ?, ASSIGNEE_ = ?, END_TIME_ = ?, DURATION_ = ?, DELETE_REASON_ = ? where ID_ = ?
==> Parameters: 2502(String), zhangsan(String), 2020-06-25 13:38:49.552(Timestamp), 227019(Long), null, 2504(String)
<== Updates: 1 updating: org.activit[email protected]46aa712c
==> Preparing: update ACT_HI_TASKINST set PROC_DEF_ID_ = ?, EXECUTION_ID_ = ?, NAME_ = ?, PARENT_TASK_ID_ = ?, DESCRIPTION_ = ?, OWNER_ = ?, ASSIGNEE_ = ?, CLAIM_TIME_ = ?, END_TIME_ = ?, DURATION_ = ?, DELETE_REASON_ = ?, TASK_DEF_KEY_ = ?, FORM_KEY_ = ?, PRIORITY_ = ?, DUE_DATE_ = ?, CATEGORY_ = ? where ID_ = ?
==> Parameters: holiday:1:4(String), 2502(String), 填寫請假申請(String), null, null, null, zhangsan(String), null, 2020-06-25 13:38:49.516(Timestamp), 226968(Long), null, usertask1(String), null, 50(Integer), null, null, 2505(String)
<== Updates: 1 ==> Preparing: delete from ACT_RU_TASK where ID_ = ? and REV_ = ?
==> Parameters: 2505(String), 1(Integer)
<== Updates: 1
--- CompleteTaskCmd finished -------------------------------------------------------- 填寫請假申請
Disconnected from the target VM, address: '127.0.0.1:60950', transport: 'socket' Process finished with exit code 0

我們來分析下日誌:

starting CompleteTaskCmd
  starting GetProcessDefinitionInfoCmd
  GetProcessDefinitionInfoCmd finished
  
  starting GetNextIdBlockCmd
  GetNextIdBlockCmd finished
CompleteTaskCmd finished
整體步驟就這樣。繼續詳細分析

想必資料庫表的資料變化,不需要我帶你們看吧

到目前為止,基本的使用就講完了。

我們來總結下:

ProcessEngineConfiguration類:
  主要作用是載入activiti.cfg.xml配置檔案。

ProcessEngine類:
  幫助我們快速得到各個service介面,並生成activiti的工作環境(也就是25張表)

Service介面:
  可以快速幫我們實現資料庫25張表的操作
  RepositoryService
  RuntimeService
  TaskService
  HistoryService

使用步驟:
  1、bpmn檔案設計外掛安裝
  2、畫流程圖
  3、流程部署:
    方法一:通過bpmn檔案部署
    方法二:通過zip壓縮包部署
    主要通過RepositoryService來實現
  4、啟動流程例項:
    主要通過RuntimeService來實現
  5、檢視使用者有哪些任務:
    主要通過TaskService來實現
  6、完成任務:
    主要通過TaskService來實現