SpringBoot+Vue+Flowable模擬實(shí)現(xiàn)請(qǐng)假審批流程
小伙伴們知道松哥最近在錄 TienChin 項(xiàng)目視頻,這個(gè)項(xiàng)目會(huì)用到工作流,為了幫助小伙伴們更好的理解這個(gè)項(xiàng)目,松哥最近會(huì)出幾篇文章和大伙聊一聊工作流 flowable 的使用,算是給 TienChin 項(xiàng)目的第一個(gè)鋪墊,當(dāng)然,在 TienChin 項(xiàng)目的系列視頻中,我也會(huì)和大家詳細(xì)聊一聊 flowable 流程引擎的使用。
今天我就先寫一個(gè)簡(jiǎn)單的請(qǐng)假流程,讓小伙伴們對(duì) flowable 先有一個(gè)直觀的認(rèn)知。
1. 效果展示
在正式開搞之前,我先來給小伙伴們看下我們今天要完成的效果。
簡(jiǎn)單起見,我這里并沒有引入用戶、角色等概念,涉及到用戶的地方都是手動(dòng)輸入,在后續(xù)的文章中我會(huì)繼續(xù)結(jié)合 Spring Security 來和大家展示引入用戶之后的情況。
我們先來看看請(qǐng)假頁(yè)面:

員工可以在這個(gè)頁(yè)面輸入姓名,請(qǐng)假天數(shù)以及請(qǐng)假理由等,然后點(diǎn)擊按鈕提交一個(gè)請(qǐng)假申請(qǐng)。
當(dāng)員工提交請(qǐng)假申請(qǐng)之后,這個(gè)請(qǐng)假申請(qǐng)默認(rèn)是由經(jīng)理來處理的,此時(shí)經(jīng)理登錄之后,就可以看到員工提交上來的請(qǐng)求:

經(jīng)理此時(shí)可以選擇批準(zhǔn)或者拒絕。無論是批準(zhǔn)還是拒絕,都可以通過短信或者郵件等告知員工。
對(duì)于員工來說,也可以在一個(gè)頁(yè)面查詢自己請(qǐng)假流程的最終情況:

可能有小伙伴已經(jīng)注意到了,我們這里所有涉及到用戶名的地方,都需要手動(dòng)輸入。這是因?yàn)槲覟榱俗屵@個(gè)案例足夠簡(jiǎn)單,暫時(shí)沒有引入 Spring Security,只是單純的和大家分享 Flowable 的用法,等小伙伴們通過這篇文章掌握了 Flowable 的基本用法之后,下篇文章我會(huì)和大家分享如何結(jié)合具體的用戶來使用。
2. 工程創(chuàng)建
我就直接來和小伙伴們展示 Spring Boot 中 flowable 的用法了。
首先我們創(chuàng)建一個(gè) Spring Boot 項(xiàng)目,創(chuàng)建的時(shí)候引入 Web 和 MySQL 驅(qū)動(dòng)依賴即可,項(xiàng)目創(chuàng)建成功之后,再引入 flowable 依賴,最終的依賴文件如下:
<dependency> ????<groupId>org.springframework.boot</groupId> ????<artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> ????<groupId>org.flowable</groupId> ????<artifactId>flowable-spring-boot-starter</artifactId> ????<version>6.7.2</version> </dependency> <dependency> ????<groupId>mysql</groupId> ????<artifactId>mysql-connector-java</artifactId> ????<scope>runtime</scope> </dependency>
項(xiàng)目創(chuàng)建成功之后,首先需要我們?cè)?application.properties 中配置一下數(shù)據(jù)庫(kù)連接信息,如下:
spring.datasource.username=root spring.datasource.password=123 spring.datasource.url=jdbc:mysql:///flowable02?serverTimezone=Asia/Shanghai&useSSL=false&nullCatalogMeansCurrent=true
配置完成之后,當(dāng) Spring Boot 項(xiàng)目第一次啟動(dòng)的時(shí)候,會(huì)自動(dòng)創(chuàng)建出來對(duì)應(yīng)的表和需要的數(shù)據(jù)。
同時(shí),Spring Boot 項(xiàng)目也會(huì)自動(dòng)創(chuàng)建并暴露 Flowable 中的 ProcessEngine、CmmnEngine、DmnEngine、FormEngine、ContentEngine 及 IdmEngine 等 Bean。
并且所有的 Flowable 服務(wù)都暴露為 Spring Bean。例如 RuntimeService、TaskService、HistoryService 等等服務(wù),我們都可以在需要使用的時(shí)候,直接注入就可以使用了。
同時(shí):
- resources/processes 目錄下的任何 BPMN 2.0 流程定義都會(huì)被自動(dòng)部署,所以在 Spring Boot 項(xiàng)目中,我們只需要將自己的流程文件放對(duì)位置即可,剩下的事情就會(huì)自動(dòng)完成。
- cases 目錄下的任何 CMMN 1.1 事例都會(huì)被自動(dòng)部署。
- forms 目錄下的任何 Form 定義都會(huì)被自動(dòng)部署。
3. 流程圖分析
今天這個(gè)例子比較簡(jiǎn)單,就是一個(gè)請(qǐng)假流程,我暫時(shí)先不跟小伙伴們?nèi)コ懂嬃鞒虉D的事,咱們直接用一個(gè)官網(wǎng)現(xiàn)成的請(qǐng)假流程圖:

