原文鏈接:blog.ouyangsihai.cn >> Activiti工作流從入門到入土:完整Hello World大比拼(Activiti工作流 API結合實例講解)
文章源碼托管:https://github.com/OUYANGSIHAI/Activiti-learninig
歡迎 star !!!
本來想著閑來無事,前面在項目中剛剛用到了工作流 Activiti 框架,寫寫博客的,但是,事情總是紛紛雜雜,一直拖延到現在,這一節原本想要寫一下關于 Activiti 的 API
,但是,想著太多這樣的博客了,而且顯得太生硬,難以理解,所以,這些 API
就在實際的 demo 中來講解。
一、建立流程圖
在開始做工作流之前,我們首先應該把具體的業務在工作流的部署流程圖體現出來,并且都測試通過,這樣就相當于成功了一半,后面的具體業務的開發就相對輕松一些了。
首先,我們先看一看在 idea 中有哪些控件,常用的控件進行了標注。
下面我們講一下建立一個流程圖的具體過程。
首先,我們需要拉入一個開始節點到 bpmn
文件中,這是圖像化的界面,只需要拉入即可。
然后,我們從控件中拉入一個 UserTask
用戶任務節點到 bpmn
文件中。
這樣子就有了兩個審批節點了,如果還需要其他的一些業務需求,我們還可以加入一些網關,這里就暫時不加了。
最后,我們只需要一個結束節點 EndEvent
就完成了這個工作流的部署圖的繪制。
我們最后看一下完整的例子。
看似已經完成了整個流程圖的繪制,但美中不足的是我們目前并沒有設置導師審批和輔導員審批到底由誰來審批,所以,我們還是需要來瞅一瞅怎么設置審批人員。
首先,我們需要選中一個審批節點,例如,選中導師審批這個節點。
其次,我們就顯而易見的可以在 idea 編輯器的左側看到一個名為 BPMN editor
的屬性框,里面包括一個用戶任務節點的可以設置的所有屬性。
注意:候選用戶、候選組、任務監聽器,這三個屬性這里暫時不講,后面再補充。
由于,這一步我們需要設置審批人,所以,我們需要在 Assignee
這個屬性中設置我們的審批人。
如上圖,這里設置導師審批這個節點的審批人為 sihai
。設置審批人除了直接設置之外,還有兩種方式設置,后面再補充。
另外一個審批節點也通過這種方式設置就可以完成審批人的設置了。
very good,這樣就基本完成了一個流程圖的創建。接下來,我們將通過實例來具體講解Activiti 的 API 的講解。
二、實例講解 API
在上面這個流程圖的創建中,我們還沒有生成 png 圖片,所以,如果不知道如何生成的,可以參考之前的這篇文章:Activiti工作流從入門到入土:整合spring。
既然是講解 API ,那么還是先看一下主要有哪些 API 吧,這樣才有一個整體把握。
這些 API 具體怎么用,接下來一一道來。
2.1 流程定義
既然是流程定義,那肯定少不了如何部署流程定義了。
2.1.1 部署流程定義方法1
@Autowired
private ProcessEngine processEngine;
@Autowired
private TaskService taskService;
@Autowired
private RuntimeService runtimeService;
@Autowired
private HistoryService historyService;
/**
* 部署流程定義(從classpath)
*/
@Test
public void deploymentProcessDefinition_classpath(){
Deployment deployment = processEngine.getRepositoryService()//與流程定義和部署對象相關的Service
.createDeployment()//創建一個部署對象
.name("流程定義")//添加部署的名稱
.addClasspathResource("bpmn/hello.bpmn")//從classpath的資源中加載,一次只能加載一個文件
.addClasspathResource("bpmn/hello.png")//從classpath的資源中加載,一次只能加載一個文件
.deploy();//完成部署
System.out.println("部署ID:"+deployment.getId());
System.out.println("部署名稱:"+deployment.getName());
}
注意:這里用的是整合 spring 之后的 junit 測試環境,如何整合 spring 請看這篇文章:Activiti工作流從入門到入土:整合spring。
輸出結果:
這樣,我們就部署了這個流程。那么具體是怎么操作的呢,我們再來看看整個過程。
獲取流程引擎對象:這個跟 spring 整合了。
通過流程引擎獲取了一個 RepositoryService 對象(倉庫對象)
由倉庫的服務對象產生一個部署對象配置對象,用來封裝部署操作的相關配置。
這是一個鏈式編程,在部署配置對象中設置顯示名,上傳流程定義規則文件
向數據庫表中存放流程定義的規則信息。
其實,這一步操作,用到了 Activiti 數據庫中的三張表,分別是:act_re_deployment(部署對象表),act_re_procdef(流程定義表),act_ge_bytearray(資源文件表)。
我們看看這三張表的變化:
1)act_re_deployment
可以看到,部署ID和部署名稱就存在這張表中。
2)act_re_procdef
這張表中,存放了部署的Deployment_ID部署流程的id、bpmn資源文件名稱、png圖片名稱等信息。
3)act_ge_bytearray
存儲流程定義相關的部署信息。即流程定義文檔的存放地。每部署一次就會增加兩條記錄,一條是關于 bpmn 規則文件的,一條是圖片的(如果部署時只指定了 bpmn 一個文件,activiti 會在部署時解析 bpmn 文件內容自動生成流程圖)。兩個文件不是很大,都是以二進制形式存儲在數據庫中。
2.1.2 部署流程定義方法2
/**
* 部署流程定義(從zip)
*/
@Test
public void deploymentProcessDefinition_zip(){
InputStream in = this.getClass().getClassLoader().getResourceAsStream("bpmn/hello.zip");
ZipInputStream zipInputStream = new ZipInputStream(in);
Deployment deployment = processEngine.getRepositoryService()//與流程定義和部署對象相關的Service
.createDeployment()//創建一個部署對象
.name("流程定義")//添加部署的名稱
.addZipInputStream(zipInputStream)//指定zip格式的文件完成部署
.deploy();//完成部署
System.out.println("部署ID:"+deployment.getId());//
System.out.println("部署名稱:"+deployment.getName());//
}
項目結構如下:
輸出結果:
如此看來,也是沒有任何問題的,唯一的區別只是壓縮成zip格式的文件,使用zip的輸入流用作部署流程定義,其他使用并無區別。
部署了流程定義之后,我們應該想查看一下流程定義的一些信息。
2.1.3 查看流程定義
/**
* 查詢流程定義
*/
@Test
public void findProcessDefinition(){
List<ProcessDefinition> list = processEngine.getRepositoryService()//與流程定義和部署對象相關的Service
.createProcessDefinitionQuery()//創建一個流程定義的查詢
/**指定查詢條件,where條件*/
// .deploymentId(deploymentId)//使用部署對象ID查詢
// .processDefinitionId(processDefinitionId)//使用流程定義ID查詢
// .processDefinitionKey(processDefinitionKey)//使用流程定義的key查詢
// .processDefinitionNameLike(processDefinitionNameLike)//使用流程定義的名稱模糊查詢
/**排序*/
.orderByProcessDefinitionVersion().asc()//按照版本的升序排列
// .orderByProcessDefinitionName().desc()//按照流程定義的名稱降序排列
/**返回的結果集*/
.list();//返回一個集合列表,封裝流程定義
// .singleResult();//返回惟一結果集
// .count();//返回結果集數量
// .listPage(firstResult, maxResults);//分頁查詢
if(list!=null && list.size()>0){
for(ProcessDefinition pd:list){
System.out.println("流程定義ID:"+pd.getId());//流程定義的key+版本+隨機生成數
System.out.println("流程定義的名稱:"+pd.getName());//對應hello.bpmn文件中的name屬性值
System.out.println("流程定義的key:"+pd.getKey());//對應hello.bpmn文件中的id屬性值
System.out.println("流程定義的版本:"+pd.getVersion());//當流程定義的key值相同的相同下,版本升級,默認1
System.out.println("資源名稱bpmn文件:"+pd.getResourceName());
System.out.println("資源名稱png文件:"+pd.getDiagramResourceName());
System.out.println("部署對象ID:"+pd.getDeploymentId());
System.out.println("*********************************************");
}
}
}
輸出結果:
查詢流程定義小結:
流程定義和部署對象相關的Service都是
RepositoryService
,后面會發現關于流程定義的都是RepositoryService
。通過這個
createProcessDefinitionQuery()
方法來設置一些查詢參數,比如通過條件、降序升序等。
2.1.4 刪除流程定義
通過刪除部署 ID 為2501的信息。
/**
* 刪除流程定義
*/
@Test
public void deleteProcessDefinition(){
//使用部署ID,完成刪除,指定部署對象id為2501刪除
String deploymentId = "2501";
/**
* 不帶級聯的刪除
* 只能刪除沒有啟動的流程,如果流程啟動,就會拋出異常
*/
// processEngine.getRepositoryService()//
// .deleteDeployment(deploymentId);
/**
* 級聯刪除
* 不管流程是否啟動,都能可以刪除
*/
processEngine.getRepositoryService()//
.deleteDeployment(deploymentId, true);
System.out.println("刪除成功!");
}
輸出結果:
到數據庫查看,發現 act_re_deployment
中的數據已經不存在了。
- 這里還是通過
getRepositoryService()
方法獲取部署定義對象,然后指定 ID 刪除信息。
2.1.5 獲取流程定義文檔的資源
這里的作用主要是查詢圖片,通過圖片可以在后面做流程展示用的。我們看看具體怎么查看。
/**
* 查看流程圖
*
* @throws IOException
*/
@Test
public void viewPic() throws IOException {
/**將生成圖片放到文件夾下*/
String deploymentId = "5001";
//獲取圖片資源名稱
List<String> list = processEngine.getRepositoryService()//
.getDeploymentResourceNames(deploymentId);
//定義圖片資源的名稱
String resourceName = "";
if (list != null && list.size() > 0) {
for (String name : list) {
if (name.indexOf(".png") >= 0) {
resourceName = name;
}
}
}
//獲取圖片的輸入流
InputStream in = processEngine.getRepositoryService()//
.getResourceAsStream(deploymentId, resourceName);
//將圖片生成到F盤的目錄下
File file = new File("F:/" + resourceName);
//將輸入流的圖片寫到磁盤
FileUtils.copyInputStreamToFile(in, file);
}
在F盤下,可以找到圖片。
2.1.6 查詢最新版本的流程定義
/**
* 查詢最新版本的流程定義
*/
@Test
public void findLastVersionProcessDefinition() {
List<ProcessDefinition> list = processEngine.getRepositoryService()//
.createProcessDefinitionQuery()//
.orderByProcessDefinitionVersion().asc()//使用流程定義的版本升序排列
.list();
/**
map集合的特點:當map集合key值相同的情況下,后一次的值將替換前一次的值
*/
Map<String, ProcessDefinition> map = new LinkedHashMap<String, ProcessDefinition>();
if (list != null && list.size() > 0) {
for (ProcessDefinition pd : list) {
map.put(pd.getKey(), pd);
}
}
List<ProcessDefinition> pdList = new ArrayList<ProcessDefinition>(map.values());
if (pdList != null && pdList.size() > 0) {
for (ProcessDefinition pd : pdList) {
System.out.println("流程定義ID:" + pd.getId());//流程定義的key+版本+隨機生成數
System.out.println("流程定義的名稱:" + pd.getName());//對應hello.bpmn文件中的name屬性值
System.out.println("流程定義的key:" + pd.getKey());//對應hello.bpmn文件中的id屬性值
System.out.println("流程定義的版本:" + pd.getVersion());//當流程定義的key值相同的相同下,版本升級,默認1
System.out.println("資源名稱bpmn文件:" + pd.getResourceName());
System.out.println("資源名稱png文件:" + pd.getDiagramResourceName());
System.out.println("部署對象ID:" + pd.getDeploymentId());
System.out.println("*********************************************************************************");
}
}
}
輸出結果:
2.1.7 流程定義總結
1、部署流程定義用到了 Activiti 的下面的幾張表。
- act_re_deployment:部署對象表
- act_re_procdef:流程定義表
- act_ge_bytearray:資源文件表
- act_ge_property:主鍵生成策略表
2、我們發現部署流程定義的操作都是在 RepositoryService
這個類下進行操作的,我們只需要通過 getRepositoryService()
拿到對象,通過鏈式規則就可以進行部署流程定義的所有操作。
2.2 工作流完整實例的使用
這一節,我們通過一個完整的例子,來總結一下前面講過的一些基本的知識,這樣能夠更好的學習前面以及后面的知識點,這也算是一個過渡的章節。
回到第一節的建立流程圖,我們已經將基本的 bpmn 圖已經建立好了,但是,需要做一個完整的實例,我們還是需要補充一些內容的,這樣才能夠把這樣的一個實例做好,我們先把第一節的那個 bpmn 圖拿過來。
首先,我們需要明確:這個圖到目前為止,我們只是簡簡單單的把流程給畫出來了,比如,我們需要審核的時候,是需要具體到某一個具體的人員去審核的,所以,我們需要給每個節點設置審核的具體人員。
注意:設置節點的審核人員后面還會分一節細講,這里只是做一個簡單的實例,所以,只需要這里能夠看懂,做好就ok了。
設置審核人員步驟
首先,我們需要選中一個節點,例如,下圖中的“導師審批”節點。
接下來,在左邊的工具欄,我們會看到好多選項,有一項為 Assignee ,我們需要在這個選項中設置我們這個節點需要設置的審批人。
Assignee設置格式:直接使用英文或者中文都可以,例如,sihai
,更復雜的設置后面再講。
下面的節點設置也是跟上面一模一樣。
輔導員審批的審批人員是:歐陽思海。
perfect,這樣流程圖的任務就完成了,下面我們就可以進行這個實例的測試階段了。
1)部署流程定義
部署流程定義,在前面的章節已經講過了,有兩種方式進行處理,一種是加載 bpmn 文件和 png 文件,還有一種是將這兩個文件壓縮成 zip 格式的壓縮文件,然后加載。這里我們使用第一種方式進行處理。
/**
* 部署流程定義(從classpath)
*/
@Test
public void deploymentProcessDefinition_classpath() {
Deployment deployment = processEngine.getRepositoryService()//與流程定義和部署對象相關的Service
.createDeployment()//創建一個部署對象
.name("hello")//添加部署的名稱
.addClasspathResource("bpmn/hello.bpmn")//從classpath的資源中加載,一次只能加載一個文件
.addClasspathResource("bpmn/hello.png")//從classpath的資源中加載,一次只能加載一個文件
.deploy();//完成部署
log.info("部署ID:" + deployment.getId());
log.info("部署名稱:" + deployment.getName());
}
現在流程定義已經有了,下面我們就需要啟動這個流程實例。
關于關于這一步做了什么事情,可以在前面的章節查看。
2)啟動流程實例
/**
* 啟動流程實例
*/
@Test
public void startProcessInstance(){
//1、流程定義的key,通過這個key來啟動流程實例
String processDefinitionKey = "hello";
//2、與正在執行的流程實例和執行對象相關的Service
// startProcessInstanceByKey方法還可以設置其他的參數,比如流程變量。
ProcessInstance pi = processEngine.getRuntimeService()
.startProcessInstanceByKey(processDefinitionKey);//使用流程定義的key啟動流程實例,key對應helloworld.bpmn文件中id的屬性值,使用key值啟動,默認是按照最新版本的流程定義啟動
log.info("流程實例ID:"+pi.getId());//流程實例ID
log.info("流程定義ID:"+pi.getProcessDefinitionId());//流程定義ID
}
注意: processDefinitionKey 是 bpmn 文件的名稱。
步驟
1 獲取到 runtimeService 實例。
2 通過 bpmn 文件的名稱,也就是 processDefinitionKey 來啟動流程實例。
3 啟動流程后,流程的任務就走到了導師審批節點。
下面就是查詢個人任務了,我們可以查詢導師審批節點的任務。
3)查詢個人任務
/**
* 查詢當前人的個人任務
*/
@Test
public void findPersonalTask(){
String assignee = "sihai";
List<Task> list = processEngine.getTaskService()//與正在執行的任務管理相關的Service
.createTaskQuery()//創建任務查詢對象
/**查詢條件(where部分)*/
.taskAssignee(assignee)//指定個人任務查詢,指定辦理人
// .taskCandidateUser(candidateUser)//組任務的辦理人查詢
// .processDefinitionId(processDefinitionId)//使用流程定義ID查詢
// .processInstanceId(processInstanceId)//使用流程實例ID查詢
// .executionId(executionId)//使用執行對象ID查詢
/**排序*/
.orderByTaskCreateTime().asc()//使用創建時間的升序排列
/**返回結果集*/
// .singleResult()//返回惟一結果集
// .count()//返回結果集的數量
// .listPage(firstResult, maxResults);//分頁查詢
.list();//返回列表
if(list!=null && list.size()>0){
for(Task task:list){
log.info("任務ID:"+task.getId());
log.info("任務名稱:"+task.getName());
log.info("任務的創建時間:"+task.getCreateTime());
log.info("任務的辦理人:"+task.getAssignee());
log.info("流程實例ID:"+task.getProcessInstanceId());
log.info("執行對象ID:"+task.getExecutionId());
log.info("流程定義ID:"+task.getProcessDefinitionId());
log.info("********************************************");
}
}
}
通過 sihai
這個審批人,查詢到了下面的信息。
分析步驟
1 首先通過 getTaskService 方法,獲取到 TaskService 對象。
2 通過 createTaskQuery 方法創建查詢對象。
3 通過 taskAssignee 方法設置審核人。
4 對于結果的返回,我們可以通過 orderByTaskCreateTime().asc() 設置排序等其他信息。
這里需要注意一點,查詢到的一個重要的信息是:任務 id(taskId),下一步,我們需要通過這個任務 id ,來完成任務。
4)辦理個人任務
/**
* 完成我的任務
*/
@Test
public void completePersonalTask() {
//任務ID,上一步查詢得到的。
String taskId = "7504";
processEngine.getTaskService()//與正在執行的任務管理相關的Service
.complete(taskId);
log.info("完成任務:任務ID:" + taskId);
}
通過上一步的任務 id :7504,完成任務。
步驟
1 首先,通過 getTaskService 方法拿到 TaskService 對象。
2 調用 complete 方法,給定具體的任務 id 完成任務。
5)查詢流程狀態(判斷流程走到哪一個節點)
這個接口還是十分需要的,當我們在具體的業務中,我們需要判斷我們的流程的狀態是什么狀態,或者說我們的流程走到了哪一個節點的時候,這一個接口就讓我們實現業務省了非常多的事情。
/**
* 查詢流程狀態(判斷流程走到哪一個節點)
*/
@Test
public void isProcessActive() {
String processInstanceId = "7501";
ProcessInstance pi = processEngine.getRuntimeService()//表示正在執行的流程實例和執行對象
.createProcessInstanceQuery()//創建流程實例查詢
.processInstanceId(processInstanceId)//使用流程實例ID查詢
.singleResult();
if (pi == null) {
log.info("流程已經結束");
} else {
log.info("流程沒有結束");
//獲取任務狀態
log.info("節點id:" + pi.getActivityId());
}
}
步驟:
1 獲取到流程實例 ProcessInstance 對象。
2 通過 getActivityId 方法獲取到實例 Id(節點 id )。
那么拿到了節點 Id
有什么作用呢?
其實,有了這個 Id 之后,我們就可以判斷流程走到哪一步了。例如,上面的輸出的節點 id 是 _4
,這個 _4 就是對應 輔導員審批節點的 id
,所以,我們就可以判讀流程其實是已經走到這個節點了,后期需要在頁面顯示流程狀態的時候就發揮作用了。
6)查詢流程執行的歷史信息
通過查看 activiti 5 的官方 API 接口,發現查看歷史信息有下面的查詢接口。
下面我們通過上面的實例對下面的方法一一進行測試。
歷史活動實例查詢接口
/**
* 歷史活動查詢接口
*/
@Test
public void findHistoryActivity() {
String processInstanceId = "7501";
List<HistoricActivityInstance> hais = processEngine.getHistoryService()//
.createHistoricActivityInstanceQuery()
.processInstanceId(processInstanceId)
.list();
for (HistoricActivityInstance hai : hais) {
log.info("活動id:" + hai.getActivityId()
+ " 審批人:" + hai.getAssignee()
+ " 任務id:" + hai.getTaskId());
log.info("************************************");
}
}
通過這個接口不僅僅查到這些信息,還有其他的方法,可以獲取更多的關于歷史活動的其他信息。
歷史流程實例查詢接口
/**
* 查詢歷史流程實例
*/
@Test
public void findHistoryProcessInstance() {
String processInstanceId = "7501";
HistoricProcessInstance hpi = processEngine.getHistoryService()// 與歷史數據(歷史表)相關的Service
.createHistoricProcessInstanceQuery()// 創建歷史流程實例查詢
.processInstanceId(processInstanceId)// 使用流程實例ID查詢
.orderByProcessInstanceStartTime().asc().singleResult();
log.info(hpi.getId() + " " + hpi.getProcessDefinitionId() + " " + hpi.getStartTime() + " "
+ hpi.getEndTime() + " " + hpi.getDurationInMillis());
}
這個接口可以查詢到關于歷史流程實例的所有信息。
歷史任務實例查詢接口
/**
* 查詢歷史任務
*/
@Test
public void findHistoryTask() {
String processInstanceId = "7501";
List<HistoricTaskInstance> list = processEngine.getHistoryService()// 與歷史數據(歷史表)相關的Service
.createHistoricTaskInstanceQuery()// 創建歷史任務實例查詢
.processInstanceId(processInstanceId)//
.orderByHistoricTaskInstanceStartTime().asc().list();
if (list != null && list.size() > 0) {
for (HistoricTaskInstance hti : list) {
log.info("\n 任務Id:" + hti.getId() + " 任務名稱:" + hti.getName() + " 流程實例Id:" + hti.getProcessInstanceId() + "\n 開始時間:"
+ hti.getStartTime() + " 結束時間:" + hti.getEndTime() + " 持續時間:" + hti.getDurationInMillis());
}
}
}
這個查詢接口可以查詢到歷史任務信息。
歷史流程變量查詢接口
/**
* 查詢歷史流程變量
*/
@Test
public void findHistoryProcessVariables() {
String processInstanceId = "7501";
List<HistoricVariableInstance> list = processEngine.getHistoryService()//
.createHistoricVariableInstanceQuery()// 創建一個歷史的流程變量查詢對象
.processInstanceId(processInstanceId)//
.list();
if (list != null && list.size() > 0) {
for (HistoricVariableInstance hvi : list) {
log.info("\n" + hvi.getId() + " " + hvi.getProcessInstanceId() + "\n" + hvi.getVariableName()
+ " " + hvi.getVariableTypeName() + " " + hvi.getValue());
}
}
}
在這個實例中沒有設置流程變量,所以,這里是查詢不到任何歷史信息的。
這個接口主要是關于歷史流程變量的設置的一些信息。
歷史本地接口查詢接口
/**
* 通過執行sql來查詢歷史數據,由于activiti底層就是數據庫表。
*/
@Test
public void findHistoryByNative() {
HistoricProcessInstance hpi = processEngine.getHistoryService()
.createNativeHistoricProcessInstanceQuery()
.sql("查詢底層數據庫表的sql語句")
.singleResult();
log.info("\n" + hpi.getId() + " " + hpi.getProcessDefinitionId() + " " + hpi.getStartTime()
+ "\n" + hpi.getEndTime() + " " + hpi.getDurationInMillis());
}
這個接口是提供直接通過 sql 語句
來查詢歷史信息的,我們只需要在 sql()
方法中寫原生的 sql 語句就可以進行數據查詢。
寫到這里,我想應該通過這樣的一個完整的實例將 Activiti 工作流的 API 都介紹的差不多了,這一節到這里也就要說拜拜了。再回看一下文章開頭的 API 接口,這也算是這一節的總結。
文章有不當之處,歡迎指正,如果喜歡微信閱讀,你也可以關注我的微信公眾號:
好好學java
,獲取優質學習資源。
最后,給大家分享我收藏的幾個不錯的 github 項目,內容都還是不錯的,如果覺得有幫助,可以順便給個 star。
- 計算機專業學生必須要啃的書籍推薦: https://github.com/hello-go-maker/cs-books
- Java 實戰項目推薦: https://github.com/hello-go-maker/Java-project
- 推薦一些很不錯的計算機學習教程,包括:數據結構、算法、計算機網絡、操作系統、Java(spring、springmvc、springboot、springcloud),也包括多個企業級實戰項目: https://github.com/hello-go-maker/cs-learn-source
- Java面試+Java后端技術學習指南】:一份通向理想互聯網公司的面試指南,包括 Java,技術面試必備基礎知識、Leetcode、計算機操作系統、計算機網絡、系統設計、分布式、數據庫(MySQL、Redis)、Java 項目實戰等: https://github.com/hello-java-maker/JavaInterview