1.什么是Activiti
在解釋activiti之前我們看一下什么是工作流。
工作流(Workflow),就是“業務過程的部分或整體在計算機應用環境下的自動化”,它主要解決的是“使在多個參與者之間按照某種預定義的規則傳遞文檔、信息或任務的過程自動進行,從而實現某個預期的業務目標,或者促使此目標的實現”。
我的理解是,工作流將一套大的業務邏輯分解成業務邏輯段, 並統一控制這些業務邏輯段的執行條件,執行順序以及相互通信。 實現業務邏輯的分解和解耦。
Activiti是一個開源的工作流引擎,它實現了BPMN 2.0規范,可以發布設計好的流程定義,並通過api進行流程調度。
BPMN即業務流程建模與標注(Business Process Model and Notation,BPMN) ,描述流程的基本符號,包括這些圖元如何組合成一個業務流程圖(Business Process Diagram)。
BPMN的流程圖長這樣子
activiti5.13使用了23張表支持整個工作流框架,底層使用mybatis操作數據庫。這些數據庫表為
1)ACT_RE_*: 'RE'表示repository。 這個前綴的表包含了流程定義相關的靜態資源(圖片,規則等)。
2)ACT_RU_*: 'RU'表示runtime。 運行時表,包含流程實例,任務,變量,異步任務等運行中的數據。流程結束時這些記錄會被刪除。
3)ACT_ID_*: 'ID'表示identity。 這些表包含用戶和組的信息。
4)ACT_HI_*: 'HI'表示history。 這些表包含歷史數據,比如歷史流程實例,變量,任務等。
5)ACT_GE_*: 通用數據,bytearray表保存文件等字節流對象。
工作流進行的基本過程如下:
定義流程(框架外) -> 部署流程定義 -> 啟動流程實例, 框架移動到任務1 -> 拾取組任務 -> 辦理個人任務, 框架移動到任務2 -> 拾取組任務 -> 辦理個人任務...
組任務是多個用戶都可以完成的任務。沒有組任務直接辦理個人任務; 有組任務需先通過拾取將組任務變成個人任務, 然后再辦理。
個人任務/組任務在表中的區別
個人任務: 表act_ru_task的ASSIGNEE段即指定的辦理人
組任務: 表act_ru_task的ASSIGNEE段為null, 相關信息在表act_ru_identitylink中, 組任務1見userid段; 組任務2見groupid段, 當然還需查詢act_id_xxx表才能精確到人.
2.Activiti的使用
2.1 創建processEngine
processEngine控制着工作流整個流程
public class processEngine { @Test public void createProcessEngine1() { String resource = "activiti-context.xml"; // 配置文件 String beanName = "processEngineConfiguration"; // 配置文件中bean name // 從配置文件創建配置對象 ProcessEngineConfiguration config = ProcessEngineConfiguration.createProcessEngineConfigurationFromResource(resource, beanName); // 根據配置創建引擎對象 ProcessEngine processEngine = config.buildProcessEngine(); } /** * 一條語句創建processEngine, 要求: * 1、配置文件必須在classpath根目錄下 * 2、配置文件名必須為activiti-context.xml或activiti.cfg.xml * 3、工廠對象的id必須為processEngine */ @Test public void createProcessEngine2() { ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine(); } }
<beans xmlns="http://www.springframework.org/schema/beans" xmlns:context="http://www.springframework.org/schema/context" xmlns:tx="http://www.springframework.org/schema/tx" 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 http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-2.5.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-3.0.xsd"> <!-- 配置 --> <bean id="processEngineConfiguration" class="org.activiti.engine.impl.cfg.StandaloneProcessEngineConfiguration"> <property name="jdbcDriver" value="com.mysql.jdbc.Driver"/> <property name="jdbcUrl" value="jdbc:mysql:///test_activiti"/> <property name="jdbcUsername" value="root"/> <property name="jdbcPassword" value="root"/> <!-- 創建processEngine時, activiti自動創建23張表 --> <property name="databaseSchemaUpdate" value="true"/> </bean> <!-- 使用配置創建引擎對象 --> <bean id="processEngine" class="org.activiti.spring.ProcessEngineFactoryBean"> <property name="processEngineConfiguration" ref="processEngineConfiguration"/> </bean> </beans>
當然, 可以與spring進一步整合, 使用spring方式獲取processEngine. applicationContext.xml如下
<beans xmlns="http://www.springframework.org/schema/beans" xmlns:context="http://www.springframework.org/schema/context" xmlns:tx="http://www.springframework.org/schema/tx" 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 http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-2.5.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-3.0.xsd"> <bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource"> <property name="driverClassName" value="com.mysql.jdbc.Driver" /> <property name="url" value="jdbc:mysql:///activiti_day2" /> <property name="username" value="root" /> <property name="password" value="root" /> </bean> <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"> <property name="dataSource" ref="dataSource"/> </bean> <!-- 流程引擎配置對象 --> <bean id="processEngineConfiguration" class="org.activiti.spring.SpringProcessEngineConfiguration"> <!-- 注入數據源 --> <property name="dataSource" ref="dataSource"/> <!-- 注入事務管理器對象 --> <property name="transactionManager" ref="transactionManager"/> <property name="databaseSchemaUpdate" value="true" /> </bean> <bean id="processEngine" class="org.activiti.spring.ProcessEngineFactoryBean"> <property name="processEngineConfiguration" ref="processEngineConfiguration" /> </bean> </beans>
2.2 部署流程定義
流程是由用戶通過bpmn等文件(底層xml)定義的, 即上面列舉的的bpmn流程圖
定義好的流程需要部署給activiti才能被其使用
/** * 部署流程定義 * 一套定義文件只有一個流程定義Key, 但可以被部署多次形成多個版本(部署表里多個id和流程定義表里多個id) * 涉及的表:act_re_deployment(部署表)、act_re_procdef(流程定義表)、act_ge_bytearray(二進制表) */ @Test public void test() throws FileNotFoundException { DeploymentBuilder deploymentBuilder = processEngine.getRepositoryService().createDeployment(); // 逐個文件部署 // deploymentBuilder.addClasspathResource("qjlc.bpmn"); // deploymentBuilder.addClasspathResource("qjlc.png"); // 壓縮文件打包部署, 推薦 ZipInputStream zipInputStream = new ZipInputStream(new FileInputStream(new File("d:\\processDef.zip"))); deploymentBuilder.addZipInputStream(zipInputStream ); Deployment deployment = deploymentBuilder.deploy(); }
2.3 啟動流程實例
/** * 啟動一個流程實例 * 涉及的表: * act_ru_execution(流程實例表), 管理流程進度 * act_ru_task(任務表), 進行到哪一個流程的哪一個任務, 該由誰完成 */ @Test public void test() throws Exception{ String processDefinitionKey = "qjlc"; //方式一:根據流程定義id啟動流程實例 //String processDefinitionId = "qjlc:6:904"; //ProcessInstance processInstance = processEngine.getRuntimeService().startProcessInstanceById(processDefinitionId); //方式二:根據流程定義Key啟動流程實例 推薦!流程定義有多個版本時會選擇最新版本 ProcessInstance processInstance = processEngine.getRuntimeService().startProcessInstanceByKey(processDefinitionKey); }
2.4 辦理任務
/** * 辦理任務, 辦理后框架自動移動到下一任務 * 涉及的表: act_ru_execution(流程實例表)、act_ru_task(任務表) */ @Test public void test() throws Exception{ String taskId = "1304"; processEngine.getTaskService().complete(taskId); }
2.5 其他操作
/** * 查詢流程定義 * 涉及的表:act_re_procdef */ @Test public void test(){ ProcessDefinitionQuery query = processEngine.getRepositoryService().createProcessDefinitionQuery(); // 查詢條件過濾 query.processDefinitionKey("qjlc"); query.orderByProcessDefinitionVersion().asc(); List<ProcessDefinition> list = query.listPage(0, 10); for (ProcessDefinition processDefinition : list) { System.out.println(processDefinition.getId()); } }
activiti中查詢的套路: processEngine.getXXXService().createXXXQuery().list()/singleResult()
processEngine.getRepositoryService().createDeploymentQuery().list(); // 查詢部署
processEngine.getRuntimeService().createProcessInstanceQuery().list(); // 查詢流程實例
processEngine.getTaskService().createTaskQuery().list(); // 查詢個人任務
processEngine.getIdentityService().createUserQuery().list(); // 查詢用戶
processEngine.getHistoryService().createHistoricActivityInstanceQuery().list(); //查詢歷史
過濾條件
查詢個人任務 query.taskAssignee()
查詢組任務 query.taskCandidate()
幾個javabean(和表對應):
Deployment------act_re_deployment
ProcessDefinition-----act_re_procdef
ProcessInstance------act_ru_execution
Task-----act_ru_task
幾個Query對象:
DeploymentQuery------act_re_deployment
ProcessDefinitionQuery-----act_re_procdef
ProcessInstanceQuery------act_ru_execution
TaskQuery-----act_ru_task
幾個Service:
RepositoryService----操作部署表、流程定義表等靜態資源信息表
RuntimeService----操作流程實例表、任務表等動態信息表
TaskService-----操作任務表
HistoryService----操作歷史表
IdentityService----操作用戶表、組表、關系表
// 刪除流程定義 @Test public void test1(){ String deploymentId = "101"; //部署id boolean cascade = false; // 級聯刪除, 設置為true的話, 有正在跑的流程實例及任務也會被刪除 processEngine.getRepositoryService().deleteDeployment(deploymentId, cascade); } // 刪除流程實例 @Test public void test2() throws Exception{ String processInstanceId = "1201"; String deleteReason = "不請假了"; // 可以添加刪除原因 processEngine.getRuntimeService().deleteProcessInstance(processInstanceId, deleteReason); }
// 根據部署id, 獲取定義文件 @Test public void test3() throws Exception{ String deploymentId = "201"; //部署id // 先獲得定義文件的名字 List<String> names = processEngine.getRepositoryService().getDeploymentResourceNames(deploymentId); for (String name : names) { InputStream in = processEngine.getRepositoryService().getResourceAsStream(deploymentId, name); FileUtils.copyInputStreamToFile(in, new File("d:\\"+name)); in.close(); } } // 根據流程定義id, 獲取定義文件 @Test public void test4() throws Exception{ String processDefinitionId = "qjlc:6:904"; //流程定義id InputStream pngStream = processEngine.getRepositoryService().getProcessDiagram(processDefinitionId); FileUtils.copyInputStreamToFile(pngStream, new File("d:\\abc.png")); }
通過javabean能訪問到某些需要的字段, 例如
processInstance.getActivityId() -> 當前執行的任務名
processDefinition.getDiagramResourceName() -> 定義文件中圖片的名字
2.6 流程變量
多個任務間可以通過流程變量通信.
流程變量以key-value形式存放, 存於表 act_ru_variable. 在同一流程實例里, 不同方式設置變量, key相同時會覆蓋
// 啟動流程實例時 設置流程變量 @Test public void test1() { String processDefinitionKey = "bxlc"; Map<String, Object> variables = new HashMap<String, Object>(); variables.put("key", "value"); ProcessInstance pi = processEngine.getRuntimeService().startProcessInstanceByKey(processDefinitionKey, variables); } // 辦理任務時 設置流程變量, 更實用! @Test public void test2() { String taskId = "206"; Map<String, Object> variables = new HashMap<>(); variables.put("key", "value"); processEngine.getTaskService().complete(taskId, variables); } // 通過RuntimeService 設置流程變量 @Test public void test3() { String executionId = "201"; // 流程實例id Map<String, Object> variables = new HashMap<>(); variables.put("key", "value"); //processEngine.getRuntimeService().setVariable(executionId, variableName, value); processEngine.getRuntimeService().setVariables(executionId, variables); } // 通過TaskService 設置流程變量 @Test public void test4() { String taskId = "304"; String key = "key"; Object value = "value"; processEngine.getTaskService().setVariable(taskId , key, value); }
// 通過RuntimeService 獲取流程變量 @Test public void test5() { String executionId = "201"; Object value = processEngine.getTaskService().getVariable(executionId, "user"); System.out.println(value); } // 通過TaskService 獲取流程變量 @Test public void test6() { String taskId = "304"; Object value = processEngine.getTaskService().getVariable(taskId, "user"); System.out.println(value); }
流程變量還可以通過在定義流程用表達式${}. 框架在該段任務執行前從act_ru_variable表里動態獲取
另外, 啟動流程實例還有一個重載函數, 除了流程變量variables還能指定業務主鍵businessKey
processEngine.getRuntimeService().startProcessInstanceByKey(processDefinitionKey, businessKey, variables);
businessKey一般設置為業務表的主鍵值, 在使用activiti的時候, 通過查詢業務表主鍵, 能方便地查詢出業務的最新狀態
2.7 組任務
組任務1
// 查詢組任務 @Test public void test1() { TaskQuery query = processEngine.getTaskService().createTaskQuery(); // 使用候選人查詢組任務 String candidateUser = "財務二"; query.taskCandidateUser(candidateUser); List<Task> list = query.list(); for (Task task : list) { System.out.println(task.getId()); } } // 拾取組任務 @Test public void test2() { String taskId = "1102"; processEngine.getTaskService().claim(taskId , "財務二"); } // 辦理組任務, 無需指定辦理人 @Test public void test3() throws Exception{ String taskId = "1102"; processEngine.getTaskService().complete(taskId); }
組任務2
// activiti使用自己的用戶與組的權限表, 因此需要設置. 但需注意要與框架外用戶/組同步設置 @Test public void test2() { // 創建組 Group group = new GroupEntity(); group.setId("財務組"); processEngine.getIdentityService().saveGroup(group); // 創建用戶 User user = new UserEntity(); user.setId("2"); processEngine.getIdentityService().saveUser(user); // 維護用戶與組的關系 processEngine.getIdentityService().createMembership("2", "財務組"); } // 查詢組任務 @Test public void test2() { TaskQuery query = processEngine.getTaskService().createTaskQuery(); String candidateUser = "2"; // 使用候選人過濾 query.taskCandidateUser(candidateUser); // 使用組過濾 //query.taskCandidateGroup("財務組"); List<Task> list = query.list(); for (Task task : list) { System.out.println(task.getId()); } } // 拾取組任務 @Test public void test3() { String taskId = "1902"; processEngine.getTaskService().claim(taskId , "2"); } // 辦理組任務略
2.8 排他網關
設置分支條件
3. 一些使用經驗
1)
考慮到工作流中的一個任務, 對應一個業務段, 可以將taskDefinitionKey設置成strus action類的method, 使之具有一定的通用性
2)
兩種對流程定義的查詢, 后者能獲得更多定義的細節信息 processDefinitionEntity.findActivity(taskId) 工作流中某任務的信息
repositoryService.createProcessDefinitionQuery().processDefinitionId(processDefinitionId).singleResult()
(ProcessDefinitionEntity) repositoryService.getProcessDefinition(processDefinitionId)