我們先來簡(jiǎn)單分析一下這張圖:
- 最左側(cè)的圓圈叫做啟動(dòng)事件(start event),這表示一個(gè)流程實(shí)例的起點(diǎn)。
- 一個(gè)流程啟動(dòng)之后,首先到達(dá)第一個(gè)有用戶圖標(biāo)的矩形中,這個(gè)矩形稱為一個(gè) User Task,在這個(gè) User Task 中,經(jīng)理可以選擇批準(zhǔn)亦或者拒絕。
- UserTask 的下一步是一個(gè)菱形,這個(gè)稱作排他網(wǎng)關(guān)(Exclusive Gateway),這個(gè)會(huì)將請(qǐng)求路由到不同的地方。
- 先說批準(zhǔn),如果在第一個(gè)矩形中,經(jīng)理選擇了批準(zhǔn),那么就會(huì)進(jìn)入到一個(gè)帶有齒輪圖標(biāo)的矩形中,在這個(gè)矩形中我們我們可以額外做一些事情,然后又會(huì)調(diào)用到一個(gè) UserTask,最終完成整個(gè)流程。
- 如果經(jīng)理選擇了拒絕,則會(huì)進(jìn)入到下面的發(fā)郵件的矩形中,在這個(gè)中我們可以給員工發(fā)送一個(gè)通知,告知他請(qǐng)假?zèng)]有通過。
- 當(dāng)系統(tǒng)走到最右邊的圓圈之后,就表示這個(gè)流程執(zhí)行結(jié)束了。
這個(gè)流程圖對(duì)應(yīng)的 XML 文件位于 src/main/resources/processes/holiday-request.bpmn20.xml 位置,其內(nèi)容如下:
<?xml?version="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:flowable="http://flowable.org/bpmn"
?????????????typeLanguage="http://www.w3.org/2001/XMLSchema"?expressionLanguage="http://www.w3.org/1999/XPath"
?????????????targetNamespace="http://www.flowable.org/processdef">
????<process?id="holidayRequest"?name="Holiday?Request"?isExecutable="true">
????????<startEvent?id="startEvent"/>
????????<sequenceFlow?sourceRef="startEvent"?targetRef="approveTask"/>
????????<userTask?id="approveTask"?name="Approve?or?reject?request"?flowable:candidateGroups="managers"/>
????????<sequenceFlow?sourceRef="approveTask"?targetRef="decision"/>
????????<exclusiveGateway?id="decision"/>
????????<sequenceFlow?sourceRef="decision"?targetRef="externalSystemCall">
????????????<conditionExpression?xsi:type="tFormalExpression">
????????????????<![CDATA[
??????????${approved}
????????]]>
????????????</conditionExpression>
????????</sequenceFlow>
????????<sequenceFlow??sourceRef="decision"?targetRef="rejectLeave">
????????????<conditionExpression?xsi:type="tFormalExpression">
????????????????<![CDATA[
??????????${!approved}
????????]]>
????????????</conditionExpression>
????????</sequenceFlow>
????????<serviceTask?id="externalSystemCall"?name="Enter?holidays?in?external?system"
?????????????????????flowable:class="org.javaboy.flowable02.flowable.Approve"/>
????????<sequenceFlow?sourceRef="externalSystemCall"?targetRef="holidayApprovedTask"/>
????????<userTask?id="holidayApprovedTask"?flowable:assignee="${employee}"?name="Holiday?approved"/>
????????<sequenceFlow?sourceRef="holidayApprovedTask"?targetRef="approveEnd"/>
????????<serviceTask?id="rejectLeave"?name="Send?out?rejection?email"
?????????????????????flowable:class="org.javaboy.flowable02.flowable.Reject"/>
????????<sequenceFlow?sourceRef="rejectLeave"?targetRef="rejectEnd"/>
????????<endEvent?id="approveEnd"/>
????????<endEvent?id="rejectEnd"/>
????</process>
</definitions>
很多想學(xué)習(xí)流程引擎的小伙伴都會(huì)被這個(gè) XML 文件勸退,但是?。?!
如果你愿意靜下心來認(rèn)真閱讀這個(gè) XML 文件,你會(huì)發(fā)現(xiàn)流程引擎原來如此簡(jiǎn)單!
我們來挨個(gè)看下這里的每一個(gè)節(jié)點(diǎn):
- process:這表示一個(gè)流程,例如本文和大家分享的請(qǐng)假就是一個(gè)流程。
- startEvent:這表示流程的開始,這就是一個(gè)開始事件。
- userTask:這就是一個(gè)具體的流程節(jié)點(diǎn)了,flowable:candidateGroups 屬性表示這個(gè)節(jié)點(diǎn)該由哪個(gè)用戶組中的用戶來處理。
- sequenceFlow:這就是連接各個(gè)流程節(jié)點(diǎn)之間的線條,這個(gè)里邊一般有兩個(gè)屬性,sourceRef 和 targetRef,前者表示線條的起點(diǎn),后者表示線條的終點(diǎn)。
- exclusiveGateway:表示一個(gè)排他性網(wǎng)關(guān),也就是那個(gè)菱形選擇框。
- 從排他性網(wǎng)關(guān)出來的線條有兩個(gè),大家注意看上面的代碼,這兩個(gè)線條中都涉及到一個(gè)變量 approved,如果這個(gè)變量為 true,則 targeRef 就是 externalSystemCall;如果這個(gè)變量為 false,則 targetRef 就是 rejectLeave。
- serviceTask:這就是我們定義的一個(gè)具體的外部服務(wù),如果在整個(gè)流程執(zhí)行的過程中,你有一些需要自己完成的事情,那么可以通過 serviceTask 來實(shí)現(xiàn),這個(gè)節(jié)點(diǎn)會(huì)有一個(gè) flowable:class 屬性,這個(gè)屬性的值就是一個(gè)自定義類。
- 另外,上文中部分節(jié)點(diǎn)中還涉及到變量 ${},這個(gè)變量是在流程執(zhí)行的過程中傳入進(jìn)來的。
總而言之,只要小伙伴們靜下心來認(rèn)真閱讀一下上面的 XML,你會(huì)發(fā)現(xiàn) So Easy!
4. 請(qǐng)假申請(qǐng)
好了,接下來我們就來看一個(gè)具體的請(qǐng)假申請(qǐng)。由于請(qǐng)假流程只要放對(duì)位置,就會(huì)自動(dòng)加載,所以我們并不需要手動(dòng)加載請(qǐng)假流程,直接開始一個(gè)請(qǐng)假申請(qǐng)流程即可。
4.1 服務(wù)端接口
首先我們需要一個(gè)實(shí)體類來接受前端傳來的請(qǐng)假參數(shù):用戶名、請(qǐng)假天數(shù)以及請(qǐng)假理由:
public?class?AskForLeaveVO?{
????private?String?name;
????private?Integer?days;
????private?String?reason;
????//?省略?getter/setter
}
再拿出祖?zhèn)鞯?RespBean,以便響應(yīng)數(shù)據(jù)方便一些:
public?class?RespBean?{
????private?Integer?status;
????private?String?msg;
????private?Object?data;
????public?static?RespBean?ok(String?msg,?Object?data)?{
????????return?new?RespBean(200,?msg,?data);
????}
????public?static?RespBean?ok(String?msg)?{
????????return?new?RespBean(200,?msg,?null);
????}
????public?static?RespBean?error(String?msg,?Object?data)?{
????????return?new?RespBean(500,?msg,?data);
????}
????public?static?RespBean?error(String?msg)?{
????????return?new?RespBean(500,?msg,?null);
????}
????private?RespBean()?{
????}
????private?RespBean(Integer?status,?String?msg,?Object?data)?{
????????this.status?=?status;
????????this.msg?=?msg;
????????this.data?=?data;
????}
????//?省略?getter/setter
}
接下來我們提供一個(gè)處理請(qǐng)假申請(qǐng)的接口:
@RestController
public?class?AskForLeaveController?{
????@Autowired
????AskForLeaveService?askForLeaveService;
????@PostMapping("/ask_for_leave")
????public?RespBean?askForLeave(@RequestBody?AskForLeaveVO?askForLeaveVO)?{
????????return?askForLeaveService.askForLeave(askForLeaveVO);
????}
}
核心邏輯在 AskForLeaveService 中,來繼續(xù)看:
@Service
public?class?AskForLeaveService?{
????@Autowired
????RuntimeService?runtimeService;
????@Transactional
????public?RespBean?askForLeave(AskForLeaveVO?askForLeaveVO)?{
????????Map<String,?Object>?variables?=?new?HashMap<>();
????????variables.put("name",?askForLeaveVO.getName());
????????variables.put("days",?askForLeaveVO.getDays());
????????variables.put("reason",?askForLeaveVO.getReason());
????????try?{
????????????runtimeService.startProcessInstanceByKey("holidayRequest",?askForLeaveVO.getName(),?variables);
????????????return?RespBean.ok("已提交請(qǐng)假申請(qǐng)");
????????}?catch?(Exception?e)?{
????????????e.printStackTrace();
????????}
????????return?RespBean.error("提交申請(qǐng)失敗");
????}
}
小伙伴們看一下,在提交請(qǐng)假申請(qǐng)的時(shí)候,分別傳入了 name、days 以及 reason 三個(gè)參數(shù),我們將這三個(gè)參數(shù)放入到一個(gè) Map 中,然后通過 RuntimeService#startProcessInstanceByKey 方法來開啟一個(gè)流程,開啟流程的時(shí)候一共傳入了三個(gè)參數(shù):
- 第一個(gè)參數(shù)表示流程引擎的名字,這就是我們剛才在流程的 XML 文件中定義的名字。
- 第二個(gè)參數(shù)表示當(dāng)前這個(gè)流程的 key,我用了申請(qǐng)人的名字,將來我們可以通過申請(qǐng)人的名字查詢這個(gè)人曾經(jīng)提交的所有申請(qǐng)流程。
- 第三個(gè)參數(shù)就是我們的變量了。
好了,這服務(wù)端就寫好了。
4.2 前端頁(yè)面
接下來我們來開發(fā)前端頁(yè)面。
前端我使用 Vue+ElementUI+Axios,咱們這個(gè)案例比較簡(jiǎn)單,就沒有必要搭建單頁(yè)面了,直接用普通的 HTML 就行了。另外,Vue 我是用了 Vue3:
<!DOCTYPE?html>
<html?lang="en">
<head>
????<meta?charset="UTF-8">
????<title>Title</title>
????<script?src="https://unpkg.com/axios/dist/axios.min.js"></script>
????<!--?Import?style?-->
????<link?rel="stylesheet"? rel="external nofollow" rel="external nofollow" rel="external nofollow" />
????<script?src="https://unpkg.com/vue@3"></script>
????<!--?Import?component?library?-->
????<script?src="http://unpkg.com/element-plus"></script>
</head>
<body>
<div?id="app">
????<h1>開始一個(gè)請(qǐng)假流程</h1>
????<table>
????????<tr>
????????????<td>請(qǐng)輸入姓名:</td>
????????????<td>
????????????????<el-input?type="text"?v-model="afl.name"/>
????????????</td>
????????</tr>
????????<tr>
????????????<td>請(qǐng)輸入請(qǐng)假天數(shù):</td>
????????????<td>
????????????????<el-input?type="text"?v-model="afl.days"/>
????????????</td>
????????</tr>
????????<tr>
????????????<td>請(qǐng)輸入請(qǐng)假理由:</td>
????????????<td>
????????????????<el-input?type="text"?v-model="afl.reason"/>
????????????</td>
????????</tr>
????</table>
????<el-button?type="primary"?@click="submit">提交請(qǐng)假申請(qǐng)</el-button>
</div>
<script>
????Vue.createApp(
????????{
????????????data()?{
????????????????return?{
????????????????????afl:?{
????????????????????????name:?'javaboy',
????????????????????????days:?3,
????????????????????????reason:?'休息一下'
????????????????????}
????????????????}
????????????},
????????????methods:?{
????????????????submit()?{
????????????????????let?_this?=?this;
????????????????????axios.post('/ask_for_leave',?this.afl)
????????????????????????.then(function?(response)?{
????????????????????????????if?(response.data.status?==?200)?{
????????????????????????????????//提交成功
????????????????????????????????_this.$message.success(response.data.msg);
????????????????????????????}?else?{
????????????????????????????????//提交失敗
????????????????????????????????_this.$message.error(response.data.msg);
????????????????????????????}
????????????????????????})
????????????????????????.catch(function?(error)?{
????????????????????????????console.log(error);
????????????????????????});
????????????????}
????????????}
????????}
????).use(ElementPlus).mount('#app')
</script>
</body>
</html>
這個(gè)頁(yè)面有幾個(gè)需要注意的點(diǎn):
- 通過 Vue.createApp 來創(chuàng)建一個(gè) Vue 實(shí)例,這跟以前 Vue2 中直接 new 一個(gè) Vue 實(shí)例不一樣。
- 在最下面,通過 use 來配置 ElementPlus 插件,這個(gè)跟 Vue2 也不一樣。在 Vue2 中,如果我們單純的在 HTML 頁(yè)面中引用 ElementUI 并不需要這個(gè)步驟。
- 剩下的東西就比較簡(jiǎn)單了,上面先引入 Vue3、Axios 以及 ElementPlus,然后三個(gè)輸入框,點(diǎn)擊按鈕提交請(qǐng)求,參數(shù)就是三個(gè)輸入框中的數(shù)據(jù),提交成功或者失敗,分別彈個(gè)框出來提示一下就行了。
好啦,這就寫好了。
然而,提交完成后,沒有一個(gè)直觀的展示,雖然前端提示說提交成功了,但是究竟成功沒,還得眼見為實(shí)。
5. 任務(wù)展示
好了,接下來我們要做的事情就是把用戶提交的流程展示出來。
按理說,比如經(jīng)理登錄成功之后,系統(tǒng)頁(yè)面就自動(dòng)展示出來經(jīng)理需要審批的流程,但是我們當(dāng)前這個(gè)例子為了簡(jiǎn)單,就沒有登錄這個(gè)操作了,需要需要用戶將來在網(wǎng)頁(yè)上選一下自己的身份,接下來就會(huì)展示出這個(gè)身份所對(duì)應(yīng)的需要操作的流程。
我們來看任務(wù)接口:
@GetMapping("/list")
public?RespBean?leaveList(String?identity)?{
????return?askForLeaveService.leaveList(identity);
}
這個(gè)請(qǐng)求參數(shù) identity 就表示當(dāng)前用戶的身份(本來應(yīng)該是登錄后自動(dòng)獲取,但是因?yàn)槲覀兡壳皼]有登錄,所以這個(gè)參數(shù)是由前端傳遞過來)。來繼續(xù)看 askForLeaveService 中的方法:
@Service
public?class?AskForLeaveService?{
????@Autowired
????TaskService?taskService;
????public?RespBean?leaveList(String?identity)?{
????????List<Task>?tasks?=?taskService.createTaskQuery().taskCandidateGroup(identity).list();
????????List<Map<String,?Object>>?list?=?new?ArrayList<>();
????????for?(int?i?=?0;?i?<?tasks.size();?i++)?{
????????????Task?task?=?tasks.get(i);
????????????Map<String,?Object>?variables?=?taskService.getVariables(task.getId());
????????????variables.put("id",?task.getId());
????????????list.add(variables);
????????}
????????return?RespBean.ok("加載成功",?list);
????}
}
Task 就是流程中要做的每一件事情,我們首先通過 TaskService,查詢出來這個(gè)用戶需要處理的任務(wù),例如前端前傳來的是 managers,那么這里就是查詢所有需要由 managers 用戶組處理的任務(wù)。
這段代碼要結(jié)合流程圖一起來理解,小伙伴們回顧下我們流程圖中有如下一句:
<userTask?id="approveTask"?name="Approve?or?reject?request"?flowable:candidateGroups="managers"/>
這意思就是說這個(gè) userTask 是由 managers 這個(gè)組中的用戶來處理,所以上面 Java 代碼中的查詢就是查詢 managers 這個(gè)組中的用戶需要審批的任務(wù)。
我們將所有需要審批的任務(wù)查詢出來后,通過 taskId 可以進(jìn)一步查詢到這個(gè)任務(wù)中當(dāng)時(shí)傳入的各種變量,我們將這些數(shù)據(jù)封裝成一個(gè)對(duì)象,并最終返回到前端。
最后,我們?cè)賮砜聪虑岸隧?yè)面:
<!DOCTYPE?html>
<html?lang="en">
<head>
????<meta?charset="UTF-8">
????<title>Title</title>
????<script?src="https://unpkg.com/axios/dist/axios.min.js"></script>
????<!--?Import?style?-->
????<link?rel="stylesheet"? rel="external nofollow" rel="external nofollow" rel="external nofollow" />
????<script?src="https://unpkg.com/vue@3"></script>
????<!--?Import?component?library?-->
????<script?src="http://unpkg.com/element-plus"></script>
</head>
<body>
<div?id="app">
????<div>
????????<div>請(qǐng)選擇你的身份:</div>
????????<div>
????????????<el-select?name=""?id=""?v-model="identity"?@change="initTasks">
????????????????<el-option?:value="iden"?v-for="(iden,index)?in?identities"?:key="index"?:label="iden"></el-option>
????????????</el-select>
????????????<el-button?type="primary"?@click="initTasks">刷新一下</el-button>
????????</div>
????</div>
????<el-table?border?strip?:data="tasks">
????????<el-table-column?prop="name"?label="姓名"></el-table-column>
????????<el-table-column?prop="days"?label="請(qǐng)假天數(shù)"></el-table-column>
????????<el-table-column?prop="reason"?label="請(qǐng)假原因"></el-table-column>
????????<el-table-column?lable="操作">
????????????<template?#default="scope">
????????????????<el-button?type="primary"?@click="approveOrReject(scope.row.id,true,scope.row.name)">批準(zhǔn)</el-button>
????????????????<el-button?type="danger"?@click="approveOrReject(scope.row.id,false,scope.row.name)">拒絕</el-button>
????????????</template>
????????</el-table-column>
????</el-table>
</div>
<script>
????Vue.createApp(
????????{
????????????data()?{
????????????????return?{
????????????????????tasks:?[],
????????????????????identities:?[
????????????????????????'managers'
????????????????????],
????????????????????identity:?''
????????????????}
????????????},
????????????methods:?{
????????????????initTasks()?{
????????????????????let?_this?=?this;
????????????????????axios.get('/list?identity='?+?this.identity)
????????????????????????.then(function?(response)?{
????????????????????????????_this.tasks?=?response.data.data;
????????????????????????})
????????????????????????.catch(function?(error)?{
????????????????????????????console.log(error);
????????????????????????});
????????????????}
????????????}
????????}
????).use(ElementPlus).mount('#app')
</script>
</body>
</html>
大家看到,首先有一個(gè)下拉框,我們?cè)谶@個(gè)下拉框中來選擇用戶的身份。選擇完成后,觸發(fā) initTasks 方法,然后在這個(gè)方法中,發(fā)起網(wǎng)絡(luò)請(qǐng)求,最終將請(qǐng)求結(jié)果渲染出來。
最終效果如下:

