前言&源碼
為了更加熟悉activiti工作流的使用和實(shí)戰(zhàn)而改造的項(xiàng)目,歡迎大家參考和提出問題建議一起學(xué)習(xí)~
源碼gitee倉庫地址:Yuzaki-NASA / Activiti7_test_car_rbac
master分支是穩(wěn)定版,dev分支是后來加了個(gè)新的并行審核流程和客戶管理,個(gè)人測(cè)了多遍沒啥問題,建議拉dev的代碼。
sql文件在caro2o-business下的resources/sql里,啟動(dòng)項(xiàng)目前記得先添加一下sql
原模板源碼gitee倉庫地址:Yuzaki-NASA / activiti7-caro2o-template
這個(gè)是參考的模板,功能除去一些被我優(yōu)化過的地方以外大多一致,還寫了很多注釋,便于對(duì)照理解學(xué)習(xí)
項(xiàng)目參考圖:
- 養(yǎng)修預(yù)約-服務(wù)項(xiàng)crud(很多頁面都類似,就不一一例舉了)
- 養(yǎng)修預(yù)約-結(jié)算單明細(xì)
- 流程管理-流程定義明細(xì)
- 套餐審核-我的已辦
- 套餐審核-我的待辦-進(jìn)度查看
項(xiàng)目總結(jié):
(源碼中的caro2o-ui下的src/assets路徑下也有總結(jié)的pdf文件)
e店邦O2O平臺(tái)項(xiàng)目總結(jié)
一、springboot
1.1、springboot自動(dòng)配置原理
用自己的大白話來總結(jié)就是:
自動(dòng)配置簡(jiǎn)單來說就是自動(dòng)去把第三方組件的Bean裝載到IOC容器中,不需要開發(fā)人員再去寫B(tài)ean相關(guān)的配置。在SpringBoot應(yīng)用里只需要在啟動(dòng)類上加@SpringBootApplication注解就可以實(shí)現(xiàn)自動(dòng)配置。
@SpringBootApplication注解是一個(gè)復(fù)合注解,真正去實(shí)現(xiàn)自動(dòng)配置的注解是它里面的@EnableAutoConfiguration這樣一個(gè)注解。自動(dòng)配置的實(shí)現(xiàn)主要依靠三個(gè)核心的關(guān)鍵技術(shù):
①、第一個(gè),引入Starter
啟動(dòng)依賴組件的時(shí)候,這個(gè)組件里必須要包含一個(gè)@Configuration配置類,而在這個(gè)配置類里面我們需要通過@Bean這個(gè)注解去聲明需要裝配到IOC容器里面的Bean對(duì)象。
②、第二個(gè),這個(gè)配置類是放在第三方的jar包里面,然后通過SpringBoot中約定優(yōu)于配置的這樣一個(gè)理念,使用Spring里擁有的SpringFactoriesLoader(Spring的一種加載方式,在Spring的底層非常常見)去把這個(gè)配置類的全限定名(路徑)放在classpath:/META-INF/spring.factories文件里面,這樣SpringBoot就可以知道第三方j(luò)ar包里面這個(gè)配置類的位置。
約定優(yōu)于配置理念:
維基百科解釋如下:
約定優(yōu)于配置(convention over configuration),也稱作按約定編程,是一種軟件設(shè)計(jì)范式,旨在減少軟件開發(fā)人員需做出決定的數(shù)量,活得簡(jiǎn)單的好處,而又不失靈活性。
本質(zhì)上是說,開發(fā)人員僅需要規(guī)定應(yīng)用中不符約定的部分,例如,如果模型中有個(gè)名為 Sale 的類,那么數(shù)據(jù)庫中對(duì)應(yīng)的表就會(huì)默認(rèn)命名為 sales。只有偏離這一約定時(shí),例如將該表命名為“products_sold”,才需寫有關(guān)這個(gè)名字的配置。
如果您所用工具的約定與你的期望相符,便可省去配置;反之,你可以配置來達(dá)到你所期待的方式。
/META-INF/spring.factories文件以key-value鍵值對(duì)作為內(nèi)容,其中有一個(gè)Key為EnableAutoConfiguration且Value為各個(gè)第三方j(luò)ar包的Configuration全限定名,而@EnableAutoConfiguration注解就是通過這里自動(dòng)加載到所有符合要求的第三方依賴。例如我們項(xiàng)目中用到的Avitiviti依賴包
③、第三個(gè),SpringBoot拿到所有第三方j(luò)ar包里面聲明的配置類以后,再通過Spring提供的ImportSelector這樣一個(gè)接口來實(shí)現(xiàn)對(duì)這些配置類的動(dòng)態(tài)加載,從而去完成自動(dòng)配置這樣一個(gè)動(dòng)作。
在我看來,Springboot是約定優(yōu)于配置這一理念下的一個(gè)產(chǎn)物,所以在很多地方都能看到這一類的思想,它的出現(xiàn)讓開發(fā)人員可以更加聚焦(集中注意)在業(yè)務(wù)代碼的編寫上,而不需要去關(guān)心和業(yè)務(wù)無關(guān)的配置。
拓展:其實(shí)自動(dòng)配置的思想在SpringFramework3.x版本里面的@Enable注解就已經(jīng)有了實(shí)現(xiàn)的一個(gè)雛形,@Enable注解是一個(gè)模塊驅(qū)動(dòng)的意思,也就是說我們只需要增加@Enable注解就能自動(dòng)打開某個(gè)功能,而不需要針對(duì)這個(gè)功能去做Bean的配置,@Enable注解的底層也是去幫我們自動(dòng)去完成這樣一個(gè)模塊相關(guān)Bean的注入的,然后基于這一理念有了后來的SpringBoot自動(dòng)配置。
1.2、springboot優(yōu)缺點(diǎn)
優(yōu)點(diǎn):
- 創(chuàng)建獨(dú)立Spring應(yīng)用
- 內(nèi)嵌web服務(wù)器(如tomcat等)
- 自動(dòng)starter依賴,簡(jiǎn)化構(gòu)建配置
- 自動(dòng)配置Spring以及第三方功能
- 提供生產(chǎn)級(jí)別的監(jiān)控、健康檢查以及外部?jī)?yōu)化配置
- 無代碼生成、無需編寫XML
缺點(diǎn):
- 迭代快
- 封裝太深,內(nèi)部原理復(fù)雜,不容易精通
1.3、springboot注解
springboot常見注解可以參考這個(gè):https://zhuanlan.zhihu.com/p/593053050?utm_id=0
來說一下caro2o項(xiàng)目中一些比較常用和重要的注解:
-
@RestController:
- @RestController是@Controller和 @ResponseBody 的結(jié)合體,兩個(gè)標(biāo)注合并起來的作用。@RestController類中的所有方法只能返回String、Object、Json等實(shí)體對(duì)象,不能跳轉(zhuǎn)到模版頁面。
- 如果只是使用@RestController注解Controller,則Controller中的方法無法返回jsp頁面,或者h(yuǎn)tml,配置的視圖解析器 InternalResourceViewResolver不起作用,返回的內(nèi)容就是Return 里的內(nèi)容。
- 如果需要返回到指定頁面,則需要用 @Controller配合視圖解析器InternalResourceViewResolver才行。如果需要返回JSON,XML或自定義mediaType內(nèi)容到頁面,則需要在對(duì)應(yīng)的方法上加上@ResponseBody注解。
-
@PathVariable:
-
@PathVariable 映射 URL 綁定的占位符,通過 @PathVariable 可以將 URL 中占位符參數(shù)綁定到控制器處理方法的入?yún)⒅校篣RL 中的 {xxx} 占位符可以通過@PathVariable(“xxx”) 綁定到操作方法的入?yún)⒅?。單個(gè)變量或數(shù)組都可以。
-
/** * 獲取養(yǎng)修信息預(yù)約詳細(xì)信息 */ @PreAuthorize("@ss.hasPermi('business:appointment:query')") @GetMapping(value = "/{id}") public AjaxResult getInfo(@PathVariable("id") Long id) { return AjaxResult.success(busAppointmentService.selectBusAppointmentById(id)); } /** * 刪除養(yǎng)修信息預(yù)約(真刪除) */ @PreAuthorize("@ss.hasPermi('business:appointment:remove')") @Log(title = "養(yǎng)修信息預(yù)約", businessType = BusinessType.DELETE) @DeleteMapping("/{ids}") public AjaxResult remove(@PathVariable Long[] ids) { return toAjax(busAppointmentService.deleteBusAppointmentByIds(ids)); } /** * 刪除養(yǎng)修信息預(yù)約(假刪除) */ @PreAuthorize("@ss.hasPermi('business:appointment:remove')") @Log(title = "刪除養(yǎng)修信息預(yù)約", businessType = BusinessType.UPDATE) @PutMapping("/delete/{ids}") public AjaxResult updateDel(@PathVariable Long[] ids) { busAppointmentService.updateDel(ids); return AjaxResult.success(); } /** * 刪除養(yǎng)修信息預(yù)約(假刪除) */ @PreAuthorize("@ss.hasPermi('business:appointment:generate')") @Log(title = "養(yǎng)修信息預(yù)約", businessType = BusinessType.INSERT) @PostMapping("/generate/{appointmentId}") public AjaxResult generate(@PathVariable Long appointmentId) { return AjaxResult.success(busAppointmentService.generate(appointmentId)); }
-
-
@RequestBody:Controller中接收的入?yún)⑹菍?duì)象的Json格式時(shí)貼,下面代碼塊中的POST和PUT方法都有用到,不多贅述了。
-
@Validated:是Spring Validation框架提供的參數(shù)驗(yàn)證功能,貼在controller類里方法的入?yún)⑶伴_啟參數(shù)校驗(yàn)功能,比較常貼在POST和PUT方法上:
-
/** * 新增養(yǎng)修信息預(yù)約 */ @PreAuthorize("@ss.hasPermi('business:appointment:add')") @Log(title = "養(yǎng)修信息預(yù)約", businessType = BusinessType.INSERT) @PostMapping public AjaxResult add(@Validated @RequestBody BusAppointment busAppointment) { return toAjax(busAppointmentService.insertBusAppointment(busAppointment)); } /** * 修改養(yǎng)修信息預(yù)約 */ @PreAuthorize("@ss.hasPermi('business:appointment:edit')") @Log(title = "養(yǎng)修信息預(yù)約", businessType = BusinessType.UPDATE) @PutMapping public AjaxResult edit(@Validated @RequestBody BusAppointment busAppointment) { return toAjax(busAppointmentService.updateBusAppointment(busAppointment)); }
-
在domain的類里的get方法上貼相關(guān)校驗(yàn)注解,如@NotBlank(貼在字符串成員上,表示不能為空或空字符串)、@NotNull(不能為Null)、@Size(限制字符串長度)等等
-
@NotBlank(message = "客戶姓名不能為空") @Size(min = 0, max = 64, message = "客戶姓名長度不能超過64個(gè)字符") public String getCustomerName() { return customerName; } public void setCustomerPhone(String customerPhone) { this.customerPhone = customerPhone; }
@NotNull(message = "預(yù)約時(shí)間不能為空") public Date getAppointmentTime() { return appointmentTime; } public void setActualArrivalTime(Date actualArrivalTime) { this.actualArrivalTime = actualArrivalTime; }
-
-
-
@JsonFormat:在Jackson中定義的一個(gè)注解,是一個(gè)時(shí)間格式化注解。此注解用于屬性上,作用是把DATE類型的數(shù)據(jù)轉(zhuǎn)化成為我們想要的格式。
-
// 例如 @JsonFormat(pattern = "yyyy-MM-dd") @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") @JsonFormat(pattern = "yyyy年MM月dd日 HH時(shí)mm分ss秒")
-
-
@Param:在Mapper類中使用,這個(gè)注解是為SQL語句中參數(shù)賦值而服務(wù)的。
- @Param的作用就是給參數(shù)命名,比如在mapper里面某方法A(int id),當(dāng)添加注解后A(@Param(“userId”) int id),也就是說外部想要取出傳入的id值,只需要取它的參數(shù)名userId就可以了。將參數(shù)值傳如SQL語句中,通過#{userId}進(jìn)行取值給SQL的參數(shù)賦值。
- Mapper類中的方法參數(shù)不為基本數(shù)據(jù)類型或者有多個(gè)參數(shù)時(shí),使用該注解。
-
@Transactional:在service層開啟事務(wù),防止執(zhí)行途中出錯(cuò)而造成的數(shù)據(jù)混亂。
-
其余還有一些若依自己封裝的注解類似@PreAuthorize(權(quán)限)、@Log(打印日志)、@Excel(導(dǎo)出文件相關(guān)注解)就不展開說明了,不同項(xiàng)目會(huì)封裝不同的自定義注解,這些都需要自己去研究其作用與實(shí)現(xiàn)。
二、rbac
2.1、概括
RBAC是一種基于角色實(shí)現(xiàn)訪問控制的權(quán)限管理機(jī)制,通過定義角色和權(quán)限、用戶和角色、角色和角色之間的關(guān)系,實(shí)現(xiàn)多層次、細(xì)粒度、可復(fù)用的權(quán)限管理系統(tǒng)。
基本模型有三個(gè)元素:用戶、角色和權(quán)限。模型設(shè)計(jì)基于“多對(duì)多”原則,即多個(gè)用戶可以具有相同的角色,一個(gè)用戶可以具有多個(gè)角色。同樣,您可以將同一權(quán)限分配給多個(gè)角色,也可以將同一角色分配給多個(gè)權(quán)限。
更詳細(xì)解釋見:https://zhuanlan.zhihu.com/p/513142061
2.2、三個(gè)元素的理解
用戶信息需要確保安全性,不能泄露。
角色關(guān)系到用戶和權(quán)限,需要設(shè)計(jì)合理。
權(quán)限字段應(yīng)在前端與后端都有校驗(yàn):前端通過菜單或按鈕的顯示與否體現(xiàn)對(duì)不同角色權(quán)限的控制,但前端可能會(huì)被用戶惡意修改視圖去顯示出因沒有權(quán)限而過濾掉的功能菜單或按鈕,此時(shí)在后端也要增加權(quán)限校驗(yàn),在該用戶沒擁有該權(quán)限時(shí),發(fā)起的請(qǐng)求返回403錯(cuò)誤,彈框提示該用戶缺少對(duì)應(yīng)權(quán)限。
三、數(shù)據(jù)字典
3.1、概括與作用
數(shù)據(jù)字典是整個(gè)平臺(tái)中數(shù)據(jù)描述的有效機(jī)制。通過界面進(jìn)行可視化的操作和維護(hù),能快速錄入和修改平臺(tái)上統(tǒng)一的字典數(shù)據(jù)。有效提高了數(shù)據(jù)的重復(fù)利用率和產(chǎn)品、項(xiàng)目的開發(fā)效率。整個(gè)數(shù)據(jù)字典數(shù)據(jù)為框架平臺(tái)所共享,用戶可以更好地對(duì)系統(tǒng)進(jìn)行自定義管理,以滿足自己的個(gè)性化需求。
3.2、怎么設(shè)計(jì)
參考:https://www.python100.com/html/82651.html
3.3、若依中使用字典
1、js中引入方法
import { getDicts } from "@/api/system/dict/data";
2、加載數(shù)據(jù)字典
export default {
data() {
return {
xxxxxOptions: [],
.....
...
created() {
this.getDicts("字典類型").then(response => {
this.xxxxxOptions = response.data;
});
},
3、讀取數(shù)據(jù)字典
<uni-data-select
v-for="dict in xxxxxOptions"
:key="dict.dictValue"
:text="dict.dictLabel"
:value="dict.dictValue"
/>
四、工作流——Activiti7
4.1、概念
沒有?作流引擎之前如果要控制業(yè)務(wù)流程我們可能通過改變某個(gè)字段的狀態(tài)來實(shí)現(xiàn),這會(huì)帶來?旦我們流程發(fā)?變化的時(shí)候我們就需要去同步修改代碼。??流程引擎它??內(nèi)置可25張表,我們只要讀取它??的表就可以了,與表對(duì)應(yīng)的它還提供?系列可操作表的接?。核??個(gè)類是ProcessEngine,通過它能獲取?系列的service接?。
4.2、如何使?
部署?作流引擎,其實(shí)就是jar包api
流程定義:.bpmn?件,是?個(gè)xml?件定義了流程信息
流程定義部署
啟動(dòng)?個(gè)流程實(shí)例
?戶查詢代辦任務(wù),?個(gè)instance有多個(gè)task
?戶辦理任務(wù)
流程結(jié)束
4.3、優(yōu)點(diǎn)缺點(diǎn)
優(yōu)點(diǎn)
1、 最大的優(yōu)點(diǎn)就是免費(fèi)開源,這也是很多人選擇的原因
2、 小項(xiàng)目中應(yīng)用簡(jiǎn)單的串行并行流轉(zhuǎn)基本能滿足需求。
缺點(diǎn)
1、節(jié)點(diǎn)定義概念不同
2、缺乏可“追溯”性
3、擴(kuò)展需要與很多的Event來實(shí)現(xiàn)
4、二次開發(fā)難度大,門檻高
4.4、常用操作步驟
【Deployment】 (創(chuàng)建并部署一個(gè)新的流程定義)
獲取方式:
repositoryService.createDeployment().deploy();
對(duì)應(yīng)的表:act_re_deployment
用于存儲(chǔ)流程部署的相關(guān)信息。該表記錄了每個(gè)流程部署的唯一標(biāo)識(shí)符(ID)、名稱(NAME)、類別(CATEGORY)、租戶標(biāo)識(shí)符(TENANT_ID)、鍵(KEY)以及部署時(shí)間(DEPLOY_TIME)等信息。
核心字段:
id、name、deployementTime、category、key、tenantid
【ProcessDefinition】 (查詢流程定義對(duì)象)
獲取方式:
repositoryService.createProcessDefinitionQuery()
.deploymentId(“流程部署id”)
.processDefinitionId(“流程定義id”)
.processDefinitionKey(“流程定義的key”)
.processDefinitionName(“流程定義的name”)
.singleResult();
對(duì)應(yīng)的表:act_re_procdef
用于存儲(chǔ)流程定義的相關(guān)信息。該表記錄了每個(gè)流程定義的ID、名稱、版本號(hào)、資源文件和圖片文件等信息。
通過查詢act_re_procdef表,您可以獲得以下信息:
- 流程定義ID(id):這是每個(gè)流程定義的唯一標(biāo)識(shí)符。
- 流程定義名稱(name):這是流程定義的名稱。
- 版本號(hào)(version):這是流程定義的版本號(hào)。
- 資源文件(resource_name):這是與流程定義關(guān)聯(lián)的資源文件名稱。
- 圖片文件(image_name):這是與流程定義關(guān)聯(lián)的圖片文件名稱。
act_re_procdef表與act_ge_bytearray表之間存在多對(duì)一的關(guān)系,即一個(gè)流程定義對(duì)應(yīng)多個(gè)資源文件和圖片文件。在Activiti中,每個(gè)流程定義都會(huì)在act_re_procdef表中增加一條記錄,同時(shí)也會(huì)在act_ge_bytearray表中存在相應(yīng)的資源記錄。
通過查詢act_re_procdef表,您可以了解流程定義的相關(guān)信息,包括其名稱、版本號(hào)以及與之關(guān)聯(lián)的資源文件和圖片文件。這對(duì)于管理和維護(hù)業(yè)務(wù)流程非常有用。
核心字段:
id、name、key、description、resourceName、deploymentId、tenantId、engineVersion
【ProcessInstance】 (查詢流程實(shí)例對(duì)象)
獲取方式:
方式1:runtimeService.startProcessInstanceByKey(processDefinitionKey);
方式2:
runtimeService.createProcessInstanceQuery()
.processInstanceId(“流程實(shí)例id”)
.processDefinitionId(“流程定義id”)
.processDefinitionKey(“流程定義的key”)
.deploymentId(“流程部署id”)
.processDefinitionName(“流程定義的name”)
.processInstanceBusinessKey(“流程實(shí)例業(yè)務(wù)key”)
.singleResult();
對(duì)應(yīng)的表:act_hi_procinst
用于存儲(chǔ)流程實(shí)例的歷史信息。該表記錄了每個(gè)流程實(shí)例的ID、名稱、業(yè)務(wù)鍵、狀態(tài)以及相關(guān)的其他信息。
通過查詢act_hi_procinst表,您可以獲得以下信息:
- 流程實(shí)例ID(proc_id):這是每個(gè)流程實(shí)例的唯一標(biāo)識(shí)符。
- 流程實(shí)例名稱(proc_name):這是流程實(shí)例的名稱。
- 業(yè)務(wù)鍵(business_key):這是與流程實(shí)例關(guān)聯(lián)的業(yè)務(wù)鍵,通常用于標(biāo)識(shí)業(yè)務(wù)流程的唯一性。
- 狀態(tài)(state):這是流程實(shí)例的狀態(tài),例如已啟動(dòng)、已完成、已暫停等。
- 其他相關(guān)信息:act_hi_procinst表還包含其他與流程實(shí)例相關(guān)的信息,例如創(chuàng)建時(shí)間、更新時(shí)間、所屬組織等。
通過查詢act_hi_procinst表,您可以了解流程實(shí)例的歷史記錄,包括其狀態(tài)變化、執(zhí)行路徑以及相關(guān)的其他信息。這對(duì)于分析和優(yōu)化業(yè)務(wù)流程非常有用。
核心字段:
name、businessKey、deploymentId、descriptionName、processDefinitionId、processDefinitionKey、processDefinitionName、startTime、startTimeUser、tenantId、activityId、 processInstanceId
【Task】 (查詢?nèi)蝿?wù)信息)
獲取方式:
taskService.createTaskQuery()
.taskId(“taskId”)
.taskAssignee(“節(jié)點(diǎn)任務(wù)負(fù)責(zé)人”)
.taskCandidateUser(“taskCandidateUser”)
.taskDefinitionKey(“taskDefinitionKey”)
.processDefinitionKey(“流程定義的key”)
.processInstanceId(“流程實(shí)例id”)
.deploymentId(“流程部署id”)
.singleResult();
對(duì)應(yīng)的表:act_ru_task
用于存儲(chǔ)正在執(zhí)行的任務(wù)信息。該表記錄了每個(gè)任務(wù)的ID、名稱、狀態(tài)、執(zhí)行路徑等信息。
通過查詢act_ru_task表,您可以獲得以下信息:
- 任務(wù)ID(task_id):這是每個(gè)任務(wù)的唯一標(biāo)識(shí)符。
- 任務(wù)名稱(name):這是任務(wù)的名稱。
- 任務(wù)狀態(tài)(status):這是任務(wù)的狀態(tài),例如待辦、已完成、正在進(jìn)行中等。
- 執(zhí)行路徑(execution_id):這是與任務(wù)關(guān)聯(lián)的流程實(shí)例的執(zhí)行路徑信息。
- 其他相關(guān)信息:act_ru_task表還包含其他與任務(wù)相關(guān)的信息,例如創(chuàng)建時(shí)間、更新時(shí)間、任務(wù)節(jié)點(diǎn)類型等。
act_ru_task表與act_ge_bytearray表之間存在多對(duì)一的關(guān)系,即一個(gè)任務(wù)對(duì)應(yīng)多個(gè)附件文件。在Activiti中,每個(gè)任務(wù)都會(huì)在act_ru_task表中增加一條記錄,同時(shí)也會(huì)在act_ge_bytearray表中存在相應(yīng)的附件記錄。
通過查詢act_ru_task表,您可以了解正在執(zhí)行的任務(wù)的相關(guān)信息,包括其ID、名稱、狀態(tài)以及執(zhí)行路徑等。這對(duì)于跟蹤和管理業(yè)務(wù)流程中的任務(wù)非常有用。
核心字段:
name、description、priority、owner、assignee、delegationState、formKey、parentTaskId、
processInstanceId、executionId、processDefinitionId、processVariables
【HistoricActivityInstance】 (查詢歷史活動(dòng)實(shí)例信息)
獲取方式:
historyService.createHistoricActivityInstanceQuery()
.processDefinitionId(“流程定義id”)
.taskAssignee(“節(jié)點(diǎn)任務(wù)負(fù)責(zé)人”)
.processInstanceId(“流程實(shí)例id”)
.singleResult();
對(duì)應(yīng)的表:act_hi_actinst
是一個(gè)歷史節(jié)點(diǎn)表,用于存儲(chǔ)歷史流程實(shí)例的信息。該表記錄了每個(gè)歷史流程實(shí)例的ID、名稱、業(yè)務(wù)鍵、狀態(tài)以及相關(guān)的其他信息,包括開始時(shí)間、結(jié)束時(shí)間等。通過查詢 act_hi_actinst 表,您可以了解已經(jīng)執(zhí)行過的流程實(shí)例的歷史記錄,例如流程的執(zhí)行路徑、各個(gè)節(jié)點(diǎn)的執(zhí)行時(shí)間等信息。這對(duì)于分析和優(yōu)化業(yè)務(wù)流程非常有用,可以幫助企業(yè)更好地了解業(yè)務(wù)流程的執(zhí)行情況,從而進(jìn)行改進(jìn)和優(yōu)化。
核心字段:
id、activityId、activityName、activityType、processDefinitionId、processInstanceId、executionId、taskId、assignee、startTime、endTime、durationInMilli、tenantId
【Execution】(查詢執(zhí)行流數(shù)據(jù))
獲取方式:
runtimeService.createExecutionQuery()
.processDefinitionKey(“流程定義的key”)
.executionId(“executionId”)
.processDefinitionId(“流程定義id”)
.processInstanceId(“流程實(shí)例id”)
.processDefinitionKey(“流程定義的key”)
.singleResult();
對(duì)應(yīng)的表:act_ru_execution
是存儲(chǔ)運(yùn)行時(shí)數(shù)據(jù)的表,主要包含執(zhí)行過程中的活動(dòng)、任務(wù)、變量等數(shù)據(jù)。該表記錄了每個(gè)流程實(shí)例的執(zhí)行路徑信息,例如當(dāng)前執(zhí)行到哪個(gè)流程節(jié)點(diǎn)、哪些分支已經(jīng)被激活等。通過查詢 act_ru_execution 表,可以獲取流程實(shí)例的實(shí)時(shí)運(yùn)行狀態(tài)信息,例如哪個(gè)任務(wù)正在由哪個(gè)用戶執(zhí)行、執(zhí)行到哪個(gè)節(jié)點(diǎn)等。這對(duì)于跟蹤和管理業(yè)務(wù)流程中的實(shí)例非常有用。
核心字段:
id、activityId、processInstanceId、name、description
【IdentityLink】(查詢身份與流程數(shù)據(jù)的綁定關(guān)系)
獲取方式:
方式1:runtimeService.getIdentityLinksForProcessInstance(processInstanceId)
方式2:repositoryService.getIdentityLinksForProcessDefinition(ProcessDefinitionId)
方式3:taskService.getIdentityLinksForTask(taskId)
對(duì)應(yīng)的表:act_ru_identitylink
存儲(chǔ)了用戶或用戶組與流程數(shù)據(jù)之間的綁定關(guān)系。該表記錄了用戶或用戶組與流程實(shí)例、流程任務(wù)等數(shù)據(jù)的關(guān)聯(lián)信息。通過查詢 act_ru_identitylink 表,可以獲取用戶或用戶組與流程實(shí)例、流程任務(wù)等數(shù)據(jù)的綁定關(guān)系,例如哪個(gè)用戶或用戶組執(zhí)行了哪個(gè)流程任務(wù)、哪些流程任務(wù)被指定給了哪些用戶或用戶組等。這對(duì)于了解業(yè)務(wù)流程的執(zhí)行情況、進(jìn)行權(quán)限管理和任務(wù)分配等操作非常有用。
核心字段:
type、userId、taskId、processDefinitionId、processInstanceId
4.5、項(xiàng)目怎么用,怎么設(shè)計(jì)表
4.5.1、流程定義明細(xì)模塊:
-
設(shè)置一個(gè)流程管理模塊,數(shù)據(jù)庫創(chuàng)建一張流程定義明細(xì)表bus_bpmn_info和與之對(duì)應(yīng)的查詢頁面,表中要有processKey和version這兩個(gè)字段,在該頁面增加一個(gè)流程文件部署功能,需要選擇審核類型、上傳bpmn流程文件、添加備注(描述信息,可不填),然后通過repositoryService服務(wù)的deploy部署一個(gè)新流程,部署后就可以在act_re_procdef表里查到剛才部署的流程定義了。將流程定義的所需信息存放到我們自己建的bpmnInfo流程定義明細(xì)表中,在查詢頁面顯示出來我們新建過的流程定義。
@Override public void deploy(DeployVO vo) throws IOException { //參數(shù)判斷--文件大小--文件后綴 if(vo == null){ throw new ServiceException("參數(shù)異常"); } MultipartFile file = vo.getFile(); if(file == null || file.getSize() == 0){ throw new ServiceException("必需選擇一個(gè)流程文件"); } String ext = FileUploadUtils.getExtension(file); if(!"bpmn".equalsIgnoreCase(ext)){ throw new ServiceException("文件格式必須為 bpmn 格式"); } //流程部署 Deployment deployment = repositoryService.createDeployment() .addInputStream(vo.getBpmnLabel(), file.getInputStream()) .deploy(); //流程類-解下流程文件, 獲取流程文件所有信息封裝對(duì)象-ProcessDefinition---act_re_procdef ProcessDefinition processDefinition = repositoryService.createProcessDefinitionQuery() .deploymentId(deployment.getId()).singleResult(); //保存流程信息對(duì)象: BpmnInfo BpmnInfo bpmnInfo = new BpmnInfo(); bpmnInfo.setInfo(vo.getInfo()); bpmnInfo.setBpmnLabel(vo.getBpmnLabel()); bpmnInfo.setBpmnType(vo.getBpmnType()); bpmnInfo.setDeployTime(deployment.getDeploymentTime()); bpmnInfo.setVersion(processDefinition.getVersion()); bpmnInfo.setProcessDefinitionKey(processDefinition.getKey()); bpmnInfoMapper.insertBpmnInfo(bpmnInfo); }
-
在流程定義明細(xì)頁面中可以查看流程文件或流程圖,具體實(shí)現(xiàn)代碼:
@Override public InputStream getResource(String type, Long id) { BpmnInfo bpmnInfo = bpmnInfoMapper.selectBpmnInfoById(id); if (bpmnInfo==null||!("xml".equalsIgnoreCase(type)||"png".equalsIgnoreCase(type))) { throw new ServiceException("參數(shù)異?;蛭募袷疆惓?); } InputStream inputStream = null; ProcessDefinition processDefinition = repositoryService.createProcessDefinitionQuery() .processDefinitionKey(bpmnInfo.getProcessDefinitionKey()) .processDefinitionVersion(bpmnInfo.getVersion()) .singleResult(); if("xml".equalsIgnoreCase(type)){ inputStream = repositoryService .getResourceAsStream(processDefinition.getDeploymentId(), bpmnInfo.getBpmnLabel()); }else if("png".equalsIgnoreCase(type)){ DefaultProcessDiagramGenerator processDiagramGenerator = new DefaultProcessDiagramGenerator(); BpmnModel bpmnModel = repositoryService.getBpmnModel(processDefinition.getId()); /** * 第一個(gè)參數(shù): 流程定義模型 * 第二個(gè)參數(shù): 高亮節(jié)點(diǎn)集合 * 第三個(gè)參數(shù): 高亮連線集合 */ inputStream = processDiagramGenerator.generateDiagram(bpmnModel, Collections.emptyList(), Collections.emptyList(), "宋體", "宋體", "宋體"); } return inputStream; }
-
流程定義的撤銷:
/** * 批量撤銷流程定義明細(xì) * * @param ids 需要?jiǎng)h除的流程定義明細(xì)主鍵 * @return 結(jié)果 */ @Override public int deleteBpmnInfoByIds(Long[] ids) { if (ids==null||ids.length<1) { throw new ServiceException("參數(shù)異常"); } for (Long id : ids) { BpmnInfo bpmnInfo = bpmnInfoMapper.selectBpmnInfoById(id); if (bpmnInfo==null) { throw new ServiceException("參數(shù)異常"); } ProcessDefinition processDefinition = repositoryService.createProcessDefinitionQuery() .processDefinitionKey(bpmnInfo.getProcessDefinitionKey()) .processDefinitionVersion(bpmnInfo.getVersion()) .singleResult(); if (processDefinition==null) { throw new ServiceException("存在撤銷項(xiàng)參數(shù)異常"); } repositoryService.deleteDeployment(processDefinition.getDeploymentId(),true); bpmnInfoMapper.deleteBpmnInfoById(id); } return 1; }
4.5.2、發(fā)起/提交審核模塊:
? 發(fā)起審核的彈框里需要用戶從前端傳入所有所需的參數(shù),如審核人等。并且前端和后端都要添加狀態(tài)判斷——該業(yè)務(wù)處在什么狀態(tài)下才允許發(fā)起審核、該業(yè)務(wù)的某些條件是否影響審核節(jié)點(diǎn)等。
@Transactional
@Override
public void startAudit(AuditVO vo) {
//參數(shù)判斷
ServiceItem serviceItem = serviceItemMapper.selectServiceItemById(vo.getId());
if(serviceItem == null){
throw new ServiceException("參數(shù)異常");
}
if(!ServiceItem.CARPACKAGE_YES.equals(serviceItem.getCarPackage())){
throw new ServiceException("必須是套餐才允許審核");
}
if(!(ServiceItem.AUDITSTATUS_INIT.equals(serviceItem.getAuditStatus())
|| ServiceItem.AUDITSTATUS_REPLY.equals(serviceItem.getAuditStatus()))){
throw new ServiceException("必須是初始化或者審核拒絕狀態(tài)才可以進(jìn)行審核");
}
//審核信息保存
CarPackageAudit audit = new CarPackageAudit();
audit.setInfo(vo.getInfo());
audit.setServiceItemId(vo.getId());
audit.setServiceItemName(serviceItem.getName());
audit.setServiceItemInfo(serviceItem.getInfo());
audit.setServiceItemPrice(serviceItem.getDiscountPrice());
audit.setCreatorId(SecurityUtils.getUserId().toString());
audit.setStatus(CarPackageAudit.STATUS_IN_ROGRESS);
audit.setCreateTime(new Date());
carPackageAuditMapper.insertCarPackageAudit(audit);
BpmnInfo bpmnInfo = bpmnInfoMapper.selectLastByType(CarPackageAudit.FLOW_AUDIT_TYPE);
//流程啟動(dòng)---businesskey map(審核流程涉及參數(shù))
String businessKey = audit.getId().toString();
String processDefinitionKey = bpmnInfo.getProcessDefinitionKey();
Map<String, Object> map = new HashMap<>();
//設(shè)置節(jié)點(diǎn)審核人:財(cái)務(wù)
if(vo.getFinanceId() != null){
map.put("financeId", vo.getFinanceId());
}
//設(shè)置節(jié)點(diǎn)審核人:店長
if(vo.getShopOwnerId() != null){
map.put("shopOwnerId", vo.getShopOwnerId());
}
// 流程圖中不支持BigDecimal 校驗(yàn), 轉(zhuǎn)換long類型
map.put("disCountPrice", serviceItem.getDiscountPrice().longValue());
ProcessInstance instance = runtimeService.startProcessInstanceByKey(processDefinitionKey, businessKey, map);
//audit.setInstanceId(instance.getProcessInstanceId()); //流程實(shí)例id
audit.setInstanceId(instance.getId()); //流程實(shí)例id
carPackageAuditMapper.updateCarPackageAudit(audit);
//套餐項(xiàng)狀態(tài)--審核中
serviceItem.setAuditStatus(ServiceItem.AUDITSTATUS_AUDITING);
serviceItemMapper.updateServiceItem(serviceItem);
}
4.5.3、套餐審核信息模塊:
-
每一個(gè)開啟審核的業(yè)務(wù)對(duì)應(yīng)一個(gè)執(zhí)行的流程實(shí)例,我們要?jiǎng)?chuàng)建一個(gè)業(yè)務(wù)表bus_car_package_audit,表中要擁有關(guān)聯(lián)服務(wù)項(xiàng)表的字段service_item_id(為了頁面回顯效果也可以將name、info、price字段加上)、關(guān)聯(lián)流程實(shí)例的字段instance_id,還可以將關(guān)聯(lián)流程定義的字段process_key也加上,還有一些狀態(tài)status和創(chuàng)建者id和創(chuàng)建時(shí)間create_time等。
CREATE TABLE `bus_car_package_audit` ( `id` bigint NOT NULL AUTO_INCREMENT COMMENT '主鍵', `service_item_id` bigint DEFAULT NULL COMMENT '服務(wù)單項(xiàng)id', `service_item_name` varchar(100) DEFAULT NULL COMMENT '服務(wù)項(xiàng)名稱', `service_item_info` varchar(255) DEFAULT NULL COMMENT '服務(wù)單項(xiàng)備注', `service_item_price` decimal(10,2) DEFAULT NULL COMMENT '服務(wù)單項(xiàng)審核價(jià)格', `instance_id` varchar(64) DEFAULT NULL COMMENT '流程實(shí)例id', `creator_id` varchar(20) DEFAULT NULL COMMENT '創(chuàng)建者', `info` varchar(255) DEFAULT NULL COMMENT '備注', `status` int DEFAULT NULL COMMENT '狀態(tài)【審核中0/審核拒絕1/審核通過2/審核撤銷3】', `create_time` datetime DEFAULT NULL COMMENT '創(chuàng)建時(shí)間', PRIMARY KEY (`id`) USING BTREE ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb3 COMMENT='套餐審核';
-
審核歷史按鈕功能:查看該條審核的審批操作歷史。使用對(duì)應(yīng)的流程實(shí)例id通過historyService.createHistoricTaskInstanceQuery()可查詢到該實(shí)例的每一條審批節(jié)點(diǎn)記錄,
@Override public List<HistoryVO> queryHistory(Long instanceId) { if(instanceId == null){ throw new ServiceException("參數(shù)異常"); } BpmnInfo bpmnInfo = bpmnInfoMapper.selectLastByType(CarPackageAudit.FLOW_AUDIT_TYPE); //原生的activit7返回domain對(duì)象 不一定滿足頁面的要求, 所以:一般將元素activiti對(duì)象進(jìn)行二次加工 List<HistoricTaskInstance> list = historyService.createHistoricTaskInstanceQuery() .processInstanceId(instanceId.toString()) // .processDefinitionKey(bpmnInfo.getProcessDefinitionKey()) //套餐審核節(jié)點(diǎn) .finished() //要求節(jié)點(diǎn)執(zhí)行審核操作 .list(); //思考: 怎么查詢歷史?? List<HistoryVO> vos = new ArrayList<>(); SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); for (HistoricTaskInstance task : list) { HistoryVO vo = new HistoryVO(); vo.setTaskName(task.getName()); // 將Date類型轉(zhuǎn)成對(duì)應(yīng)格式的String vo.setEndTime(sdf.format(task.getEndTime())); vo.setStartTime(sdf.format(task.getStartTime())); //間隔時(shí)間: 花費(fèi)時(shí)間: endTime-startTime // 格式是 毫秒 ---> xx年 xx天 xxx月xxx日 xx時(shí) vo.setDurationInMillis(task.getDurationInMillis() / 1000 + "s"); //審核備注 //查詢節(jié)點(diǎn)審核備注信息? //由于可能存在并行網(wǎng)關(guān),有多條審核備注,所以要拼接在一起 List<Comment> comments = taskService.getTaskComments(task.getId()); if(comments != null || comments.size() > 0){ StringBuilder sb = new StringBuilder(80); for (Comment comment : comments) { //節(jié)點(diǎn)備注信息 sb.append(comment.getFullMessage()); } vo.setComment(sb.toString()); } vos.add(vo); } return vos; }
-
進(jìn)度查看按鈕功能:查看流程進(jìn)行到哪,在流程圖png中將進(jìn)行到的節(jié)點(diǎn)用紅框高亮的方式顯示出來。
@Override public InputStream getProcessImg(Long id) { BpmnInfo bpmnInfo = bpmnInfoMapper.selectLastByType(CarPackageAudit.FLOW_AUDIT_TYPE); ProcessDefinition processDefinition = repositoryService.createProcessDefinitionQuery() .processDefinitionKey(bpmnInfo.getProcessDefinitionKey()) .processDefinitionVersion(bpmnInfo.getVersion()) //指定版本 .singleResult();//??? CarPackageAudit audit = carPackageAuditMapper.selectCarPackageAuditById(id); List<String> activeActivityIds = new ArrayList<>(); if(audit.getStatus().equals(CarPackageAudit.STATUS_IN_ROGRESS)){ //高亮顯示當(dāng)前流程所在節(jié)點(diǎn)-坐標(biāo) activeActivityIds = runtimeService.getActiveActivityIds(audit.getInstanceId()); }else{ activeActivityIds = Collections.emptyList(); } //圖片 DefaultProcessDiagramGenerator processDiagramGenerator = new DefaultProcessDiagramGenerator(); BpmnModel bpmnModel = repositoryService.getBpmnModel(processDefinition.getId()); /** * 第一個(gè)參數(shù): 流程定義模型 * 第二個(gè)參數(shù): 高亮節(jié)點(diǎn)集合---當(dāng)前流程推進(jìn)到哪個(gè)節(jié)點(diǎn)了---傳的是節(jié)點(diǎn)坐標(biāo) * 第三個(gè)參數(shù): 高亮連線集合 */ InputStream inputStream = processDiagramGenerator.generateDiagram(bpmnModel, activeActivityIds, Collections.emptyList(), "宋體", "宋體", "宋體"); return inputStream; }
-
撤銷審核按鈕功能:顧名思義。先校驗(yàn)該流程狀態(tài)是否允許被撤銷,撤銷時(shí)需要完成三個(gè)步驟——服務(wù)套餐狀態(tài)置為初始化、審核信息記錄狀態(tài)置為審核撤銷、將運(yùn)行流程實(shí)例(關(guān)聯(lián)到的幾個(gè)表)執(zhí)行撤銷方法runtimeService.deleteProcessInstance()
@Override public void auditCancel(Long id) { //參數(shù)校驗(yàn) CarPackageAudit audit = carPackageAuditMapper.selectCarPackageAuditById(id); if(audit == null){ throw new ServiceException("參數(shù)異常"); } if(!CarPackageAudit.STATUS_IN_ROGRESS.equals(audit.getStatus())){ throw new ServiceException("只有在審核中狀態(tài)才允許撤銷操作"); } //服務(wù)套餐--狀態(tài)-初始化 ServiceItem serviceItem = serviceItemMapper.selectServiceItemById(audit.getServiceItemId()); serviceItem.setAuditStatus(ServiceItem.AUDITSTATUS_INIT); serviceItemMapper.updateServiceItem(serviceItem); //審核信息記錄--狀態(tài)--撤銷 audit.setStatus(CarPackageAudit.STATUS_CANCEL); carPackageAuditMapper.updateCarPackageAudit(audit); //流程--流程結(jié)束--刪除 runtimeService.deleteProcessInstance(audit.getInstanceId(), "審核被撤銷了"); }
4.5.4、我的待辦、我的已辦模塊:
-
前端代碼可以直接拷貝套餐審核信息模塊的vue文件,因?yàn)椴樵兊亩际菢I(yè)務(wù)表bus_car_package_audit。不同的是該兩個(gè)模塊只負(fù)責(zé)流程的推動(dòng)和審批,故沒有撤銷按鈕功能,而我的待辦模塊會(huì)多一個(gè)“審批”功能,即分配給當(dāng)前用戶的審批流程可以通過該操作選擇同意或拒絕來推動(dòng)流程進(jìn)行。
-
查詢功能參數(shù)需要添加當(dāng)前用戶條件,因?yàn)橹荒懿榈疆?dāng)前登錄用戶自己負(fù)責(zé)的審核流程。若依有自帶的SecurityUtils工具類獲取當(dāng)前登錄用戶的id、name等信息,再通過taskService.createTaskQuery().taskAssignee(SecurityUtils.getUserId().toString()).list();拿到當(dāng)前尚在推動(dòng)流程階段(未結(jié)束)的用戶自己負(fù)責(zé)的流程實(shí)例,獲取到流程實(shí)例id,即可在業(yè)務(wù)表bus_car_package_audit查詢到對(duì)應(yīng)的審核業(yè)務(wù)數(shù)據(jù)。上述說的是我的待辦模塊,而在我的已辦模塊,只需將查詢未結(jié)束的流程換成查詢?nèi)苛鞒蹋◤膆istory表中查)即可,List list = historyService.createHistoricTaskInstanceQuery().taskAssignee(SecurityUtils.getUserId().toString()).list();
-
這里提供一個(gè)更為直觀的多表聯(lián)查方法:我們一開始就能用SecurityUtils拿到用戶id,在對(duì)應(yīng)的表通過ASSIGNEE_字段篩選出當(dāng)前登錄用戶所負(fù)責(zé)的流程實(shí)例(待辦則查act_ru_task表,已辦則查act_hi_taskinst表),再通過拿到的流程實(shí)例id的字符串集合去業(yè)務(wù)表bus_car_package_audit獲取到最終自己負(fù)責(zé)的業(yè)務(wù)數(shù)據(jù)。
// mapper接口方法,注意因?yàn)楸绕胀ú樵兌嗔藆serId條件,所以需要加@Param注解給多個(gè)參數(shù)命名,傳CarPackageAudit對(duì)象是為了頁面上的條件查詢,即通過審核狀態(tài)與創(chuàng)建時(shí)間篩選數(shù)據(jù)。最后傳的字符串tableName是查詢的表名,因?yàn)榇k與已辦的sql只有一個(gè)表名之差,所以復(fù)用一下,在動(dòng)態(tài)sql里使用${}替換字符串,因?yàn)椴皇峭ㄟ^參數(shù)傳入的字段,所以不會(huì)有動(dòng)態(tài)sql注入的風(fēng)險(xiǎn)。 List<CarPackageAudit> selectHisByUserId(@Param("userId") Long userId, @Param("carPackageAudit") CarPackageAudit carPackageAudit, @Param("tableName") String tableName);
<!-- mapper.xml里的sql --> <select id="selectHisByUserId" resultMap="CarPackageAuditResult"> select c.* from bus_car_package_audit c LEFT JOIN ${tableName} a ON a.PROC_INST_ID_ = c.instance_id <where> a.ASSIGNEE_ = #{userId} <if test="carPackageAudit.params.beginCreateTime != null and carPackageAudit.params.beginCreateTime != '' and carPackageAudit.params.endCreateTime != null and carPackageAudit.params.endCreateTime != ''"> and c.create_time between #{carPackageAudit.params.beginCreateTime} and #{carPackageAudit.params.endCreateTime} </if> <if test="carPackageAudit.status != null"> and c.status = #{carPackageAudit.status}</if> </where> </select>
// service中的方法: // 已辦 List<CarPackageAudit> list = carPackageAuditMapper.selectHisByUserId(SecurityUtils.getUserId(),carPackageAudit,"act_ru_task"); // 待辦 List<CarPackageAudit> list = carPackageAuditMapper.selectHisByUserId(SecurityUtils.getUserId(),carPackageAudit,"act_hi_taskinst");
-
我的已辦中的審批功能:首先校驗(yàn)狀態(tài)是否能進(jìn)行審核。然后taskService.createTaskQuery().processInstanceId(audit.getInstanceId())查詢?nèi)蝿?wù),判斷是否為null(因?yàn)槿羰褂门潘W(wǎng)關(guān),可能其他人先一步審核通過了,若為null,則什么也不做直接return),然后根據(jù)是否審核通過添加備注信息:taskService.addComment(task.getId(), audit.getInstanceId().toString(), message); 然后新建一個(gè)map存放節(jié)點(diǎn)條件,key為條件字段的變量名value為布爾值(同意or拒絕),然后任務(wù)處理taskService.complete(task.getId(), map);。隨后業(yè)務(wù)線推進(jìn),若審核通過,判斷是否還有下一個(gè)節(jié)點(diǎn):若有則什么也不做(等待流程到下個(gè)節(jié)點(diǎn)繼續(xù)推動(dòng)),若沒有則代表當(dāng)前流程正常結(jié)束,即可修改套餐狀態(tài)和業(yè)務(wù)信息狀態(tài)。若審核拒絕,則直接修改套餐狀態(tài)和業(yè)務(wù)信息狀態(tài)。
@Override public void audit(PackageAuditVO vo) { //審核條件 //id != null //狀態(tài) 為審核中 if(vo == null){ throw new ServiceException("參數(shù)異常"); } CarPackageAudit audit = carPackageAuditMapper.selectCarPackageAuditById(vo.getId()); if(audit == null || !CarPackageAudit.STATUS_IN_ROGRESS.equals(audit.getStatus())){ throw new ServiceException("參數(shù)異常或者狀態(tài)異常"); } //流程推進(jìn): 節(jié)點(diǎn)審核 //查詢?nèi)蝿?wù) Task task = taskService.createTaskQuery() .processInstanceId(audit.getInstanceId()) .singleResult(); if(task == null){ return; } //審核備注 String message = ""; if(CarPackageAudit.STATUS_PASS.equals(vo.getAuditStatus())){ //通過 message = "審批人:" + SecurityUtils.getUsername() + "通過, 審核備注:[" + vo.getAuditInfo() + "]"; }else{ //拒絕 message = "審批人:" + SecurityUtils.getUsername() + "拒絕, 審核備注:[" + vo.getAuditInfo() + "]"; } taskService.addComment(task.getId(), audit.getInstanceId().toString(), message); Map<String, Object> map = new HashMap<>(); map.put("shopOwner", CarPackageAudit.STATUS_PASS.equals(vo.getAuditStatus())); //處理 taskService.complete(task.getId(), map); ServiceItem serviceItem = serviceItemMapper.selectServiceItemById(audit.getServiceItemId()); //業(yè)務(wù)線推進(jìn) if(CarPackageAudit.STATUS_PASS.equals(vo.getAuditStatus())){ //審核通過 Task nextTask = taskService.createTaskQuery() .processInstanceId(audit.getInstanceId()) .singleResult(); //判斷是否有下一個(gè)節(jié)點(diǎn) if(nextTask == null){ // 沒有: 當(dāng)前流程正常結(jié)束 // 1:服務(wù)套餐--審核通過 serviceItem.setAuditStatus(ServiceItem.AUDITSTATUS_APPROVED); serviceItemMapper.updateServiceItem(serviceItem); // 2:審核流程信息--審核通過 audit.setStatus(CarPackageAudit.STATUS_PASS); carPackageAuditMapper.updateCarPackageAudit(audit); } //有: 當(dāng)前流程還在繼續(xù) -- 啥都不做 }else { //審核拒絕 //1:服務(wù)套餐--審核拒絕 serviceItem.setAuditStatus(ServiceItem.AUDITSTATUS_REPLY); serviceItemMapper.updateServiceItem(serviceItem); //2:審核流程信息--審核拒絕 audit.setStatus(CarPackageAudit.STATUS_REJECT); carPackageAuditMapper.updateCarPackageAudit(audit); //3:流程--流程正常結(jié)束 } }
4.6、思考總結(jié):
- 熟悉activiti重要的那些表,及對(duì)應(yīng)的service操作。
- 設(shè)計(jì)業(yè)務(wù)表時(shí)想清楚需要哪些字段去關(guān)聯(lián)哪些activiti的表和哪些其他業(yè)務(wù),還有需要顯示的數(shù)據(jù),表設(shè)計(jì)得好則代碼寫起來就能簡(jiǎn)便很多。
- 使用工作流實(shí)現(xiàn)某個(gè)功能時(shí),從最后需要拿到的數(shù)據(jù)往前推,最后的數(shù)據(jù)有哪些對(duì)應(yīng)的字段可以和哪些表關(guān)聯(lián),建立每張表的聯(lián)系,最后聯(lián)系到我們提供的參數(shù)數(shù)據(jù),即可搭建好這座參數(shù)與返回值連接的橋梁,完成需求。
五、若依腳手架
5.1、概念
腳手架(scaffolding)指的是創(chuàng)建項(xiàng)目時(shí),自動(dòng)完成的創(chuàng)建初始文件等初始化工作。這些工作往往是每次新建工程都要進(jìn)行的重復(fù)性工作。如創(chuàng)建Maven 項(xiàng)目時(shí)使用的原型(archetype)等。腳手架是一種由一些 model–view–controller 框架支持的技術(shù),程序員可以在其中指定應(yīng)用程序數(shù)據(jù)庫的使用方式。
5.2、如何快速掌握腳手架
- 看官方文檔
- 使用腳手架創(chuàng)建項(xiàng)目后看代碼并實(shí)際上手操作
- 問有經(jīng)驗(yàn)的老前輩
5.3、如何通過腳手架快速復(fù)制出一個(gè)curd操作流程
有官方文檔就按照官方文檔操作一遍,沒有就自己搗鼓或問別人。
5.4、其他
工作中很大概率不會(huì)使用若依這樣的腳手架,則拿到腳手架后的改造就要自己操作了,注意哪些文件夾和類名要改,pom里依賴的坐標(biāo)名、版本等,最后再全局替換一下需要替換的字段,啟動(dòng)項(xiàng)目看看有沒有問題。最好是公司的腳手架已配置好初始化信息。
六、項(xiàng)目——操作
6.1、開發(fā)意識(shí)
- 產(chǎn)品是開發(fā)給用戶用的,一些功能和需求多站在用戶角度考慮,寫代碼前先整體想好該怎么開發(fā),想得細(xì)致入微一些,考慮得周全一些,把要實(shí)現(xiàn)的步驟盡可能明細(xì)地列舉出來,能畫出業(yè)務(wù)流程原型圖最好,覺得不合理或有更優(yōu)方案的地方及時(shí)和經(jīng)理或上級(jí)溝通交流,確定好最終方案,再開始開發(fā),事半功倍。
- 設(shè)計(jì)一張表的時(shí)候,先把頁面列表要什么字段加進(jìn)去,其次再考慮每個(gè)字段可能要關(guān)聯(lián)的其他字段(例如頁面只顯示用戶姓名,但我們要把用戶id的字段也加上,因?yàn)閕d才是可以唯一識(shí)別的主鍵),其次再考慮該表會(huì)關(guān)聯(lián)到的其他表需要通過什么字段關(guān)聯(lián)起來或者建立起什么關(guān)系,最后考慮該表的應(yīng)用場(chǎng)景應(yīng)該再加些什么字段去豐富它(經(jīng)驗(yàn)積累,如創(chuàng)建時(shí)間create_time,狀態(tài)status,軟刪除is_delete,創(chuàng)建人user_id等等)??紤]好每個(gè)字段用什么數(shù)據(jù)類型,是否要采用字典(常用于可選項(xiàng)較少的下拉框選項(xiàng))。
- 添加時(shí)彈窗需要回顯什么,如果是要封裝的數(shù)據(jù)則在后端包裝一個(gè)VO類去傳,后端接收前端傳來的入?yún)⑿枰b時(shí)也同理。編輯和刪除操作時(shí)多考慮除了對(duì)應(yīng)的數(shù)據(jù)改變以外,其他數(shù)據(jù)和表的狀態(tài)是否要一起改變,不要漏掉關(guān)聯(lián)的邏輯。
- 寫業(yè)務(wù)需求時(shí)還是要多多思考多多溝通,盡善盡美。
6.2、修改bug能力
后端:
- 先看拋出的異常類型,鎖定bug產(chǎn)生的原因。通過報(bào)錯(cuò)信息定位到報(bào)錯(cuò)位置,仔細(xì)排查解決。
- 看看是不是哪個(gè)注解漏貼了,哪里有可能造成空指針了,包import導(dǎo)入的對(duì)不對(duì)、是不是你要用的那個(gè)依賴的包。
- mapper.xml里的sql先在sql工具中的查詢頁面運(yùn)行一遍,沒報(bào)錯(cuò)再粘貼過去。
- debug模式打斷點(diǎn)看執(zhí)行時(shí)數(shù)據(jù)是否正常。
前端:
- 多用console.log查看數(shù)據(jù)是否有問題。
- 注意漏加this的問題
- 異步數(shù)據(jù)沒獲取到的問題
- 箭頭函數(shù)造成的作用域問題,使得this指向有誤,解決方法:在箭頭函數(shù)外寫let that = this,箭頭函數(shù)中使用that來指向this。
七、項(xiàng)目——技術(shù)上
7.1、基礎(chǔ):
-
使用腳手架添加菜單和生成代碼時(shí),務(wù)必注意模塊名和路徑這類敏感信息不要寫錯(cuò)。善用數(shù)據(jù)字典。
-
善用各種工具類,例如驗(yàn)證手機(jī)號(hào)和驗(yàn)證車牌號(hào),能省很多事
// 驗(yàn)證是否非法手機(jī)號(hào) boolean phoneLegal = RegexUtils.isPhoneLegal(busAppointment.getCustomerPhone()); Assert.isTrue(phoneLegal, "非法手機(jī)號(hào)碼"); // 驗(yàn)證是否非法車牌號(hào) VehiclePlateNoUtil.VehiclePlateNoEnum vehiclePlateNo = VehiclePlateNoUtil.getVehiclePlateNo(busAppointment.getLicensePlate()); Assert.notNull(vehiclePlateNo, "非法車牌號(hào)"); // 獲取當(dāng)前登錄用戶信息 Long userId = SecurityUtils.getUserId(); String username = SecurityUtils.getUsername(); // 將Date數(shù)據(jù)轉(zhuǎn)成想要的格式的String字符串 SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); vo.setEndTime(sdf.format(task.getEndTime())); vo.setStartTime(sdf.format(task.getStartTime()));
-
domain中有用到數(shù)據(jù)字典字段的類,在類里加上靜態(tài)常量,避免手寫出錯(cuò)或后期要修改時(shí)造成的各種麻煩
public static final Integer FLOW_AUDIT_TYPE = 0;//服務(wù)套餐審核類型 public static final Integer FLOW_PERSONAL_LEAVE = 1;//事假審核類型 public static final Integer FLOW_SICK_LEAVE = 2;//病假審核類型 public static final Integer STATUS_IN_ROGRESS = 0;//審核中 public static final Integer STATUS_REJECT = 1;//審核拒絕(拒絕) public static final Integer STATUS_PASS = 2;//審核通過(同意) public static final Integer STATUS_CANCEL = 3;//審核撤銷 public static final Integer IS_DELETE_YES = 1; // 已刪除 public static final Integer IS_DELETE_NO = 0; // 未刪除
-
添加目錄、二級(jí)菜單、菜單下的按鈕時(shí),若需要添加權(quán)限字段,則記得統(tǒng)一添加(前端v-hasPermi,后端controller的方法上@PreAuthorize(“@ss.hasPermi(‘business:appointment:add’)”),腳手架頁面的菜單權(quán)限字段上),前端權(quán)限控制是否顯示,后端權(quán)限控制當(dāng)前用戶是否有權(quán)執(zhí)行該請(qǐng)求。
// 后端 @PreAuthorize("@ss.hasPermi('business:appointment:add')") @Log(title = "新增養(yǎng)修信息預(yù)約", businessType = BusinessType.INSERT) @PostMapping public AjaxResult add(@Validated @RequestBody BusAppointment busAppointment) { return toAjax(busAppointmentService.insertBusAppointment(busAppointment)); }
<!-- 前端 --> <el-col :span="1.5"> <el-button type="primary" plain icon="el-icon-plus" size="mini" @click="handleAdd" v-hasPermi="['business:appointment:add']" >新增</el-button > </el-col>
// 頁面:
-
寫動(dòng)態(tài)sql或條件查詢語句時(shí)注意代碼書寫格式,批量操作的數(shù)組用where xxx in (xxx),時(shí)間范圍用between,善用
<include refid="xxx"/>
<sql id="selectBusStatementVo"> select id, customer_name, ... is_delete from bus_statement </sql> <select id="selectBusStatementList" parameterType="BusStatement" resultMap="BusStatementResult"> <include refid="selectBusStatementVo"/> <where> <if test="params.beginActualArrivalTime != null and params.beginActualArrivalTime != '' and params.endActualArrivalTime != null and params.endActualArrivalTime != ''"> and actual_arrival_time between #{params.beginActualArrivalTime} and #{params.endActualArrivalTime} </if> <if test="isDelete != null "> and is_delete = #{isDelete}</if> </where> </select> <delete id="deleteBusStatementByIds" parameterType="String"> delete from bus_statement where id in <foreach item="id" collection="array" open="(" separator="," close=")"> #{id} </foreach> </delete>
-
軟刪除時(shí)記得修改一些邏輯上相關(guān)的sql,因?yàn)檐泟h除數(shù)據(jù)還存在,要加上is_delete = #{isDelete}的條件
-
使用postman測(cè)試接口:
-
通過驗(yàn)證碼請(qǐng)求http://localhost:8080/captchaImage獲取到驗(yàn)證碼的uuid和code
-
登錄請(qǐng)求http://localhost:8080/login,body通過raw-JSON格式帶上uuid、code、username、password。
{ "uuid": "b9896c01fb814f128d4f6bb47d5fb99f", "username": "admin", "password": "admin123", "code": "14" }
-
登錄成功后,后續(xù)在需要測(cè)試的接口請(qǐng)求頭帶上Content-Type和Authorization,Content-Type固定填入application/json,Authorization填入剛才登錄接口返回的token
-
可在若依框架里系統(tǒng)管理-參數(shù)設(shè)置中關(guān)閉驗(yàn)證碼
-
7.2、拓展:
-
預(yù)約單超時(shí)取消:采用若依自帶的定時(shí)任務(wù)功能。因是個(gè)人項(xiàng)目不需要考慮表數(shù)據(jù)量過大,設(shè)置的是每小時(shí)執(zhí)行一次定時(shí)任務(wù)。若定時(shí)任務(wù)需要遍歷的表數(shù)據(jù)量過大,則應(yīng)錯(cuò)峰執(zhí)行定時(shí)任務(wù),如每天凌晨執(zhí)行。
/** * 定時(shí)任務(wù)調(diào)度測(cè)試 * * @author ruoyi */ @Component("appointmentTask") public class AppointmentTask { @Autowired private BusAppointmentMapper appointmentMapper; /** * 預(yù)約超時(shí)取消 */ public void AppointmentOvertime() { List<Integer> status = new ArrayList<>(); status.add(BusAppointment.STATUS_APPOINTMENT); List<BusAppointment> list = appointmentMapper.selectByStatus(status, BusAppointment.IS_DEL); for (BusAppointment busAppointment : list) { Calendar calendar = Calendar.getInstance(); calendar.setTime(busAppointment.getAppointmentTime()); calendar.add(Calendar.HOUR_OF_DAY, 6); Date overTime = calendar.getTime(); if (overTime.before(new Date())) { appointmentMapper.updateStatus(busAppointment.getId(), BusAppointment.STATUS_OVERTIME); // System.out.println(busAppointment.getCustomerName() + "已超時(shí)"); } } } }
前端定時(shí)任務(wù)頁面:
拓拓展:Calendar類的入門使用
-
結(jié)算單明細(xì)頁面,數(shù)據(jù)無論做任何修改后,在執(zhí)行保存前都不允許操作支付按鈕。給支付按鈕標(biāo)簽加:disabled=“canPay”,初始值為true,任何修改操作的方法里都將該值置為true,執(zhí)行保存方法后該值置為false。
-
客戶管理-拜訪記錄-回訪顧客下拉框,首先在create生命周期里查詢獲取到bus_customer表里的全顧客列表customerList,然后將customerList放入el-select下的el-option作為v-for遍歷的數(shù)組,key和value為item.id,lable為item.name
<el-form-item label="回訪客戶" prop="customerId"> <el-select v-model="queryParams.customerId" placeholder="請(qǐng)選擇" clearable > <el-option v-for="item in customerList" :key="item.id" :label="item.customerName" :value="item.id" /> </el-select> </el-form-item>
created() { this.getUserList(); this.getCustomerList(); }, methods: { getCustomerList() { listCustomer().then((response) => { this.customerList = response.rows; console.log(this.customerList); }); }, }
-
增加了并行網(wǎng)關(guān)的工作流流程審核:
-
先在IDEA19制定一張并行網(wǎng)關(guān)的bpmn
-
要改動(dòng)的地方不多,前端加一個(gè)發(fā)起并行審核的按鈕
-
<el-col :span="1.5"> <el-button type="success" plain icon="el-icon-edit" size="mini" :disabled="!canAudit" @click="handleParallelAudit" v-hasPermi="['business:serviceItem:edit']" >發(fā)起并行審核</el-button > </el-col> <!-- 審核窗口復(fù)制一個(gè)之前的,只是多一欄下拉框供給并行的選擇第二個(gè)店長,然后綁定另一個(gè)值v-model="shopOwnerId2 --> <el-form-item label="審核人(店長):" prop="shopOwners" v-if="isParallel" > <el-select size="medium" v-model="shopOwnerId2"> <el-option v-for="item in auditInfo.shopOwners" :key="item.userId" :label="item.nickName" :value="item.userId" > </el-option> </el-select> </el-form-item>
-
data() { shopOwnerId2: null, // 是否是并行審核 isParallel: false, } methods:{ /** 發(fā)起并行審核彈窗 */ handleParallelAudit() { if (!this.canAudit) { return; } getAuditInfo(this.id).then((res) => { this.resetAudit(); console.log(res); this.auditInfo = res.data; this.isParallel = true; this.auditOpen = true; }); }, /** 確認(rèn)發(fā)起審核 */ auditSubmit() { let param = { id: this.id, shopOwnerId: this.shopOwnerId, shopOwnerId2: this.shopOwnerId2, financeId: this.financeId, info: this.info, }; // 開始審核 if (this.isParallel) { startParallelAudit(param).then((res) => { this.getList(); this.$modal.msgSuccess("發(fā)起審核成功!"); this.isParallel = false; this.auditOpen = false; }) .catch(() => {}); } else { startAudit(param).then((res) => { this.getList(); this.$modal.msgSuccess("發(fā)起審核成功!"); this.isParallel = false; this.auditOpen = false; }); } }, }
-
-
后端新增一個(gè)接口接收并行工作流的審核提交,新建一個(gè)vo類,因?yàn)槎嗔藗€(gè)shopOwnerId2參數(shù)要接收。服務(wù)層也可以復(fù)制之前的再修改調(diào)整,改一下校驗(yàn)邏輯,注意在并行網(wǎng)關(guān)時(shí)雙店長審核有一個(gè)審核拒絕,另一個(gè)在activiti里并不會(huì)自動(dòng)刪除(結(jié)束流程)而是還在ru_task里,需要手動(dòng)結(jié)束其他并行流程,提供一個(gè)思路
-
else { // else里寫審核拒絕邏輯 // 拿到當(dāng)前審核流程實(shí)例下的其他并行流程 List<Task> list = taskService.createTaskQuery() .processInstanceId(audit.getInstanceId()).list(); if (list != null && list.size() > 0) { for (Task otherTask : list) { taskService.complete(otherTask.getId(), map); taskService.addComment(otherTask.getId(), audit.getInstanceId().toString(), "其余審核人已拒絕"); } } serviceItemMapper.updateServiceItemStatus(audit.getServiceItemId(), BusServiceItem.AUDITSTATUS_REPLY); audit.setStatus(CarPackageAudit.STATUS_REJECT); carPackageAuditMapper.updateCarPackageAudit(audit); }
-
-
一些可能不會(huì)報(bào)錯(cuò)的bug:流程走向有問題,先看看bpmn有沒有寫對(duì),使用文本編輯看看每個(gè)節(jié)點(diǎn)的參數(shù)和連接下個(gè)節(jié)點(diǎn)是否正確。然后看代碼,很可能是服務(wù)層寫錯(cuò)了,比如我遇到一個(gè)問題是兩個(gè)店長都審核完了,結(jié)果走財(cái)務(wù)審核時(shí)又跳到了一個(gè)店長角色審核,一看才發(fā)現(xiàn)是添加條件map時(shí)財(cái)務(wù)節(jié)點(diǎn)的value復(fù)制錯(cuò)了vo.getshowOnerId。文章來源:http://www.zghlxwxcb.cn/news/detail-842636.html
-
收工,后續(xù)有機(jī)會(huì)再迭代新功能。文章來源地址http://www.zghlxwxcb.cn/news/detail-842636.html
到了這里,關(guān)于一個(gè)開源的汽修r(nóng)bac后臺(tái)管理系統(tǒng)項(xiàng)目,基于若依框架,實(shí)現(xiàn)了activiti工作流,附源碼的文章就介紹完了。如果您還想了解更多內(nèi)容,請(qǐng)?jiān)谟疑辖撬阉鱐OY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!