文章源碼託管:https://github.com/OUYANGSIHAI/Activiti-learninig
歡迎 star !!!java
原本想着閒來無事,前面在項目中剛剛用到了工做流 Activiti 框架,寫寫博客的,可是,事情老是紛紛雜雜,一直拖延到如今,這一節本來想要寫一下關於 Activiti 的 API
,可是,想着太多這樣的博客了,並且顯得太生硬,難以理解,因此,這些 API
就在實際的 demo 中來說解。git
在開始作工做流以前,咱們首先應該把具體的業務在工做流的部署流程圖體現出來,而且都測試經過,這樣就至關於成功了一半,後面的具體業務的開發就相對輕鬆一些了。github
首先,咱們先看一看在 idea 中有哪些控件,經常使用的控件進行了標註。spring
下面咱們講一下創建一個流程圖的具體過程。sql
首先,咱們須要拉入一個開始節點到 bpmn
文件中,這是圖像化的界面,只須要拉入便可。數據庫
而後,咱們從控件中拉入一個 UserTask
用戶任務節點到 bpmn
文件中。編程
這樣子就有了兩個審批節點了,若是還須要其餘的一些業務需求,咱們還能夠加入一些網關,這裏就暫時不加了。api
最後,咱們只須要一個結束節點 EndEvent
就完成了這個工做流的部署圖的繪製。微信
咱們最後看一下完整的例子。框架
看似已經完成了整個流程圖的繪製,但美中不足的是咱們目前並無設置導師審批和輔導員審批到底由誰來審批,因此,咱們仍是須要來瞅一瞅怎麼設置審批人員。
首先,咱們須要選中一個審批節點,例如,選中導師審批這個節點。
其次,咱們就顯而易見的能夠在 idea 編輯器的左側看到一個名爲 BPMN editor
的屬性框,裏面包括一個用戶任務節點的能夠設置的全部屬性。
注意:候選用戶、候選組、任務監聽器,這三個屬性這裏暫時不講,後面再補充。
因爲,這一步咱們須要設置審批人,因此,咱們須要在 Assignee
這個屬性中設置咱們的審批人。
如上圖,這裏設置導師審批這個節點的審批人爲 sihai
。設置審批人除了直接設置以外,還有兩種方式設置,後面再補充。
另一個審批節點也經過這種方式設置就能夠完成審批人的設置了。
very good,這樣就基本完成了一個流程圖的建立。接下來,咱們將經過實例來具體講解Activiti 的 API 的講解。
在上面這個流程圖的建立中,咱們尚未生成 png 圖片,因此,若是不知道如何生成的,能夠參考以前的這篇文章:Activiti工做流從入門到入土:整合spring。
既然是講解 API ,那麼仍是先看一下主要有哪些 API 吧,這樣纔有一個總體把握。
這些 API 具體怎麼用,接下來一一道來。
既然是流程定義,那確定少不了如何部署流程定義了。
@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 文件內容自動生成流程圖)。兩個文件不是很大,都是以二進制形式存儲在數據庫中。
/** * 部署流程定義(從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的輸入流用做部署流程定義,其餘使用並沒有區別。
部署了流程定義以後,咱們應該想查看一下流程定義的一些信息。
/** * 查詢流程定義 */ @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()
方法來設置一些查詢參數,好比經過條件、降序升序等。
經過刪除部署 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 刪除信息。這裏的做用主要是查詢圖片,經過圖片能夠在後面作流程展現用的。咱們看看具體怎麼查看。
/** * 查看流程圖 * * @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盤下,能夠找到圖片。
/** * 查詢最新版本的流程定義 */ @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("*********************************************************************************"); } } }
輸出結果:
一、部署流程定義用到了 Activiti 的下面的幾張表。
二、咱們發現部署流程定義的操做都是在 RepositoryService
這個類下進行操做的,咱們只須要經過 getRepositoryService()
拿到對象,經過鏈式規則就能夠進行部署流程定義的全部操做。
這一節,咱們經過一個完整的例子,來總結一下前面講過的一些基本的知識,這樣可以更好的學習前面以及後面的知識點,這也算是一個過渡的章節。
回到第一節的創建流程圖,咱們已經將基本的 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(){ //一、流程定義的key,經過這個key來啓動流程實例 String processDefinitionKey = "hello"; //二、與正在執行的流程實例和執行對象相關的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
,獲取優質學習資源。