一、為什么選擇Activiti

二、核心7大接口、28張表

(一)7大接口
- RepositoryService:提供一系列管理流程部署和流程定義的API。
- RuntimeService:在流程運行時對流程實例進行管理與控制。
- TaskService:對流程任務進行管理,例如任務提醒、任務完成和創建任務等。
- IdentityService:提供對流程角色數據進行管理的API,這些角色數據包括用戶組、用戶及它們之間的關系。
- ManagementService:提供對流程引擎進行管理和維護的服務。
- HistoryService:對流程的歷史數據進行操作,包括查詢、刪除這些歷史數據。
- FormService:表單服務。
(二)28張表

1、act_ge_ 通用數據表,ge是general的縮寫
2、act_hi_ 歷史數據表,hi是history的縮寫,對應HistoryService接口
3、act_id_ 身份數據表,id是identity的縮寫,對應IdentityService接口
4、act_re_ 流程存儲表,re是repository的縮寫,對應RepositoryService接口,存儲流程部署和流程定義等靜態數據
5、act_ru_ 運行時數據表,ru是runtime的縮寫,對應RuntimeService接口和TaskService接口,存儲流程實例和用戶任務等動態數據
三、創建BPMN業務流程模型
1.將Activiti提供的流程設計器應用activiti-app.war部署到Tomcat的webapps目錄。
2.創建新的MySql數據庫。修改activiti-app\WEB-INF\classes\META-INF\activiti-app目錄下的activiti-app.properties配置文件,默認使用H2內存數據庫,創建的模型重啟后會丟失,改成使用MySql數據庫。
3.瀏覽器訪問http://localhost:8080/activiti-app,登錄賬戶:admin:test
4.創建一個請假審批流程圖

-
給每個用戶任務指派候選組(有權限執行當前任務的角色)
指派候選組
指派候選組 -
排他網關設置條件分支表達式
設置條件分支
設置條件分支
5.導出流程圖為.bpmn20.xml文件
導出xml文件
四、Spring Boot與Activiti 6.0整合
1.在POM文件中添加依賴
<dependency>
<groupId>org.activiti</groupId>
<artifactId>activiti-spring-boot-starter-basic</artifactId>
<version>6.0.0</version>
</dependency>
2.將導出的.bpmn20.xml文件拷貝到項目文件夾/resources/processes下
3.application.properties文件添加配置項
spring.activiti.database-schema-update=true
databaseSchemaUpdate配置項可以設置流程引擎啟動和關閉時數據庫執行的策略。 databaseSchemaUpdate有以下四個值:
- false:false為默認值,設置為該值后,Activiti在啟動時,會對比數據庫表中保存的版本,如果沒有表或者版本不匹配時,將在啟動時拋出異常。
- true:設置為該值后,Activiti會對數據庫中所有的表進行更新,如果表不存在,則Activiti會自動創建。
- create-drop:Activiti啟動時,會執行數據庫表的創建操作,在Activiti關閉時,執行數據庫表的刪除操作。
- drop-create:Activiti啟動時,執行數據庫表的刪除操作在Activiti關閉時,會執行數據庫表的創建操作。
4.啟動應用,會在數據庫里創建28張表,表創建好之后停止應用。application.properties文件修改配置項
#每次應用啟動不檢查Activiti數據表是否存在及版本號是否匹配,提升應用啟動速度
spring.activiti.database-schema-update=false
5.application.properties文件增加配置項
#保存歷史數據級別設置為full最高級別,便於歷史數據的追溯
spring.activiti.history-level=full
對於歷史數據,保存到何種粒度,Activiti提供了history-level屬性對其進行配置。history-level屬性有點像log4j的日志輸出級別,該屬性有以下四個值:
- none:不保存任何的歷史數據,因此,在流程執行過程中,這是最高效的。
- activity:級別高於none,保存流程實例與流程行為,其他數據不保存。
- audit:除activity級別會保存的數據外,還會保存全部的流程任務及其屬性。audit為history的默認值。
- full:保存歷史數據的最高級別,除了會保存audit級別的數據外,還會保存其他全部流程相關的細節數據,包括一些流程參數等。
6.完成以上步驟,就可以在程序中使用自動注入的方式,使用Activiti的7大接口。
@Autowired
private RuntimeService runtimeService;
@Autowired
private TaskService taskService;
@Autowired
private IdentityService identityService;
@Autowired
private RepositoryService repositoryService;
@Autowired
private ProcessEngine processEngine;
@Autowired
private HistoryService historyService;
五、項目中的用戶、角色與Activiti中的用戶、用戶組整合
每個項目都有自己的用戶、角色表,Activiti也有自己的用戶、用戶組表。因此項目中的用戶、角色與Activiti中的用戶、用戶組要做整合。
//項目中每創建一個新用戶,對應的要創建一個Activiti用戶
//兩者的userId和userName一致
User admin=identityService.newUser("1");
admin.setLastName("admin");
identityService.saveUser(admin);
//項目中每創建一個角色,對應的要創建一個Activiti用戶組
Group adminGroup=identityService.newGroup("1");
adminGroup.setName("admin");
identityService.saveGroup(adminGroup);
//用戶與用戶組關系綁定
identityService.createMembership("1","1");
六、請假審批流程
1.請假申請和請假審批數據庫表設計
表設計原則:流程數據和業務數據相分離。Activiti相關表只負責流程的跳轉、走向等。流程中產生的業務表單數據、審批意見、附件等存儲在開發人員定義的業務表中。流程數據和業務數據之間通過processInstanceId(流程實例ID)和業務數據主鍵相互關聯。
為什么不使用Activiti相關表來存儲表單數據和附件?

