SpringBoot整合工作流引擎Activiti
環(huán)境:Springboot2.2.11.RELEASE + Activiti7.1.0.M6 + MySQL
環(huán)境說(shuō)明:
<dependencies>
<dependency>
<groupId>org.activiti.dependencies</groupId>
<artifactId>activiti-dependencies</artifactId>
<version>7.1.0.M6</version>
<type>pom</type>
</dependency>
<dependency>
<groupId>org.activiti</groupId>
<artifactId>activiti-spring-boot-starter</artifactId>
<version>7.1.0.M6</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<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.1.4</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
<exclusions>
<exclusion>
<groupId>org.junit.vintage</groupId>
<artifactId>junit-vintage-engine</artifactId>
</exclusion>
</exclusions>
</dependency>
</dependencies>
不知為何activit7中要吧這security強(qiáng)關(guān)聯(lián)。。。
以上是pom.xml中所要引入的依賴(lài)。
所有的表:
圖片
表結(jié)構(gòu)說(shuō)明:
ACT_RE_*: 'RE’表示repository。這個(gè)前綴的表包含了流程定義和流程 靜態(tài)資源(圖片、規(guī)則等等)
ACT_RU_*: 'RU’表示runtime。這些運(yùn)行時(shí)的表,包含流程實(shí)例,認(rèn)為,變量,異步任務(wù)等運(yùn)行中的數(shù)據(jù)。Activiti只在流程實(shí)例執(zhí)行過(guò)程中保持這些數(shù)據(jù),在流程結(jié)束時(shí)就會(huì)刪除這些記錄。這樣運(yùn)行時(shí)表可以一直很小速度很快。
ACT_HI_*: 'HI’表示history。這些表包含歷史數(shù)據(jù),比如歷史流程實(shí)例,遍歷,任務(wù)等等。
ACT_GE_*: 'GE’表示general。通用數(shù)據(jù),用于不同場(chǎng)景。
數(shù)據(jù)表分類(lèi)
通用數(shù)據(jù)(act_ge_*)
圖片
流程定義(act_re_*)
圖片
運(yùn)行實(shí)例(act_ru_*)
圖片
歷史流程(act_hi_*)
圖片
其他
圖片
來(lái)源網(wǎng)絡(luò)
核心類(lèi)
ProcessEngine
????流程引擎的抽象,可以通過(guò)此類(lèi)獲取需要的所有服務(wù)。
????通過(guò)ProcessEngine獲取,Activiti將不同生命周期的服務(wù)封裝在不同Service中,包括定義、部署、運(yùn)行。通過(guò)服務(wù)類(lèi)可獲取相關(guān)生命周期中的服務(wù)信息。
TaskService
????流程運(yùn)行過(guò)程中,每個(gè)任務(wù)節(jié)點(diǎn)的相關(guān)操作接口,如complete,delete,delegate等。
RepositoryService
????流程定義和部署相關(guān)的存儲(chǔ)服務(wù)。
RuntimeService
????流程運(yùn)行時(shí)相關(guān)的服務(wù),如根據(jù)流程好啟動(dòng)流程實(shí)例startProcessInstanceByKey。
HistoryService
????歷史記錄相關(guān)服務(wù)接口。
關(guān)于eclipse中安裝插件就不說(shuō)了,我是把插件下載下來(lái)安裝的,在線安裝不上。
設(shè)計(jì)請(qǐng)假流程
在src/main/resources下新建processes文件夾,springboot下默認(rèn)的流程文件定義路徑前綴及文件后綴如下:
圖片
這里可以在application.yml配置文件中更改。
設(shè)計(jì)一個(gè)請(qǐng)假的流程holiday.bpmn
圖片
<?xml versinotallow="1.0" encoding="UTF-8"?>
<definitions xmlns="http://www.omg.org/spec/BPMN/20100524/MODEL" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:activiti="http://activiti.org/bpmn" xmlns:bpmndi="http://www.omg.org/spec/BPMN/20100524/DI" xmlns:omgdc="http://www.omg.org/spec/DD/20100524/DC" xmlns:omgdi="http://www.omg.org/spec/DD/20100524/DI" typeLanguage="http://www.w3.org/2001/XMLSchema" expressionLanguage="http://www.w3.org/1999/XPath" targetNamespace="http://www.pack.org">
<process id="holiday" name="holiday" isExecutable="true">
<startEvent id="startevent1" name="Start"></startEvent>
<endEvent id="endevent1" name="End"></endEvent>
<userTask id="usertask1" name="部門(mén)經(jīng)理審批" activiti:assignee="${mgr}"></userTask>
<userTask id="usertask2" name="總經(jīng)理審批" activiti:assignee="${top}"></userTask>
<sequenceFlow id="flow2" sourceRef="usertask1" targetRef="usertask2"></sequenceFlow>
<sequenceFlow id="flow3" sourceRef="usertask2" targetRef="endevent1"></sequenceFlow>
<userTask id="usertask3" name="填寫(xiě)審批單" activiti:assignee="${assignee}"></userTask>
<sequenceFlow id="flow4" sourceRef="startevent1" targetRef="usertask3"></sequenceFlow>
<sequenceFlow id="flow5" sourceRef="usertask3" targetRef="usertask1"></sequenceFlow>
</process>
<bpmndi:BPMNDiagram id="BPMNDiagram_holiday">
<bpmndi:BPMNPlane bpmnElement="holiday" id="BPMNPlane_holiday">
<bpmndi:BPMNShape bpmnElement="startevent1" id="BPMNShape_startevent1">
<omgdc:Bounds height="35.0" width="35.0" x="505.0" y="60.0"></omgdc:Bounds>
</bpmndi:BPMNShape>
<bpmndi:BPMNShape bpmnElement="endevent1" id="BPMNShape_endevent1">
<omgdc:Bounds height="35.0" width="35.0" x="505.0" y="550.0"></omgdc:Bounds>
</bpmndi:BPMNShape>
<bpmndi:BPMNShape bpmnElement="usertask1" id="BPMNShape_usertask1">
<omgdc:Bounds height="55.0" width="105.0" x="470.0" y="290.0"></omgdc:Bounds>
</bpmndi:BPMNShape>
<bpmndi:BPMNShape bpmnElement="usertask2" id="BPMNShape_usertask2">
<omgdc:Bounds height="55.0" width="105.0" x="470.0" y="420.0"></omgdc:Bounds>
</bpmndi:BPMNShape>
<bpmndi:BPMNShape bpmnElement="usertask3" id="BPMNShape_usertask3">
<omgdc:Bounds height="55.0" width="105.0" x="470.0" y="170.0"></omgdc:Bounds>
</bpmndi:BPMNShape>
<bpmndi:BPMNEdge bpmnElement="flow2" id="BPMNEdge_flow2">
<omgdi:waypoint x="522.0" y="345.0"></omgdi:waypoint>
<omgdi:waypoint x="522.0" y="420.0"></omgdi:waypoint>
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge bpmnElement="flow3" id="BPMNEdge_flow3">
<omgdi:waypoint x="522.0" y="475.0"></omgdi:waypoint>
<omgdi:waypoint x="522.0" y="550.0"></omgdi:waypoint>
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge bpmnElement="flow4" id="BPMNEdge_flow4">
<omgdi:waypoint x="522.0" y="95.0"></omgdi:waypoint>
<omgdi:waypoint x="522.0" y="170.0"></omgdi:waypoint>
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge bpmnElement="flow5" id="BPMNEdge_flow5">
<omgdi:waypoint x="522.0" y="225.0"></omgdi:waypoint>
<omgdi:waypoint x="522.0" y="290.0"></omgdi:waypoint>
</bpmndi:BPMNEdge>
</bpmndi:BPMNPlane>
</bpmndi:BPMNDiagram>
</definitions>
上面的每一個(gè)節(jié)點(diǎn)(任務(wù))都動(dòng)態(tài)的指派了用戶(hù)執(zhí)行。
填寫(xiě)審批單:${assignee};
部門(mén)經(jīng)理審批: ${mgr};
總經(jīng)理審批:${top};
每一個(gè)節(jié)點(diǎn)執(zhí)行完成時(shí)都需要指明下一個(gè)節(jié)點(diǎn)的執(zhí)行人。
配置文件
mybatis:
mapper-locations: classpath:mapper/*.xml
type-aliases-package: com.pack.domain
server:
port: 8080
spring:
activiti:
check-process-definitions: true
db-history-used: true
history-level: full
database-schema-update: true
datasource:
driverClassName: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://localhost:3306/activiti?serverTimeznotallow=GMT%2B8
username: root
password: xxxxxx
type: com.zaxxer.hikari.HikariDataSource
hikari:
minimumIdle: 10
maximumPoolSize: 200
autoCommit: true
idleTimeout: 30000
poolName: MasterDatabookHikariCP
maxLifetime: 1800000
connectionTimeout: 30000
connectionTestQuery: SELECT 1
spring.activiti.db-history-used:表示是用歷史表,如果不設(shè)置為true那么只會(huì)生成17張表,只有設(shè)置為true后才會(huì)生成25張表。如果不生成歷史表那么,流程圖及運(yùn)行節(jié)點(diǎn)無(wú)法展示。
spring.activiti.history-level:對(duì)于歷史數(shù)據(jù),保存到何種粒度,Activiti提供了history-level屬性對(duì)其進(jìn)行配置。history-level屬性有點(diǎn)像log4j的日志輸出級(jí)別,該屬性有以下四個(gè)值:
- none:不保存任何的歷史數(shù)據(jù),因此,在流程執(zhí)行過(guò)程中,這是最高效的。
- activity:級(jí)別高于none,保存流程實(shí)例與流程行為,其他數(shù)據(jù)不保存。
- audit:除activity級(jí)別會(huì)保存的數(shù)據(jù)外,還會(huì)保存全部的流程任務(wù)及其屬性。audit為history的默認(rèn)值。
- full:保存歷史數(shù)據(jù)的最高級(jí)別,除了會(huì)保存audit級(jí)別的數(shù)據(jù)外,還會(huì)保存其他全部流程相關(guān)的細(xì)節(jié)數(shù)據(jù),包括一些流程參數(shù)等。
spring.activiti.check-process-definitions:如果不設(shè)置為true,那么流程定義必須手動(dòng)進(jìn)行部署。
- sprint security配置,放行任何請(qǐng)求:
- 服務(wù)工具類(lèi)HolidayService
@Service
public class HolidayService {
private static final Logger logger = LoggerFactory.getLogger(HolidayService.class);
@Resource
private ProcessEngine processEngine;
@Resource
private RepositoryService repositoryService ;
@Resource
private RuntimeService runtimeService ;
@Resource
private TaskService taskService ;
/**
* <p>
* 流程定義的部署 activiti表有哪些?
* act_re_deployment 流程定義部署表,記錄流程部署信息
* act_re_procdef 流程定義表,記錄流程定義信息
* act_ge_bytearray 資源表(bpmn文件及png文件)
* </p>
* <p>時(shí)間:2021年1月22日-下午3:33:00</p>
* @author xg
* @return void
*/
public void createDeploy() {
Deployment deployment = repositoryService.createDeployment()
.addClasspathResource("processes/holiday.bpmn")
.addClasspathResource("processes/holiday.png")
.name("請(qǐng)假申請(qǐng)單流程")
.key("holiday")
.category("InnerP")
.deploy();
logger.info("流程部署id: {}", deployment.getId());
logger.info("流程部署名稱(chēng): {}", deployment.getName());
}
// 注意這里這個(gè)方法是當(dāng)我們沒(méi)有開(kāi)啟自動(dòng)部署流程定義時(shí),就需要手動(dòng)部署。
/**
* <p>
* 流程定義查詢(xún)
* </p>
* <p>時(shí)間:2021年1月22日-下午3:45:02</p>
* @author xg
* @param processDefinition
* @return void
*/
public List<ProcessDefinition> queryProcessDefinitionByKey(String processDefinition) {
// 查詢(xún)流程定義
ProcessDefinitionQuery processDefinitionQuery = repositoryService.createProcessDefinitionQuery();
List<ProcessDefinition> list = processDefinitionQuery.processDefinitionKey(processDefinition).list();
list.forEach(pd -> {
logger.info("------------------------------------------------");
logger.info("流程部署id:{}", pd.getDeploymentId());
logger.info("流程定義id:{}", pd.getId());
logger.info("流程定義名稱(chēng):{}", pd.getName());
logger.info("流程定義key:{}", pd.getKey());
logger.info("流程定義版本:{}", pd.getVersion());
logger.info("------------------------------------------------");
});
return list ;
}
/**
* <p>
* 刪除流程
* </p>
* <p>時(shí)間:2021年1月22日-下午4:21:40</p>
* @author xg
* @return void
*/
public void deleteDeployment(String deploymentId) {
// 設(shè)置true 級(jí)聯(lián)刪除流程定義,即使該流程有流程實(shí)例啟動(dòng)也可以刪除,設(shè)置為false非級(jí)別刪除方式,如果流程
repositoryService.deleteDeployment(deploymentId, true);
}
/**
* <p>
* 啟動(dòng)流程實(shí)例(比如用戶(hù)根據(jù)定義好的流程發(fā)起一個(gè)流程的實(shí)例(這里的請(qǐng)假流程申請(qǐng)))
* <p>時(shí)間:2021年1月22日-下午4:54:56</p>
* @author xg
* @return void
*/
public void startProcessInstanceById(String processDefinitionId) {
ProcessInstance processInstance = runtimeService.startProcessInstanceById(processDefinitionId) ;
logger.info("流程定義ID: {}", processInstance.getProcessDefinitionId());
logger.info("流程實(shí)例ID: {}", processInstance.getId());
}
/**
* <p>
* 啟動(dòng)流程實(shí)例,指定業(yè)務(wù)Key(方便關(guān)聯(lián)業(yè)務(wù)數(shù)據(jù))(比如用戶(hù)根據(jù)定義好的流程發(fā)起一個(gè)流程的實(shí)例(這里的請(qǐng)假流程申請(qǐng)))
* Businesskey(業(yè)務(wù)標(biāo)識(shí))
啟動(dòng)流程實(shí)例時(shí),指定的businesskey,就會(huì)在act_ru_execution #流程實(shí)例的執(zhí)行表中存儲(chǔ)businesskey。
Businesskey:業(yè)務(wù)標(biāo)識(shí),通常為業(yè)務(wù)表的主鍵,業(yè)務(wù)標(biāo)識(shí)和流程實(shí)例一一對(duì)應(yīng)。業(yè)務(wù)標(biāo)識(shí)來(lái)源于業(yè)務(wù)系統(tǒng)。存儲(chǔ)業(yè)務(wù)標(biāo)識(shí)就是根據(jù)業(yè)務(wù)標(biāo)識(shí)來(lái)關(guān)聯(lián)查詢(xún)業(yè)務(wù)系統(tǒng)的數(shù)據(jù)。
比如:請(qǐng)假流程啟動(dòng)一個(gè)流程實(shí)例,就可以將請(qǐng)假單的id作為業(yè)務(wù)標(biāo)識(shí)存儲(chǔ)到activiti中,
將來(lái)查詢(xún)activiti的流程實(shí)例信息就可以獲取請(qǐng)假單的id從而關(guān)聯(lián)查詢(xún)業(yè)務(wù)系統(tǒng)數(shù)據(jù)庫(kù)得到請(qǐng)假單信息。
* <p>時(shí)間:2021年1月22日-下午4:54:56</p>
* @author xg
* @return void
*/
public void startProcessInstanceToBussinessKey(String processDefinitionId, String bussinessKey) {
ProcessInstance processInstance = runtimeService.startProcessInstanceById(processDefinitionId, bussinessKey);
logger.info("流程定義ID: {}", processInstance.getProcessDefinitionId());
logger.info("流程實(shí)例ID: {}", processInstance.getId());
logger.info("BussinessKey: {}", processInstance.getBusinessKey()) ;
}
/**
* <p>
* 設(shè)置assignee的取值,用戶(hù)可以在界面上設(shè)置流程的執(zhí)行人
* </p>
* <p>時(shí)間:2021年1月22日-下午8:30:39</p>
* @author xg
* @param processDefinitionId
* @return void
*/
public void startProcessInstanceAssignVariables(String processDefinitionId, Map<String, Object> variables) {
ProcessInstance processInstance = runtimeService.startProcessInstanceById(processDefinitionId, variables);
logger.info("流程定義ID: {}", processInstance.getProcessDefinitionId());
logger.info("流程實(shí)例ID: {}", processInstance.getId());
logger.info("BussinessKey: {}", processInstance.getBusinessKey()) ;
}
/**
* <p>
* 查詢(xún)指派關(guān)聯(lián)的用戶(hù)任務(wù)
* </p>
* <p>時(shí)間:2021年1月23日-上午11:39:56</p>
* @author xg
* @param assignee 關(guān)聯(lián)用戶(hù)
* @return List<Task>
*/
public List<Task> queryTasks(String assignee) {
TaskQuery query = taskService.createTaskQuery() ;
return query.taskAssignee(assignee).orderByTaskCreateTime().asc().list() ;
}
public void executionTask(Map<String, Object> variables, String instanceId) {
Task task = taskService.createTaskQuery().processInstanceId(instanceId).singleResult() ;
if (task == null) {
logger.error("任務(wù)【{}】不存在", instanceId) ;
throw new RuntimeException("任務(wù)【" + instanceId + "】不存在") ;
}
taskService.complete(task.getId(), variables) ;
}
}
- HolidayController接口
@RestController
@RequestMapping("/holidays")
public class HolidayController {
@Resource
private HolidayService holidayService ;
/**
* <p>查詢(xún)制定key的流程審批</p>
* <p>時(shí)間:2021年1月23日-上午11:17:02</p>
* @author xg
* @param key ProcessDefinitionKey
* @return R
*/
@GetMapping("")
public R lists(String key) {
return R.success(holidayService.queryProcessDefinitionByKey(key)) ;
}
/**
* <p>創(chuàng)建請(qǐng)假流程審批(私有)</p>
* <p>時(shí)間:2021年1月23日-上午10:31:47</p>
* @author xg
* @return R
*/
@GetMapping("/_deploy")
public R createDeploy() {
holidayService.createDeploy();
return R.success() ;
}
/**
* <p>啟動(dòng)請(qǐng)假審批流程</p>
* <p>時(shí)間:2021年1月23日-上午10:32:55</p>
* @author xg
* @param userId
* @param processDefinitionId 流程定義Id
* @return R
*/
@GetMapping("/start")
public R startProcess(String userId, String processDefinitionId) {
Map<String, Object> variables = new HashMap<>() ;
variables.put("assignee", userId) ;
holidayService.startProcessInstanceAssignVariables(processDefinitionId, variables) ;
return R.success() ;
}
/**
* <p>
* 查詢(xún)指派給我的任務(wù)
* </p>
* <p>時(shí)間:2021年1月23日-上午11:41:21</p>
* @author xg
* @param userId 用戶(hù)Id
* @return R
*/
@GetMapping("/tasks")
public R myTasks(String userId) {
List<Task> list = holidayService.queryTasks(userId) ;
// 注意這里需要我們自己組裝下數(shù)據(jù),不然會(huì)報(bào)錯(cuò)。
List<Map<String, Object>> result = list.stream().map(task -> {
Map<String, Object> res = new HashMap<String, Object>() ;
res.put("id", task.getId()) ;
res.put("assignee", task.getAssignee()) ;
res.put("createTime", task.getCreateTime()) ;
res.put("bussinessKey", task.getBusinessKey()) ;
res.put("category", task.getCategory()) ;
res.put("dueDate", task.getDueDate()) ; // 到期日期
res.put("desc", task.getDescription()) ;
res.put("name", task.getName()) ;
res.put("owner", task.getOwner()) ;
res.put("instanceId", task.getProcessInstanceId()) ;
res.put("variables", task.getProcessVariables()) ;
return res ;
}).collect(Collectors.toList()) ;
return R.success(result) ;
}
/**
* <p>
* 填寫(xiě)審批單
* </p>
* <p>時(shí)間:2021年1月23日-上午11:57:30</p>
* @author xg
* @param Map取值如下
* @param days 請(qǐng)假天數(shù)
* @param explain 審批單說(shuō)明
* @param instanceId 流程實(shí)例ID
* @param assignee 指定下一個(gè)流程執(zhí)行人
* @return R
*/
@GetMapping("/apply")
public R fillApply(@RequestParam Map<String, Object> variables) {
String instanceId = (String) variables.remove("instanceId") ;
if (StringUtils.isEmpty(instanceId)) {
return R.failure("未知任務(wù)") ;
}
holidayService.executionTask(variables, instanceId);
return R.success() ;
}
}
測(cè)試:
1、啟動(dòng)服務(wù)
這里放在processes中的流程定義文件已經(jīng)被自動(dòng)部署上了。查看表act_re_procdef
- 查詢(xún)制定key的流程審批
接口:/holidays
圖片
- 啟動(dòng)請(qǐng)假審批流程
接口:/holidays/start
圖片
參數(shù):
processDefinitionId:流程定義中的ID。
userId:要處理用戶(hù)的id。
查看表信息:
圖片
這時(shí)候就為用戶(hù)id為:10000的生成了一個(gè)要處理的任務(wù),填寫(xiě)審批單。
- 查詢(xún)指派給我需要處理的任務(wù)
接口:/holidays/tasks
圖片
- 填寫(xiě)審批單
接口:/holidays/apply
圖片
參數(shù):
mgr:指定下一個(gè)節(jié)點(diǎn)處理人。
explain:請(qǐng)假原因。
days:請(qǐng)假天數(shù)。
這里根據(jù)自己的業(yè)務(wù)需要去設(shè)置。
再次調(diào)用查詢(xún)接口userId=10002
圖片
流程已經(jīng)到了部門(mén)經(jīng)理。
再次調(diào)用/holidays/apply接口。
圖片
參數(shù):
top:指明總經(jīng)理節(jié)點(diǎn)需要處理的userId。
圖片
再次調(diào)用/holidays/apply接口。(總經(jīng)理處理)
圖片
查詢(xún)對(duì)應(yīng)的任務(wù)表信息,已經(jīng)沒(méi)有數(shù)據(jù)了。
圖片
查詢(xún)表:act_hi_actinst
圖片
到此一個(gè)流程就走完了。下篇 查看流程圖。
完畢!?。?/p>