一,什麼是工作流?
1,工作流(
Workflow
),就是“業務過程的部分或整體在計算機應用環境下的自動化”,它主要解決的是“使在多個參與者之間按照某種預定義的規則傳遞文檔、信息或任務的過程自動進行,從而實現某個預期的業務目標,或者促使此目標的實現。2,工作流管理系統(
Workflow Management System
,WfMS
)是一個軟件系統,它完成工作量的定義和管理,並按照在系統中預先定義好的工作流邏輯進行工作流實例的執行。工作流管理系統不是企業的業務系統,而是爲企業的業務系統的運行提供了一個軟件的支撐環境。3,常見的工作流框架有
Activity
、JBPM
、OSWorkFlow
、WorkFlow
。
二,實現自動化
1,業務流程圖要規範化,要遵守一套標準。
2,業務流程圖本質上是一個
xml
文件,這樣就可以存入所有數據。3,讀取業務流程圖的過程就是解析
xml
文件的過程。4,讀取一個業務流程圖中的節點就相當於是解析一個xml結構,進一步將數據插入到
mysql
的表中,形成一條記錄。5,將所有的節點都讀取並存入
mysql
表中。6,後面只要讀取
mysql
表中的記錄就行了,讀一條記錄就相當於讀一個節點。7,業務流程的推進,後面就轉化爲讀表中數據,並且處理數據,結束時這一行數據就可以刪除了,刪除就代表該業務流程結束。
三, ActivitiDesigner流程設計器安裝與使用(Eclipse版本)
Eclipse
版本的流程設計器下載地址:鏈接:https://pan.baidu.com/s/1kgPqXlA75JzBSZ0xvkAG1w 提取碼:vgl1
下載完成後解壓,然後替換Eclipse
安裝目錄列表中的相同的兩個目錄裏面的jar
包,也就是移到Eclipse
的安裝目錄的那兩個文件夾,然後重新打開Eclipse
,就已經有流程設計器插件了。
四,ActivitiDesigner流程設計器安裝與使用(idea版本)
安裝actiBPM
插件,並重啓idea
安裝完成後,在資源文件夾resources
右鍵新建actiBPMFiles
,然後就可以畫流程圖了。
五,Activiti支持的數據庫及準備工作
Activiti
工作流需要數據庫的25張表支撐。
數據庫:mysql
創建表方式:通過運行java程序創建表。
1,快速創建一個springboot項目
過程不用多說
2,加入maven依賴的座標(jar包)
<properties>
<java.version>1.8</java.version>
<slf4j.version>1.6.6</slf4j.version>
<log4j.version>1.2.12</log4j.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>2.0.1</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
<dependency>
<groupId>org.activiti</groupId>
<artifactId>activiti-engine</artifactId>
<version>7.0.0.Beta1</version>
</dependency>
<dependency>
<groupId>org.activiti</groupId>
<artifactId>activiti-spring</artifactId>
<version>7.0.0.Beta1</version>
</dependency>
<!--activiti整合springboot的座標-->
<dependency>
<groupId>org.activiti</groupId>
<artifactId>activiti-spring-boot-starter</artifactId>
<version>7.0.0.Beta2</version>
</dependency>
<dependency>
<groupId>org.activiti</groupId>
<artifactId>activiti-bpmn-model</artifactId>
<version>7.0.0.Beta1</version>
</dependency>
<dependency>
<groupId>org.activiti</groupId>
<artifactId>activiti-bpmn-converter</artifactId>
<version>7.0.0.Beta1</version>
</dependency>
<dependency>
<groupId>org.activiti</groupId>
<artifactId>activiti-json-converter</artifactId>
<version>7.0.0.Beta1</version>
</dependency>
<dependency>
<groupId>org.activiti</groupId>
<artifactId>activiti-bpmn-layout</artifactId>
<version>7.0.0.Beta1</version>
</dependency>
<dependency>
<groupId>org.activiti.cloud</groupId>
<artifactId>activiti-cloud-services-api</artifactId>
<version>7.0.0.Beta1</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
</dependency>
<!-- log start -->
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<version>${log4j.version}</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>
</dependency>
<!-- log end -->
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.4.5</version>
</dependency>
<dependency>
<groupId>commons-dbcp</groupId>
<artifactId>commons-dbcp</artifactId>
<version>1.4</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<repositories>
<repository>
<id>alfresco</id>
<name>Activiti Releases</name>
<url>https://artifacts.alfresco.com/nexus/content/repositories/activiti-releases/</url>
<releases>
<enabled>true</enabled>
</releases>
</repository>
</repositories>
3,配置Activiti連接數據庫的配置文件
這裏使用dbcp
連接池連接mysql
數據庫,在resources
資源文件夾下新建文件activiti.cfg.xml
文件。
前提是mysql
數據庫新建一個庫名爲activiti
。
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:tx="http://www.springframework.org/schema/tx"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/contex http://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.xsd">
<!--數據源配置dbcp-->
<bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource">
<property name="driverClassName" value="com.mysql.jdbc.Driver" />
<property name="url" value="jdbc:mysql://localhost:3306/activiti" />
<property name="username" value="root" />
<property name="password" value="1234" />
<property name="maxActive" value="3" />
<property name="maxIdle" value="1" />
</bean>
<!--activiti單獨運行的ProcessEngine配置,使用單獨啓動方式-->
<bean id="processEngineConfiguration" class="org.activiti.engine.impl.cfg.StandaloneProcessEngineConfiguration">
<property name="dataSource" ref="dataSource"></property>
<property name="databaseSchemaUpdate" value="true"/>
</bean>
<!--<bean id="processEngineConfiguration" class="org.activiti.engine.impl.cfg.StandaloneProcessEngineConfiguration">
<property name="jdbcDriver" value="com.mysql.jdbc.Driver"/>
<property name="jdbcUrl" value="jdbc:mysql://localhost:3306/itcast0711activiti"/>
<property name="jdbcUsername" value="root"/>
<property name="jdbcPassword" value="root"/>
<property name="databaseSchemaUpdate" value="true"/>
</bean>-->
</beans>
4,運行代碼來創建mysql所需要的25張表
隨便哪個地方寫下面的測試類,然後運行,那麼數據庫中就已經創建了25張表。
//測試activiti所需要的25張表的生成
@Test
public void testGenTable() {
//1,創建ProcessEngineConfiguration對象
ProcessEngineConfiguration configuration=
ProcessEngineConfiguration.createProcessEngineConfigurationFromResource("activiti.cfg.xml");
//2,創建ProcessEngine對象
ProcessEngine processEngine = configuration.buildProcessEngine();
//3,打印processEngine對象
System.out.println(processEngine);
}
5,上述25張數據庫表的命名規則
Activiti
表都以ACT_
開頭。
- ACT_RE_*,這個前綴的表包含了流程定義和流程靜態資源(圖片,規則等等)
- ACT_RU_*,這些運行時的表,包含流程實例,任務,變量,異步任務,等運行中的數據,
Activiti
只在流程實例執行過程中保存這些數據,在流程結束後就會刪除這些記錄。 - ACT_HI_*,這些表包含歷史數據,比如歷史流程實例,變量,任務等等。
- ACT_GE_*,通用數據,用於不同場景下。
六,Activiti架構圖
從圖中可以看出,通過ProcessEngine
對象可以得到下面的好多Service
,而下面的Service
的作用就是直接對數據庫中的25張表進行增刪改查操作,很簡便,不用再自己寫增刪改查了。
Activiti
架構圖說明
-
ProcessEngineConfiguration
類,主要作用是加載activiti.cfg.xml
文件。 -
ProcessEngine
類,作用是幫助我們可以快速得到各個service
接口,並且可以生成activiti
的工作環境,25張表。 -
Service
接口,作用是可以快速實現數據庫25張表的操作。-
ResposityService
-
RuntimeService
-
TaskService
-
HistoryService
-
1,使用idea的ActiveDesigner完成流程定義
新建流程holiday.bpmn
,在resources
資源文件夾下新建BPNMFile
文件,並畫一個流程,這裏舉例請假流程。
1,點擊白色區域,看左邊的id
和name
,每一個流程圖都有唯一的id
和name
,可以自定義。
2,點擊任務格子,看左邊的Assignee
,如下圖,是指定該任務分配給誰來執行的。都分配一下,這裏分配結果==》填寫請假單:zhangsan;部門經理審批:lisi;總經理審批:wangwu。
2,idea中製作Activiti流程定義的png文件
畫完圖後,但是並沒有生成png
圖片,這個時候重命名剛纔創建的文件,把後綴改成xml
,然後右鍵
然後保存到與剛纔創建文件的一個文件夾內就可以了
下面是保存按鈕
如果流程圖是中文亂碼的情況,那麼在idea
安裝目錄的下面兩個文件添加一句代碼。
-Dfile.encoding=UTF-8
然後重新啓動idea
就可以了,重新走一遍上面的過程即可。
3,Activiti實現流程定義部署的步驟及實現
寫一個測試類進行測試流程部署。
//流程定義的部署
@Test
public void testDeploy(){
//1,創建processEngine對象
ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();
//2,得到repositoryService實例
RepositoryService repositoryService = processEngine.getRepositoryService();
//3,進行部署
Deployment deployment = repositoryService.createDeployment()
.addClasspathResource("holiday.bpmn") //添加bpmn資源
.addClasspathResource("holiday.png")
.name("請假申請流程")
.deploy();
//4,輸出部署的一些信息
System.out.println(deployment.getId());
System.out.println(deployment.getName());
}
打印出
1
請假申請流程
看數據庫表act_re_deployment
表新增了一條數據。(該表存放部署信息)
act_re_procdef
表也新增了一條數據。(該表存放流程定義的一些信息)
act_ge_bytearray
新增了兩條,這是存儲的文件。(該表存放流程定義的bpmn文件及png文件)
總的來說,就是部署一個新的流程時,數據庫發生變化的是上面的三張表。
4,Activiti實現流程實例啓動的步驟及實現
提及一個概念:一個流程定義可以對應多個流程實例。
寫一個測試類,啓動流程實例。
//啓動流程實例:前提是已經完成了流程定義的部署工作
@Test
public void testBeginFlow(){
//1,創建processEngine對象
ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();
//2,得到RuntimeService實例
RuntimeService runtimeService = processEngine.getRuntimeService();
//3,創建流程實例,流程定義的key需要知道流程唯一id:holiday
ProcessInstance processInstance = runtimeService.startProcessInstanceByKey("holiday");
//4,輸出實例相關信息
System.out.println("實例部署id:"+processInstance.getDeploymentId());
System.out.println("流程實例id:"+processInstance.getId());
System.out.println("活動id:"+processInstance.getActivityId());
}
打印:
實例部署id:null
流程實例id:2501
活動id:null
看數據表act_hi_procinst
新增了一條數據。
啓動一個流程實例影響數據庫的表:
- act_hi_actinst 已完成的活動
- act_hi_identitylink 參與者信息
- act_hi_procinst 流程實例
- act_hi_taskinst 任務實例
- act_ru_execution 執行表
- act_ru_identitylink 參與者信息
- act_ru_task 任務
5,Activiti實現指定用戶查詢任務列表 [查詢待辦]
流程啓動後,各個任務的負責人就可以查詢自己當前需要處理的任務,查詢出來的任務都是該用戶的待辦任務。
寫一個測試類,查詢指定用戶下面的待辦任務列表。
//查詢當前用戶的任務列表
@Test
public void query(){
//1,創建processEngine對象
ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();
//2,得到TaskService對象
TaskService taskService = processEngine.getTaskService();
//3,根據流程定義的key,負責人assignee來實現當前用戶的任務列表查詢
List<Task> list = taskService.createTaskQuery()
.processDefinitionKey("holiday")
.taskAssignee("zhangsan")
.list();
//4,任務列表的展示
for(Task task:list){
System.out.println("流程實例id:"+task.getProcessInstanceId());
System.out.println("任務id:"+task.getId());
System.out.println("任務負責人:"+task.getAssignee());
System.out.println("任務名稱:"+task.getName());
}
}
打印:
流程實例id:2501
任務id:2505
任務負責人:zhangsan
任務名稱:填寫請假單
6,Activiti實現當前用戶任務處理
寫一個測試類,處理當前用戶的待辦任務。
//處理任務
@Test
public void chuli(){
//1,創建processEngine對象
ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();
//2,得到TaskService對象
TaskService taskService = processEngine.getTaskService();
//3,處理任務,結合當前用戶任務列表的查詢操作,得到任務ID:2505
taskService.complete("2505");
}
運行之後,該任務就完成了,然後就跳到下一個審批的節點了。
看數據庫act_hi_taskinst
表的變化,填寫請假單有了結束時間,然後新增了一條部門經理審批,並且結束時間爲NULL,這就說明跳到下一個節點了。
處理任務操作背後影響的表:
- act_hi_actinst 歷史數據
- act_hi_identitylink 參與者信息
- act_hi_taskinst
- act_ru_execution
- act_ru_identitylink
- act_ru_task 該表從始至終只存一條數據,就是當前執行的節點信息(走到哪一步了),如果流程執行完了則會刪除這一條數據。
7,Activiti流程部署的zip包方式的補充
將holiday.bpmn
和holiday.png
壓縮成holiday.zip
包進行部署,效果和上面的(標題3)一樣。
//流程定義的部署
@Test
public void testDeploy(){
//1,創建processEngine對象
ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();
//2,得到repositoryService實例
RepositoryService repositoryService = processEngine.getRepositoryService();
//轉化出ZipInputStream流對象
InputStream inputStream = this.getClass().getClassLoader().getResourceAsStream("holiday.zip");
//將inputStream轉化爲ZipInputStream流
ZipInputStream zipInputStream=new ZipInputStream(inputStream);
//3,進行部署
Deployment deployment = repositoryService.createDeployment()
.addZipInputStream(zipInputStream)
.name("請假申請流程")
.deploy();
//4,輸出部署的一些信息
System.out.println(deployment.getId());
System.out.println(deployment.getName());
}
這種方法也可以的。
8,Activiti流程定義信息查詢
說明:什麼是流程定義?流程定義就是啓動了的流程圖,比方說上面啓動了一個請假申請流程,那麼該流程定義就是指這個請假申請流程。
寫一個測試類,查詢流程定義信息。
//流程定義查詢
@Test
public void defined(){
//1,創建processEngine對象
ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();
//2,得到RepositoryService對象
RepositoryService repositoryService = processEngine.getRepositoryService();
//3,得到ProcessDefinitionQuery對象,可以認爲它就是一個查詢器
ProcessDefinitionQuery processDefinitionQuery = repositoryService.createProcessDefinitionQuery();
//4,設置條件,並查詢出當前的所有流程定義,查詢條件:流程定義的key=holiday
List<ProcessDefinition> holiday = processDefinitionQuery.processDefinitionKey("holiday")
.orderByProcessDefinitionVersion() //設置排序方式,根據流程定義的版本號進行排序
.desc() //降序排列
.list();
//5,輸出流程定義信息
for(ProcessDefinition processDefinition:holiday){
System.out.println("流程定義ID:"+processDefinition.getId());
System.out.println("流程定義名稱:"+processDefinition.getName());
System.out.println("流程定義的Key:"+processDefinition.getKey());
System.out.println("流程定義的版本號:"+processDefinition.getVersion());
System.out.println("流程部署的ID:"+processDefinition.getDeploymentId());
}
}
打印出了流程定義的所有信息。
流程定義ID:holiday:1:4
流程定義名稱:請假流程
流程定義的Key:holiday
流程定義的版本號:1
流程部署的ID:1
9,Activiti流程定義信息刪除
刪除已經部署成功的流程定義。
@Test
public void del(){
//1,創建processEngine對象
ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();
//2,得到RepositoryService對象
RepositoryService repositoryService = processEngine.getRepositoryService();
//3,執行刪除流程定義,參數代表流程部署的id
repositoryService.deleteDeployment("1");
}
刪除流程定義影響的表和部署流程定義影響的表是一樣的,刪除是減去,部署是增加,都是同樣的三張表。
注意事項:
1,當我們正在執行的這一套流程沒有完全審批結束的時候,如果此時要刪除流程定義信息就會失敗。
2,如果要強制刪除,可以使用repositoryService.deleteDeployment("1",true)
;參數true
代表級聯刪除,此時就會先刪除沒有完成的流程結點,最後就可以刪除流程定義信息,false
值代表不級聯刪除,不寫默認爲false
七,Activiti實現資源文件保存的需求與方案分析
1,Activiti流程定義資源
通過流程定義對象獲取流程定義資源,獲取bpmn
和png
。
真實應用場景:用戶想查看這個請假申請流程具體有哪些步驟要走?
技術方案:
- 第一種方式使用
Activiti
的api
來實現; - 其實就是原理層面,可以使用
jdbc
對blob
類型,clob
類型數據的讀取和保存。 IO
流轉換,使用commons-io.jar
包可以輕鬆解決IO
操作。
需求:
- 數據庫裏有一個現成的流程定義,現在要把數據庫中存放該流程定義的資源文件讀取到硬盤的指定位置。
- 從
act_ge_bytearray
表中讀取兩個資源文件,並將兩個資源文件保存到路徑:D:\
2,Activiti實現資源文件保存的實現步驟
寫一個測試類實現資源文件保存到本地磁盤。
裏面輸入流和輸出流轉換需要用到了commons-io.jar
包,這裏引入一個在idea
中快速搜索並選擇版本號並導入依賴的方法。
Alt
+Insert
鍵,選擇Dependency
,搜索依賴的名字,並選擇版本號,確定導入即可。
//保存資源文件
@Test
public void save() throws IOException {
//1,創建processEngine對象
ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();
//2,得到RepositoryService對象
RepositoryService repositoryService = processEngine.getRepositoryService();
//3,得到ProcessDefinitionQuery對象,可以認爲它就是一個查詢器
ProcessDefinitionQuery processDefinitionQuery = repositoryService.createProcessDefinitionQuery();
//4,設置查詢條件
processDefinitionQuery.processDefinitionKey("holiday");//參數是流程定義的key
//5,執行查詢操作,查詢出想要的流程定義
ProcessDefinition processDefinition = processDefinitionQuery.singleResult();
//6,通過流程定義信息,得到部署ID
String deploymentId = processDefinition.getDeploymentId();
//7,通過repositoryService的方法,實現讀取圖片信息及bpmn文件信息(輸入流)
//getResourceAsStream()方法的參數說明:第一個參數是部署id,第二個參數代表資源名稱
//processDefinition.getDiagramResourceName() 獲取png圖片資源的名稱
//processDefinition.getResourceName() 獲取bpmn文件的名稱
InputStream pngIs = repositoryService.getResourceAsStream(deploymentId, processDefinition.getDiagramResourceName());
InputStream bpmnIs = repositoryService.getResourceAsStream(deploymentId, processDefinition.getResourceName());
//8,構建出OutputStream流
OutputStream pngOs=new FileOutputStream("D:\\工作\\"+processDefinition.getDiagramResourceName());
OutputStream bpmnOs=new FileOutputStream("D:\\工作\\"+processDefinition.getResourceName());
//9,輸入流、輸出流的轉換(commons-io.jar中的方法)
IOUtils.copy(pngIs,pngOs);
IOUtils.copy(bpmnIs,bpmnOs);
//10,關閉流
pngOs.close();
bpmnOs.close();
pngIs.close();
bpmnIs.close();
}
查看本地D:/工作
路徑下,有了holiday.png
和holiday.bpmn
文件,已經下載到了本地。
3,Activiti歷史信息的查詢
即使流程定義已經刪除了,但是流程執行的歷史信息依然保存在activiti
的act_hi_*
相關的表中。所以我們還是可以查詢流程執行的歷史信息,可以通過HistoryService
來查看相關的歷史記錄。
/查詢歷史數據
@Test
public void select(){
//1,創建processEngine對象
ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();
//2,得到HistoryService對象
HistoryService historyService = processEngine.getHistoryService();
//3,得到查詢器
HistoricActivityInstanceQuery instanceQuery = historyService.createHistoricActivityInstanceQuery();
//4,執行查詢
instanceQuery.processInstanceId("2501"); //設置流程實例的id
List<HistoricActivityInstance> list = instanceQuery.orderByHistoricActivityInstanceStartTime().asc().list();//排序
//5,遍歷查詢結果
for(HistoricActivityInstance instance:list){
System.out.println(instance.getActivityId());
System.out.println(instance.getActivityName());
System.out.println(instance.getProcessDefinitionId());
System.out.println(instance.getProcessInstanceId());
System.out.println("===========================");
}
}
打印
八,Activiti與業務系統整合開發的原理分析
Activiti:數據庫中的25張表只是負責Activiti
的工作正常流程,和業務數據沒有關係。
業務系統:存放業務數據。
比如請假流程,提交的表單信息是存到業務數據庫中,但是要把Activiti
和業務系統關聯起來,意思就是,提交一個請假審批,Activiti
怎麼知道這個審批流程是誰提交的?因此關聯的方法是把業務主鍵(請假表的id
字段)保存到Activiti
裏面。
存放到哪裏?
act_ru_execution
表中的bussiness_key
字段就是業務主鍵(業務標識)。
1,Activiti實現與業務系統businessKey整合操作
在上面啓動流程實例時,參數是一個,也就是流程id。
//3,創建流程實例,流程定義的key需要知道流程唯一id:holiday
ProcessInstance processInstance = runtimeService.startProcessInstanceByKey("holiday");
但是要把業務表(請假表)的業務標識(請假表的id
)和Activiti
整合起來,需要傳入第二個參數businessKey
,第二個參數就是請假表的id
。
@Test
public void bang(){
//1,創建processEngine對象
ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();
//2,得到RuntimeService實例
RuntimeService runtimeService = processEngine.getRuntimeService();
//3,創建流程實例,流程定義的key需要知道流程唯一id:holiday
ProcessInstance processInstance = runtimeService.startProcessInstanceByKey("holiday","1001");
System.out.println(processInstance.getBusinessKey());
}
打印:1001
觀察act_ru_execution
表,已經和業務id
綁定了。
2,Activiti全部流程的掛起與激活
需求:公司制度發生了變化,原本沒有批完的流程怎麼辦?
某些情況可能由於流程變更需要當前運行的流程暫停而不是直接刪除,流程暫停後將不會繼續執行。操作流程定義爲掛起狀態,該流程定義下邊所有的流程實例全部暫停,並不允許啓動新的流程實例。
//流程定義全部掛起
@Test
public void guaqi(){
//1,創建processEngine對象
ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();
//2,得到RepositoryService對象
RepositoryService repositoryService = processEngine.getRepositoryService();
//3,查詢流程定義的對象
ProcessDefinition processDefinition = repositoryService.createProcessDefinitionQuery()
//可以根據流程定義的id查詢指定流程定義,不過id要到Activiti數據庫表中去看, act_re_procdef表
// .processDefinitionId()
.processDefinitionKey("holiday") //也可以根據流程定義的key查詢
.singleResult();
//4,得到當前流程定義的實例是否都爲暫停狀態
boolean suspended = processDefinition.isSuspended();
//5,如果都爲暫停狀態,則全部激活
if(suspended){
repositoryService.activateProcessDefinitionById(processDefinition.getId(),true,null);
System.out.println("流程定義:"+processDefinition.getId()+"被激活");
}else{
repositoryService.suspendProcessDefinitionById(processDefinition.getId(),true,null);
System.out.println("流程定義:"+processDefinition.getId()+"被掛起");
}
}
運行測試類,打印
流程定義:holiday:1:4被掛起
再次運行測試類,打印
流程定義:holiday:1:4被激活
3,Activiti單個流程實例的掛起與激活
processInstanceId(“2501”),是通過流程實例id查詢到的流程實例對象
//單個流程實例的掛起
@Test
public void single(){
//1,創建processEngine對象
ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();
//2,得到RuntimeService對象
RuntimeService runtimeService = processEngine.getRuntimeService();
//3,查詢流程實例對象
ProcessInstance holiday = runtimeService.createProcessInstanceQuery().processInstanceId("2501").singleResult();
//4,查看當前流程定義的實例是否爲暫停狀態
boolean suspended = holiday.isSuspended();
//判斷
if(suspended){
runtimeService.activateProcessInstanceById(holiday.getId());
System.out.println("流程實例:"+holiday.getId()+"被激活");
}else{
runtimeService.suspendProcessInstanceById(holiday.getId());
System.out.println("流程實例:"+holiday.getId()+"被掛起");
}
}
打印
流程實例:2501被掛起
4,Activiti中個人任務分配的固定方式和UEL方式
上面所述,每個節點設置的Assignee
對應的值爲zhangsan
,代表某個人,那就是固定方式分配,但是被定死了,只有張三才能完成該節點,但是肯定是不行的,請假是所有員工都可以申請的。
引出UEL
方式
UEL
方式,也稱爲統一表達式。
寫法:${變量名}
什麼時候設置表達式?
在啓動流程實例的時候設置流程變量,如下
//啓動流程實例,動態設置assignee
@Test
public void dongtai(){
//1,創建processEngine對象
ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();
//2,得到RuntimeService對象
RuntimeService runtimeService = processEngine.getRuntimeService();
//3,設置assignee的取值,用戶可以在界面上設置流程的執行人
Map<String,Object> map=new HashMap<>();
map.put("assignee0","zhangsan");
map.put("assignee1","lisi");
map.put("assignee2","wangwu");
//4,啓動流程實例,同時還要設置流程定義的assignee的值
ProcessInstance holiday = runtimeService.startProcessInstanceById("holiday", map);
System.out.println(holiday.getName());
}
前提:在界面上要分別設置assignee
的值爲assignee0
,assignee1
,assignee2
。
結合項目使用的話,獲取到提交請假單的名字或者唯一標識,然後動態賦值,然後存到activiti
數據庫中名字就是該設置的名字。
5,監聽器方式實現activiti流程assignee設置
在界面中添加監聽器。
定義任務監聽類,該類必須實現org.activiti.delegate.TaskListener
接口。
public class MyTaskListener implements TaskListener(){
@Override
public void notify(Delegate delegate){
//這裏指定任務負責人
delegate.setAssignee("張三");
}
}
九,Activiti的流程變量概述及作用
1,白話引入Activiti流程變量
比如說現在有一個貸款流程,發起流程的用戶如果貸款數目小於100000,那麼業務經理審批完之後就可以到會計節點,然後放貸款,但是如果貸款數目大於100000了,那麼就要到總經理節點審批,總經理審批完之後才能到會計節點。
那麼問題來了,Activiti怎麼知道大於100000就要到總經理審批節點呢?那麼就需要用到流程變量了。
2,Activiti流程變量的作用域
global變量:一個任務或一個流程實例。可以理解爲全局的,所有節點都可以使用該變量。
local變量:僅僅針對一個任務和一個執行實例範圍,範圍沒有流程實例大。可以理解爲局部的,只有一個節點可以使用。
global
變量名不可以重複,如果名字重複,則會被覆蓋。
3,Activiti流程變量的使用方式
可以在連線上設置UEL表達式,決定流程走向。
比如:{price<100000},price就是變量名稱。
點擊流程圖的連線,設置流程變量即可。
4,Activiti流程變量案例
需求:員工創建請假單,由部門經理審覈,部門經理審覈通過之後請假3天以下由人事經理直接審覈,3天以上先由總經理審覈,總經理審覈通過之後再由人事經理存檔。
定義BPMN文件:
設置請假單申請assignee:zhangsan,部門審批assignee:lisi,總經理審批assignee:wangwu,人事經理存檔assignee:zhaoliu。
畫好圖之後,點擊連線(人事經理前面的連線設置爲${holiday.num<3}
,總經理前面的連線設置爲${holiday.num>3}
),這是UEL
方式賦值,可以對象點出來的屬性這種,對象就是下面的實體類。
然後重複上面的動作:改bpmn
文件後綴爲xml
文件,然後生成png
文件,然後再把xml
恢復成bpmn
。
其實不生成png
也沒啥問題,工作流一點兒影響都沒有。
//請假實體類
@Data
public class Holiday implements Serializable{
private Integer id;
private String holidayName;//申請人名字
private Date beginDate;//開始時間
private Date endDate;//結束時間
private Float num;//請假天數
private String reason;//請假事由
private String type;//請假類型
}
注意:如果將實體類存儲到流程變量中,必須實現序列化接口seriallizable
,否則在存儲這個實體類的時候就會報異常,爲了防止由於新增字段無法反序列化,需要生成serialVersionUID
。
(2)部署流程定義
//流程變量的測試
public class HolidayTest {
//新的請假流程定義的部署
@Test
public void start(){
//得到ProcessEngine
ProcessEngine defaultProcessEngine = ProcessEngines.getDefaultProcessEngine();
//得到RepositoryService
RepositoryService repositoryService = defaultProcessEngine.getRepositoryService();
//部署
Deployment deploy = repositoryService.createDeployment()
.addClasspathResource("holiday4.bpmn")
.addClasspathResource("holiday4.png")
.name("請假流程-測試流程變量")
.deploy();
System.out.println(deploy.getId());
System.out.println(deploy.getName());
}
}
運行,這就部署好了該流程定義。
(3)啓動流程實例,同時設置流程變量
//啓動流程實例,同時設置流程變量
@Test
public void begin(){
//得到ProcessEngine
ProcessEngine defaultProcessEngine = ProcessEngines.getDefaultProcessEngine();
//得到RuntimeService
RuntimeService runtimeService = defaultProcessEngine.getRuntimeService();
//流程定義的key:myProcess_1
String key="myProcess_1";
Map<String,Object> map=new HashMap<>();
//設置請假天數
Holiday holiday=new Holiday();
holiday.setNum(1F);
map.put("holiday",holiday);
//啓動流程實例,並設置流程變量的值
ProcessInstance processInstance = runtimeService.startProcessInstanceByKey(key, map);
System.out.println(processInstance.getName());
System.out.println(processInstance.getProcessDefinitionId());
}
運行,就啓動了一個流程實例。
(4)測試流程分支
依次調用此方法完成任務,zhangsan,lisi,wangwu/zhaoliu---------判斷流程變量的請假天數
@Test
public void redify(){
//得到ProcessEngine
ProcessEngine defaultProcessEngine = ProcessEngines.getDefaultProcessEngine();
//得到taskService
TaskService taskService = defaultProcessEngine.getTaskService();
//查詢當前用戶是否存在任務
String key="myProcess_1";
Task task = taskService.createTaskQuery().processDefinitionKey(key)
.taskAssignee("zhangsan").singleResult();
//如果有任務,結束
if(task!=null){
taskService.complete(task.getId());
System.out.println("任務執行完畢");
}
}
張三執行完之後,看act_hi_actinst
表,張三填寫申請已經結束,來到了李四部門經理審批。
然後再執行李四()把zhangsan改成lisi,運行上面的測試類。
運行完之後,李四的任務也執行完畢了,也就是部門審批完了,看act_hi_actinst
表的變化。
發現來到了人事經理存檔的節點,是因爲上面設置的請假天數是1天,所以直接來到了人事,如果請假天數大於3天,那麼會先到總經理審批節點,然後纔到人事節點。
同理,把lisi改成zhaoliu,也就是執行人事經理存檔,運行,看act_hi_actinst
表的變化。
流程執行完畢。
5,Activiti完成任務時設置流程變量的值
上面的流程變量的賦值是創建流程實例就有值了,而完成任務設置值是完成了該節點之後然後流程變量纔有的值。
6,通過流程實例ID設置流程變量的值
流程實例創建好之後可以得到該實例id,然後在啓動流程實例的時候通過流程實例id設置流程變量的值。
//啓動流程實例,同時設置流程變量
@Test
public void begin(){
//得到ProcessEngine
ProcessEngine defaultProcessEngine = ProcessEngines.getDefaultProcessEngine();
//得到RuntimeService
RuntimeService runtimeService = defaultProcessEngine.getRuntimeService();
//流程定義的key:myProcess_1
String key="myProcess_1";
Map<String,Object> map=new HashMap<>();
//設置請假天數
Holiday holiday=new Holiday();
holiday.setNum(1F);
map.put("holiday",holiday);
//啓動流程實例,並設置流程變量的值
//第一個參數:流程實例的id
//第二個參數:流程變量名
//第三個參數:流程變量所對應的值
runtimeService.setVariable("2501","holiday",holiday);
// ProcessInstance processInstance = runtimeService.startProcessInstanceByKey(key, map);
// System.out.println(processInstance.getName());
// System.out.println(processInstance.getProcessDefinitionId());
}
7,通過當前任務ID來設置流程變量的值
首先需要得到當前任務的id。
String taskId="1404";
TaskService taskService=processEngine.getTaskService();
//使用taskService來設置
taskService.setVariable(taskId,"holiday",holiday);
十,Activiti組任務
需求:前面講述的,每個節點只能有一個任務負責人,現在要一個節點可以有多個任務負責人。
設置任務候選人:
流程圖任務節點的配置中配置candiddate-users
(候選人),多個候選人之間用逗號隔開。
1,Activiti組任務辦理流程
-
第一步:查詢組任務,指定候選人,查詢該候選人當前的待辦任務。候選人不能辦理業務。
-
第二步:拾取任務,該組任務的所有候選人都可以拾取,將候選人的組任務變成個人任務,原來候選人就變成了該任務的負責人。
-
如果拾取後不想辦理該業務怎麼辦?
-
需要將已經拾取的個人任務歸還到組裏邊,將個人任務變成了組任務。
-
-
第三步:查詢個人任務,查詢方式同個人任務部分,根據assignee查詢用戶負責的個人任務。
-
第四步:辦理個人任務。
2,Activiti查詢候選人的組任務
//查詢組任務
@Test
public void find(){
ProcessEngine defaultProcessEngine = ProcessEngines.getDefaultProcessEngine();
TaskService taskService = defaultProcessEngine.getTaskService();
String key="myProcess_1";//流程定義的key
String candidate_users="zhangsan";//指定某一個候選人
//查詢
List<Task> list = taskService.createTaskQuery()
.processDefinitionKey(key)
.taskCandidateUser(candidate_users)//設置候選用戶
.list();
//輸出
for(Task task:list){
System.out.println(task.getProcessInstanceId());
System.out.println(task.getId());
System.out.println(task.getName());
System.out.println(task.getAssignee());
}
}
其中
System.out.println(task.getAssignee());
打印爲null,是因爲查到的任務沒有具體的負責人,需要拾取任務。說明張三隻是候選人,還不是任務負責人。
3,Activiti拾取組任務
候選人拾取組任務後該任務變成自己的個人任務。
//查詢組任務
@Test
public void find(){
ProcessEngine defaultProcessEngine = ProcessEngines.getDefaultProcessEngine();
TaskService taskService = defaultProcessEngine.getTaskService();
String key="myProcess_1";//流程定義的key
String candidate_users="zhangsan";//指定某一個候選人
//查詢
Task task = taskService.createTaskQuery()
.processDefinitionKey(key)
.taskCandidateUser(candidate_users)
.singleResult();
if(task!=null){
//第一個參數:任務id,第二個參數:候選人
taskService.claim(task.getId(),candidate_users);
System.out.println("任務拾取完畢");
}
}
候選人張三拾取組任務以後,該任務就已經有任務負責人了(assignee),就是老張了。
4,Activiti組任務-用戶任務查詢&完成任務
上面張三已經拾取了組任務,變成了個人任務,現在張三查詢自己的個人任務並完成任務。
@Test
public void find(){
ProcessEngine defaultProcessEngine = ProcessEngines.getDefaultProcessEngine();
TaskService taskService = defaultProcessEngine.getTaskService();
String key="myProcess_1";//流程定義的key
String candidate_users="zhangsan";
//查詢
Task task = taskService.createTaskQuery()
.processDefinitionKey(key)
.taskAssignee(candidate_users)//這裏就可以指定具體的任務負責人了
.singleResult();
if(task!=null){
taskService.complete(task.getId());
System.out.println("任務執行完畢");
}
}
5,Activiti組任務-當前用戶歸還組任務
如果個人不想辦理該組任務,可以歸還組任務,歸還後該用戶不再是該任務的負責人。
//查詢組任務
@Test
public void find(){
ProcessEngine defaultProcessEngine = ProcessEngines.getDefaultProcessEngine();
TaskService taskService = defaultProcessEngine.getTaskService();
String key="myProcess_1";//流程定義的key
String candidate_users="zhangsan";
//查詢
Task task = taskService.createTaskQuery()
.processDefinitionKey(key)
.taskAssignee(candidate_users)
.singleResult();
if(task!=null){
//如果設置爲null,歸還組任務,該任務沒有負責人
taskService.setAssignee(task.getId(),null);
}
}
說明:建議歸還任務前校驗該用戶是否是該任務的負責人。
也可以通過setAssignee
方法將任務委託給其他用戶負責,注意被委託的用戶可以不是候選人。
6,Activiti組任務-任務交接的實現
任務交接:任務負責人將任務交給其他候選人辦理該業務。
//查詢組任務
@Test
public void find(){
ProcessEngine defaultProcessEngine = ProcessEngines.getDefaultProcessEngine();
TaskService taskService = defaultProcessEngine.getTaskService();
String key="myProcess_1";//流程定義的key
String candidate_users="zhangsan";
//查詢
Task task = taskService.createTaskQuery()
.processDefinitionKey(key)
.taskAssignee(candidate_users)
.singleResult();
if(task!=null){
taskService.setAssignee(task.getId(),"lisi");
System.out.println("交接任務成功");
}
}
十一,Activiti網關
1,排他網關
排他網關用來在流程中實現決策,當流程執行到這個網關,所有分支都會判斷條件是否爲true,如果爲true則執行該分支。
注意:排他網關智慧選擇一個爲true
的分支執行,即使有兩個分支條件都爲true
,排他網關也會只選擇一條分支去執行。
這是關鍵,因爲如果兩個分支條件都滿足,那同時走兩個分支是不現實的,要不然流程就全亂套了。
使用方法:在流程圖界面拖一個排他網關放在中間就行了。
2,並行網關
作用:並行網關把一個流程分成多條分支,併發執行,執行完之後再把這些分支聚合到一起成一個流程,然後繼續往下走。
如果有一個分支沒有執行完,那麼流程不會繼續走,並行網關會一直等所有分支都執行完畢,並聚合在一起之後才接着走流程。
並行網關並不會解析條件,即使順序流中定義了條件,也會被忽略。
3,包含網關
這裏忽略。