Activiti為了應用的靈活性和通用性采用了縱表的方式存儲表單數據。假設一條請假申請表單數據有10個字段,那就需要10條記錄存儲原本橫表只需要一條記錄存儲的數據。采用縱表的方式會有如下問題:
- 會有大量的冗余數據並且數據量會急劇的增長
- 查詢語句復雜,查詢效率低
- 尤其不適合做后期的統計報表分析

Activiti存儲附件使用Blob數據格式,文件存儲在數據庫里,數據庫的數據文件會變得超大,不利於數據庫備份和遷移。
請假申請表結構

請假審批表結構

2.填寫請假申請表單,啟動流程實例

//啟動流程實例,字符串"vacation"是BPMN模型文件里process元素的id
ProcessInstance processInstance = runtimeService.startProcessInstanceByKey("vacation");
//流程實例啟動后,流程會跳轉到請假申請節點
Task vacationApply = taskService.createTaskQuery().processInstanceId(processInstance.getId()).singleResult();
//設置請假申請任務的執行人
taskService.setAssignee(vacationApply.getId(), req.getUserId().toString());
//設置流程參數:請假天數和表單ID
//流程引擎會根據請假天數days>3判斷流程走向
//formId是用來將流程數據和表單數據關聯起來
Map<String, Object> args = new HashMap<>();
args.put("days", req.getDays());
args.put("formId", formId);
//完成請假申請任務
taskService.complete(vacationApply.getId(), args);
3.待審批列表

//查出當前登錄用戶所在的用戶組
List<Group> groups = identityService.createGroupQuery()
.groupMember(String.valueOf(userId)).list();
List<String> groupNames = groups.stream()
.map(group -> group.getName()).collect(Collectors.toList());
//查詢用戶組的待審批請假流程列表
List<Task> tasks = taskService.createTaskQuery()
.processDefinitionKey("vacation")
.taskCandidateGroupIn(groupNames)
.listPage(pageNum - 1, pageSize);
//根據流程實例ID查詢請假申請表單數據
List<String> processInstanceIds = tasks.stream()
.map(task -> task.getProcessInstanceId())
.collect(Collectors.toList());
List<VacationApplyBasicPO> vacationApplyList =
vacationRepository.getVacationApplyList(processInstanceIds);
4.請假審批功能