當(dāng)然用戶也可以點(diǎn)擊刷新按鈕,刷新列表。
這樣,當(dāng)?shù)谖逍」?jié)中,員工提交了一個(gè)請(qǐng)假審批之后,我們?cè)谶@個(gè)列表中就可以查看到員工提交的請(qǐng)假審批了(在流程圖中,我們直接設(shè)置了用戶的請(qǐng)假審批固定提交給 managers,在后續(xù)的文章中,松哥會(huì)教大家如何把這個(gè)提交的目標(biāo)用戶變成一個(gè)動(dòng)態(tài)的)。
6. 請(qǐng)假審批
接下來經(jīng)理就可以選擇批準(zhǔn)或者是拒絕這請(qǐng)假了。
首先我們封裝一個(gè)實(shí)體類用來接受前端傳來的請(qǐng)求:
public?class?ApproveRejectVO?{
????private?String?taskId;
????private?Boolean?approve;
????private?String?name;
????//?省略?getter/setter
}
參數(shù)都好理解,approve 為 true 表示申請(qǐng)通過,false 表示申請(qǐng)被拒絕。
接下來我們來看接口:
@PostMapping("/handler")
public?RespBean?askForLeaveHandler(@RequestBody?ApproveRejectVO?approveRejectVO)?{
????return?askForLeaveService.askForLeaveHandler(approveRejectVO);
}
看具體的 askForLeaveHandler 方法:
@Service
public?class?AskForLeaveService?{
????@Autowired
????TaskService?taskService;
????public?RespBean?askForLeaveHandler(ApproveRejectVO?approveRejectVO)?{
????????try?{
????????????boolean?approved?=?approveRejectVO.getApprove();
????????????Map<String,?Object>?variables?=?new?HashMap<String,?Object>();
????????????variables.put("approved",?approved);
????????????variables.put("employee",?approveRejectVO.getName());
????????????Task?task?=?taskService.createTaskQuery().taskId(approveRejectVO.getTaskId()).singleResult();
????????????taskService.complete(task.getId(),?variables);
????????????if?(approved)?{
????????????????//如果是同意,還需要繼續(xù)走一步
????????????????Task?t?=?taskService.createTaskQuery().processInstanceId(task.getProcessInstanceId()).singleResult();
????????????????taskService.complete(t.getId());
????????????}
????????????return?RespBean.ok("操作成功");
????????}?catch?(Exception?e)?{
????????????e.printStackTrace();
????????}
????????return?RespBean.error("操作失敗");
????}
}
大家注意這個(gè)審批流程:
- 審批時(shí)需要兩個(gè)參數(shù),approved 和 employee,approved 為 true,就會(huì)自動(dòng)進(jìn)入到審批通過的流程中,approved 為 false 則會(huì)自動(dòng)進(jìn)入到拒絕流程中。
- 通過 taskService,結(jié)合 taskId,從流程中查詢出對(duì)應(yīng)的 task,然后調(diào)用 taskService.complete 方法傳入 taskId 和 變量,以使流程向下走。
- 小伙伴們?cè)倩仡櫼幌挛覀兦懊娴牧鞒虉D,如果請(qǐng)求被批準(zhǔn)備了,那么在執(zhí)行完自定義的 Approve 邏輯后,就會(huì)進(jìn)入到 Holiday approved 這個(gè) userTask 中,注意此時(shí)并不會(huì)繼續(xù)向下走了(還差一步到結(jié)束事件);如果是請(qǐng)求拒絕,則在執(zhí)行完自定義的 Reject 邏輯后,就進(jìn)入到結(jié)束事件了,這個(gè)流程就結(jié)束了。
- 針對(duì)第三條,所以代碼中我們還需要額外再加一步,如果是 approved 為 true,那么就再?gòu)漠?dāng)前流程中查詢出來需要執(zhí)行的 task,再調(diào)用 complete 繼續(xù)走一步,此時(shí)就到了結(jié)束事件了,這個(gè)流程就結(jié)束了。注意這次的查詢是根據(jù)當(dāng)前流程的 ID 查詢的,一個(gè)流程就是一條線,這條線上有很多 Task,我們可以從 Task 中獲取到流程的 ID。
好啦,接口就寫好了。
當(dāng)然,這里還涉及到兩個(gè)自定義的邏輯,就是批準(zhǔn)或者拒絕之后的自定義邏輯,這個(gè)其實(shí)很好寫,如下:
public?class?Approve?implements?JavaDelegate?{
????@Override
????public?void?execute(DelegateExecution?execution)?{
????????System.out.println("申請(qǐng)通過:"+execution.getVariables());
????}
}
我們自定義類實(shí)現(xiàn) JavaDelegate 接口即可,然后我們?cè)?execute 方法中做自己想要做的事情即可,execution 中有這個(gè)流程中的所有變量。我們可以在這里發(fā)郵件、發(fā)短信等等。Reject 的定義方式也是類似的。這些自定義類寫好之后,將來配置到流程圖中即可(可查看上文的流程圖)。
最后再來看看前端提交方法就簡(jiǎn)單了(頁(yè)面源碼上文已經(jīng)列出):
approveOrReject(taskId,?approve,name)?{
????let?_this?=?this;
????axios.post('/handler',?{taskId:?taskId,?approve:?approve,name:name})
????????.then(function?(response)?{
????????????_this.initTasks();
????????})
????????.catch(function?(error)?{
????????????console.log(error);
????????});
}
這就一個(gè)普通的 Ajax 請(qǐng)求,批準(zhǔn)的話第二個(gè)參數(shù)就為 true,拒絕的話第二個(gè)參數(shù)就為 false。
7. 結(jié)果查詢
最后,每個(gè)用戶都可以查看自己曾經(jīng)的申請(qǐng)記錄。本來這個(gè)登錄之后就可以展示了,但是因?yàn)槲覀儧]有登錄,所以這里也是需要手動(dòng)輸入查詢的用戶,然后根據(jù)用戶名查詢這個(gè)用戶的歷史記錄,我們先來看查詢接口:
@GetMapping("/search")
public?RespBean?searchResult(String?name)?{
????return?askForLeaveService.searchResult(name);
}
參數(shù)就是要查詢的用戶名。具體的查詢流程如下:
public?RespBean?searchResult(String?name)?{
????List<HistoryInfo>?historyInfos?=?new?ArrayList<>();
????List<HistoricProcessInstance>?historicProcessInstances?=?historyService.createHistoricProcessInstanceQuery().processInstanceBusinessKey(name).finished().orderByProcessInstanceEndTime().desc().list();
????for?(HistoricProcessInstance?historicProcessInstance?:?historicProcessInstances)?{
????????HistoryInfo?historyInfo?=?new?HistoryInfo();
????????Date?startTime?=?historicProcessInstance.getStartTime();
????????Date?endTime?=?historicProcessInstance.getEndTime();
????????List<HistoricVariableInstance>?historicVariableInstances?=?historyService.createHistoricVariableInstanceQuery()
????????????????.processInstanceId(historicProcessInstance.getId())
????????????????.list();
????????for?(HistoricVariableInstance?historicVariableInstance?:?historicVariableInstances)?{
????????????String?variableName?=?historicVariableInstance.getVariableName();
????????????Object?value?=?historicVariableInstance.getValue();
????????????if?("reason".equals(variableName))?{
????????????????historyInfo.setReason((String)?value);
????????????}?else?if?("days".equals(variableName))?{
????????????????historyInfo.setDays(Integer.parseInt(value.toString()));
????????????}?else?if?("approved".equals(variableName))?{
????????????????historyInfo.setStatus((Boolean)?value);
????????????}?else?if?("name".equals(variableName))?{
????????????????historyInfo.setName((String)?value);
????????????}
????????}
????????historyInfo.setStartTime(startTime);
????????historyInfo.setEndTime(endTime);
????????historyInfos.add(historyInfo);
????}
????return?RespBean.ok("ok",?historyInfos);
}
- 我們當(dāng)時(shí)在開啟流程的時(shí)候,傳入了一個(gè)參數(shù) key,這里就是再次通過這個(gè) key,也就是用戶名去查詢歷史流程,查詢的時(shí)候還加上了 finished 方法,這個(gè)表示要查詢的流程必須是執(zhí)行完畢的流程,對(duì)于沒有執(zhí)行完畢的流程,這里不查詢,查完之后,按照流程最后的處理時(shí)間進(jìn)行排序。
- 遍歷第一步的查詢結(jié)果,從 HistoricProcessInstance 中提取出每一個(gè)流程的詳細(xì)信息,并存入到集合中,并最終返回。
- 這里涉及到兩個(gè)歷史數(shù)據(jù)查詢,createHistoricProcessInstanceQuery 用來查詢歷史流程,而 createHistoricVariableInstanceQuery 則主要是用來查詢流程變量的。
最后,前端通過表格展示這個(gè)數(shù)據(jù)即可:
<!DOCTYPE?html>
<html?lang="en">
<head>
????<meta?charset="UTF-8">
????<title>Title</title>
????<script?src="https://unpkg.com/axios/dist/axios.min.js"></script>
????<!--?Import?style?-->
????<link?rel="stylesheet"? rel="external nofollow" rel="external nofollow" rel="external nofollow" />
????<script?src="https://unpkg.com/vue@3"></script>
????<!--?Import?component?library?-->
????<script?src="http://unpkg.com/element-plus"></script>
</head>
<body>
<div?id="app">
????<div?style="margin-top:?50px">
????????<el-input?v-model="name"?style="width:?300px"?placeholder="請(qǐng)輸入用戶名"></el-input>
????????<el-button?type="primary"?@click="search">查詢</el-button>
????</div>
????<div>
????????<el-table?border?strip?:data="historyInfos">
????????????<el-table-column?prop="name"?label="姓名"></el-table-column>
????????????<el-table-column?prop="startTime"?label="提交時(shí)間"></el-table-column>
????????????<el-table-column?prop="endTime"?label="審批時(shí)間"></el-table-column>
????????????<el-table-column?prop="reason"?label="事由"></el-table-column>
????????????<el-table-column?prop="days"?label="天數(shù)"></el-table-column>
????????????<el-table-column?label="狀態(tài)">
????????????????<template?#default="scope">
????????????????????<el-tag?type="success"?v-if="scope.row.status">已通過</el-tag>
????????????????????<el-tag?type="danger"?v-else>已拒絕</el-tag>
????????????????</template>
????????????</el-table-column>
????????</el-table>
????</div>
</div>
<script>
????Vue.createApp(
????????{
????????????data()?{
????????????????return?{
????????????????????historyInfos:?[],
????????????????????name:?'zhangsan'
????????????????}
????????????},
????????????methods:?{
????????????????search()?{
????????????????????let?_this?=?this;
????????????????????axios.get('/search?name='?+?this.name)
????????????????????????.then(function?(response)?{
????????????????????????????if?(response.data.status?==?200)?{
????????????????????????????????_this.historyInfos=response.data.data;
????????????????????????????}?else?{
????????????????????????????????_this.$message.error(response.data.msg);
????????????????????????????}
????????????????????????})
????????????????????????.catch(function?(error)?{
????????????????????????????console.log(error);
????????????????????????});
????????????????}
????????}
????).use(ElementPlus).mount('#app')
</script>
</body>
</html>
這個(gè)都是一些常規(guī)操作,我就不多說了,最終展示效果如下:

以上就是SpringBoot+Vue+Flowable模擬實(shí)現(xiàn)請(qǐng)假審批流程的詳細(xì)內(nèi)容,更多關(guān)于SpringBoot Vue Flowable請(qǐng)假審批流程的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
- Springboot整合Flowable6.x導(dǎo)出bpmn20的步驟詳解
- springBoot集成flowable的流程解析
- springboot整合flowable框架入門步驟
- Springboot+Flowable?快速實(shí)現(xiàn)工作流的開發(fā)流程
- Springboot結(jié)合Flowable實(shí)現(xiàn)工作流開發(fā)
- 基于springboot的flowable工作流實(shí)戰(zhàn)流程分析
- springboot2.5.2與 flowable6.6.0整合流程引擎應(yīng)用分析
- springboot開發(fā)flowable定時(shí)任務(wù)問題
相關(guān)文章
使用springmvc運(yùn)行流程分析,手寫spring框架嘗試
這篇文章主要介紹了使用springmvc運(yùn)行流程分析,手寫spring框架嘗試,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2021-10-10
Activiti進(jìn)階之組任務(wù)實(shí)現(xiàn)示例詳解
這篇文章主要為大家介紹了Activiti進(jìn)階之組任務(wù)實(shí)現(xiàn)示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2022-08-08
關(guān)于@PropertySource配置的用法解析
這篇文章主要介紹了關(guān)于@PropertySource配置的用法解析,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2022-03-03
Java中將 int[] 數(shù)組 轉(zhuǎn)換為 List分享
這篇文章主要介紹了Java中將 int[] 數(shù)組 轉(zhuǎn)換為 List分享的相關(guān)資料,需要的朋友可以參考下2022-12-12
Spring Boot 使用 Swagger 構(gòu)建 RestAPI 接口文檔
這篇文章主要介紹了Spring Boot 使用 Swagger 構(gòu)建 RestAPI 接口文檔,幫助大家更好的理解和使用Spring Boot框架,感興趣的朋友可以了解下2020-10-10
使用@Transactional 設(shè)置嵌套事務(wù)不回滾
這篇文章主要介紹了使用@Transactional 設(shè)置嵌套事務(wù)不回滾問題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2021-07-07
java開發(fā)分布式服務(wù)框架Dubbo暴露服務(wù)過程詳解
這篇文章主要為大家介紹了java開發(fā)分布式服務(wù)框架Dubbo暴露服務(wù)的過程詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步2021-11-11