//查詢當前審批節點
Task vacationAudit = taskService.createTaskQuery()
.taskId(req.getTaskId()).singleResult();
if (req.getAuditResult() == 1) {//審批通過
//設置流程參數:審批ID
Map<String, Object> args = new HashMap<>();
args.put("auditId", auditId);
<span class="hljs-comment"><span class="hljs-comment">//設置審批任務的執行人</span></span>
taskService.claim(vacationAudit.getId(), req.getUserId().toString());
<span class="hljs-comment"><span class="hljs-comment">//完成審批任務</span></span>
taskService.complete(vacationAudit.getId(), args);
} else {
//審批不通過,結束流程
runtimeService.deleteProcessInstance(vacationAudit.getProcessInstanceId(), auditId);
}
5.查看流程圖功能

//controller層代碼
@RequestMapping(value = "/image", method = RequestMethod.GET)
public void image(HttpServletResponse response, @RequestParam String processInstanceId) {
try {
InputStream is = vacationService.getDiagram(processInstanceId);
if (is == null)
return;
response.setContentType(<span class="hljs-string"><span class="hljs-string">"image/png"</span></span>);
BufferedImage image = ImageIO.read(is);
OutputStream out = response.getOutputStream();
ImageIO.write(image, <span class="hljs-string"><span class="hljs-string">"png"</span></span>, out);
is.close();
out.close();
} <span class="hljs-keyword"><span class="hljs-keyword">catch</span></span> (Exception ex) {
logger.error(<span class="hljs-string"><span class="hljs-string">"查看流程圖失敗"</span></span>, ex);
}
}
//service層代碼
@Override
public InputStream getDiagram(String processInstanceId) {
//獲得流程實例
ProcessInstance processInstance = runtimeService.createProcessInstanceQuery()
.processInstanceId(processInstanceId).singleResult();
String processDefinitionId = StringUtils.EMPTY;
if (processInstance == null) {
//查詢已經結束的流程實例
HistoricProcessInstance processInstanceHistory =
historyService.createHistoricProcessInstanceQuery()
.processInstanceId(processInstanceId).singleResult();
if (processInstanceHistory == null)
return null;
else
processDefinitionId = processInstanceHistory.getProcessDefinitionId();
} else {
processDefinitionId = processInstance.getProcessDefinitionId();
}
<span class="hljs-comment"><span class="hljs-comment">//使用宋體</span></span>
String fontName = <span class="hljs-string"><span class="hljs-string">"宋體"</span></span>;
<span class="hljs-comment"><span class="hljs-comment">//獲取BPMN模型對象</span></span>
BpmnModel model = repositoryService.getBpmnModel(processDefinitionId);
<span class="hljs-comment"><span class="hljs-comment">//獲取流程實例當前的節點,需要高亮顯示</span></span>
List<String> currentActs = Collections.EMPTY_LIST;
<span class="hljs-keyword"><span class="hljs-keyword">if</span></span> (processInstance != <span class="hljs-keyword"><span class="hljs-keyword">null</span></span>)
currentActs = runtimeService.getActiveActivityIds(processInstance.getId());
<span class="hljs-keyword"><span class="hljs-keyword">return</span></span> processEngine.getProcessEngineConfiguration()
.getProcessDiagramGenerator()
.generateDiagram(model, <span class="hljs-string"><span class="hljs-string">"png"</span></span>, currentActs, <span class="hljs-keyword"><span class="hljs-keyword">new</span></span> ArrayList<String>(),
fontName, fontName, fontName, <span class="hljs-keyword"><span class="hljs-keyword">null</span></span>, <span class="hljs-number"><span class="hljs-number">1.0</span></span>);
}
最后,spring boot應用打成jar包部署的時候,需要注意:。