都霓凱,解佳慧,蔣杰
(1.四川大學(xué),四川 成都 610000;2.四川核工業(yè)技師學(xué)院,四川 廣元 628000)
每年都會(huì)有大量職業(yè)技術(shù)學(xué)校的老師和學(xué)生參加該比賽,展現(xiàn)其突出的實(shí)踐能力。參加該比賽,前期需投入一系列競(jìng)賽相關(guān)器材。然而,許多學(xué)校由于經(jīng)費(fèi)限制,無(wú)法購(gòu)置足夠的競(jìng)賽器材,導(dǎo)致一部分學(xué)生參賽受限;參賽后,購(gòu)置的競(jìng)賽器材難有用武之地而被迫閑置,造成極大的浪費(fèi)。因此,有必要針對(duì)參賽機(jī)構(gòu)建立一個(gè)競(jìng)賽設(shè)備共享平臺(tái),促成各個(gè)機(jī)構(gòu)間閑置競(jìng)賽設(shè)備的流通,以實(shí)現(xiàn)資源的充分利用。本項(xiàng)目組給出一種基于Spring Boot與Vue的競(jìng)賽設(shè)備供需平臺(tái)的設(shè)計(jì)與實(shí)現(xiàn)。項(xiàng)目完整源代碼在Gitee已上傳,并且成功在服務(wù)器上運(yùn)行供內(nèi)部成員使用。
為了方便代碼實(shí)現(xiàn)和后期運(yùn)營(yíng)維護(hù),本項(xiàng)目采取了前后端分離的工作模式,前端通過(guò)ajax調(diào)用后端api來(lái)完成整個(gè)系統(tǒng)的運(yùn)作。前后端均使用IntelliJ IDEA開(kāi)發(fā)。測(cè)試階段,進(jìn)行本地的前后端代碼結(jié)合測(cè)試。開(kāi)發(fā)完成后部署在Linux服務(wù)器上進(jìn)行測(cè)試和發(fā)布。
表1 項(xiàng)目環(huán)境表
(1)Tomcat。是一個(gè)輕量級(jí)的Web應(yīng)用服務(wù)器,免費(fèi)使用且開(kāi)放了源代碼。利用它可以響應(yīng)HTML頁(yè)面的響應(yīng)請(qǐng)求。而Spring框架中內(nèi)置了1個(gè)tomcat服務(wù)器,極大方便了使用者的開(kāi)發(fā)。當(dāng)在1臺(tái)機(jī)器上配置好Apache 服務(wù)器,可利用它響應(yīng)HTML(標(biāo)準(zhǔn)通用標(biāo)記語(yǔ)言下的一個(gè)應(yīng)用)頁(yè)面的訪問(wèn)請(qǐng)求。實(shí)際上Tomcat是Apache 服務(wù)器的擴(kuò)展,但運(yùn)行時(shí)它是獨(dú)立運(yùn)行的,所以當(dāng)你運(yùn)行tomcat時(shí),它實(shí)際上作為一個(gè)與Apache 獨(dú)立的進(jìn)程單獨(dú)運(yùn)行的。
(2)Spring Data JPA。JPA即Java Persistence API,由Sun公司官方提出的Java持久化規(guī)范。Spring Data JPA 是 Spring 基于 ORM 框架、JPA 規(guī)范的基礎(chǔ)上封裝的一套 JPA 應(yīng)用框架,底層使用了 Hibernate的 JPA 技術(shù)實(shí)現(xiàn),可使開(kāi)發(fā)者用極簡(jiǎn)的代碼即可實(shí)現(xiàn)對(duì)數(shù)據(jù)的訪問(wèn)和操作,它提供了包括增刪改查等在內(nèi)的常用功能,且易于擴(kuò)展。以此項(xiàng)目后端架構(gòu)為例,DAO層會(huì)對(duì)JpaRepository繼承,具體規(guī)則可以查詢Spring文檔。
圖1 Spring Data Jpa示例
(4)lombok。是一個(gè)快捷的Java插件。主要使用其中的@Data注解,在類外加上這個(gè)注解之后,可以自動(dòng)生成get()/set()類內(nèi)部成員變量的方法,即不需要顯示地定義就可以調(diào)用,可節(jié)省上百行甚至上千行代碼。
(5)hash算法。hash 算法(散列算法、摘要算法)即把任意長(zhǎng)度的輸入映射為固定長(zhǎng)度的輸出,比如,密碼 Evanniubi 變成五位的輸出 kchpl,這種算法不可逆,且存在信息損失,對(duì)于復(fù)雜密碼來(lái)說(shuō),破解成本極高。加嚴(yán)是提高 hash 算法的安全性的一個(gè)常用手段。
(6)開(kāi)箱即用。是Spring Boot框架中非常重要的一個(gè)概念,通過(guò)在開(kāi)發(fā)過(guò)程中,使用約定省去了以往在開(kāi)發(fā)中繁瑣的配置工作,用注解完全省去了XML文件的配置工作。傳統(tǒng)Spring IO平臺(tái)飽受非議的一點(diǎn)就是大量的XML配置以及復(fù)雜的依賴管理,開(kāi)發(fā)人員需要不斷編寫XML,而且在一些場(chǎng)景中甚至不需要編寫繁瑣的import語(yǔ)句。開(kāi)箱即用策略使得Spring Boot甚至可以在140個(gè)字符內(nèi)實(shí)現(xiàn)可運(yùn)行的web應(yīng)用,且沒(méi)有代碼的生成,極大得提升了產(chǎn)品的關(guān)注度。
由于賬號(hào)性質(zhì)不同,所以系統(tǒng)功能基本分為2個(gè)子類:用戶類和管理員類。用戶類下又需要設(shè)計(jì)增加、修改、查詢、刪除等功能,以及對(duì)自己賬號(hào)的管理。但是競(jìng)賽項(xiàng)目的增刪、修改,競(jìng)賽所需設(shè)備的增刪、修改都需要管理員權(quán)限,且用戶不能獲取他人賬號(hào)所有的信息。查詢也分很多種,需要根據(jù)具體需求來(lái)設(shè)計(jì)具體功能。管理員類除了擁有普通用戶的全部功能外,還可以對(duì)所有競(jìng)賽項(xiàng)目、競(jìng)賽所需設(shè)備、共享設(shè)備、訂單以及所有賬號(hào)進(jìn)行管理。
后端小組在JetBrain官方網(wǎng)站上申請(qǐng)了學(xué)生認(rèn)證,使用其公司提供的正版IntelliJ IDEA最終版進(jìn)行開(kāi)發(fā)。借助目前比較熱門的Spring框架搭建項(xiàng)目??蚣芑窘M成已經(jīng)提供,所以只需要豐富src/main路徑下的內(nèi)容即可。
后端根據(jù)層次劃分共有11層:(1)config包下進(jìn)行web和shiro的配置,需要定義允許特定請(qǐng)求跨域的方法以及編寫基于URL的過(guò)濾器;(2)entity包下將數(shù)據(jù)庫(kù)各實(shí)體映射成類,通過(guò)@Entity注解表明這個(gè)類代表了數(shù)據(jù)庫(kù)中的一個(gè)實(shí)體表,@Table(name = “”)注解中,name值需要和數(shù)據(jù)庫(kù)中表名相同;(3)DAO包對(duì)JpaRepository類進(jìn)行繼承,實(shí)現(xiàn)數(shù)據(jù)持久化的操作,直接操作數(shù)據(jù)庫(kù),用于與數(shù)據(jù)庫(kù)的直接交互,定義增刪改查等操作。其中大部分基本函數(shù)已經(jīng)寫好,并且由于Jpa遵循著一套規(guī)范,所以想要擴(kuò)展出我們需要的函數(shù)相對(duì)容易,只需按照規(guī)定的語(yǔ)法編寫函數(shù)名稱和返回值即可。具體規(guī)范可以參照Spring官方文檔;(4)service包是對(duì)DAO包內(nèi)函數(shù)的進(jìn)一步封裝,實(shí)現(xiàn)增刪查改等基本功能,并按照需求實(shí)現(xiàn)特殊的功能,負(fù)責(zé)業(yè)務(wù)邏輯,跟功能相關(guān)的代碼一般寫在這里,編寫、調(diào)用各種方法對(duì) DAO 取得的數(shù)據(jù)進(jìn)行操作;(5)controller包是最后一層封裝,需要將函數(shù)與接口關(guān)聯(lián)。所謂接口即前端調(diào)用的url,負(fù)責(zé)數(shù)據(jù)交互,即接收前端發(fā)送的數(shù)據(jù),通過(guò)調(diào)用 Service 獲得處理后的數(shù)據(jù)并返回。在實(shí)踐中我們傾向于讓 Controller 顯得清涼一些,以方便代碼的閱讀者尋找分析功能的入口;(6)result包定義了controller包中,函數(shù)成功或者失敗的返回?cái)?shù)據(jù)類型,一般需要code,message,data 3個(gè)部分組成;(7)utils包實(shí)現(xiàn)需要使用的工具類。例如對(duì)字符串的處理方法;(8)error包配置錯(cuò)誤處理;(9)exception包配置異常處理;(10)filter包實(shí)現(xiàn)過(guò)濾器方法,即攔截用戶訪問(wèn)沒(méi)有相應(yīng)權(quán)限的網(wǎng)頁(yè),同時(shí)不允許用戶未登錄就進(jìn)入平臺(tái)各頁(yè)面;(11)realm包為shiro進(jìn)行用戶認(rèn)證的核心,這里定義獲取認(rèn)證與授權(quán)信息的方法。
后端中最基本的最重要的層次為DAO-servicecontroller,也是項(xiàng)目開(kāi)發(fā)的主要工作內(nèi)容。
本項(xiàng)目使用MySQL 8.0數(shù)據(jù)庫(kù),以學(xué)生資格認(rèn)證申請(qǐng)了正版Navicat 16來(lái)可視化管理數(shù)據(jù)庫(kù)。數(shù)據(jù)庫(kù)中共有7個(gè)實(shí)體表:dept(所屬單位)、user(用戶)、order(訂單)、share_eq(共享設(shè)備詳情)、equipments(設(shè)備種類)、equip_comp(設(shè)備、競(jìng)賽關(guān)聯(lián)表)、competition(競(jìng)賽項(xiàng)目)。其中除了主碼約束之外,還有部分屬性使用check約束取值。每個(gè)表之間的主碼還有多對(duì)一外碼約束。代碼中可以通過(guò) @ManyToOne與@JoinColumn()注解實(shí)現(xiàn)。由于competition與equipments是多對(duì)多關(guān)系,所以直接在數(shù)據(jù)庫(kù)中建立實(shí)體表equip_comp來(lái)維護(hù)關(guān)聯(lián)。
wmax denotes the size of the region where the boundary between the doped and undoped zones is moved back and forth under the effect of a periodic bias signal. The two basic expressions for this model are:
競(jìng)賽項(xiàng)目需要分成各等級(jí),competition表的tag屬性存儲(chǔ)了這個(gè)信息。需求中還需要各競(jìng)賽設(shè)備由官方給出指導(dǎo)價(jià)格,由equipments的guiding體現(xiàn)。2個(gè)表中的desc_屬性需要加下劃線,也是因?yàn)閐esc是保留關(guān)鍵字。share_eq表中surplus表示剩余量,設(shè)備提供方申請(qǐng)入網(wǎng)設(shè)備時(shí)可能設(shè)備數(shù)量有多個(gè)。需求方預(yù)約設(shè)備時(shí)也不能無(wú)限制預(yù)約,每個(gè)訂單都會(huì)讓剩余量減1,余量為0則不能進(jìn)行預(yù)約。刪除訂單后余量重新加1。禁用狀態(tài)status則用來(lái)判斷是否可以進(jìn)行預(yù)約。被禁用的理由有多種,余量不足時(shí)也會(huì)將禁用狀態(tài)設(shè)為false,即禁用。order表中的evaluate屬性用于記錄評(píng)分。分值只能由設(shè)備借用者在完成訂單之后給出。
各表主碼均為id。其中user表與dept表沒(méi)有外碼約束,user表的school屬性應(yīng)該從dept表的name中取值。但由于SQL的特性,外碼只能參照表的主碼作為約束,所以school與dept之間沒(méi)有參照關(guān)系,而是由前端的選擇框?qū)崿F(xiàn)。share_eq表的eid屬性參照equipments表中的id,uid屬性參照user表中的id,均為多對(duì)一關(guān)系。order表,實(shí)際上數(shù)據(jù)庫(kù)中表名為orde,原因是order是java保留關(guān)鍵字,使用會(huì)報(bào)錯(cuò),之后論文中均直接稱其為order表。order表的sid參照share_eq表的id,uid參照user表的id。
共有4個(gè)位置使用了check約束。user表中對(duì)role屬性進(jìn)行約束check (`role`=`user``adm``provider`),只可以取三者中的一個(gè)。這樣的語(yǔ)法在不同版本的MySQL中可能報(bào)錯(cuò),有的需要將字符串使用雙引號(hào)括起來(lái),并使用OR關(guān)鍵詞。在dept表中,check(`local`=`川東``川西``川南``川北``川中`),后期可以豐富部門的地址,具體到成都市的某區(qū)某街道。目前只能取4種值是為了演示根據(jù)地址查詢共享設(shè)備的功能。competition表中,check(`tag`=`土木建筑類``裝備制造類``交通運(yùn)輸類``電子與信息類``財(cái)經(jīng)商貿(mào)類``旅游服務(wù)類`),即對(duì)競(jìng)賽進(jìn)行分類,這樣可以根據(jù)類別查詢競(jìng)賽項(xiàng)目。最后是order表中check (status = 1 OR status = 2 OR status = 3),check (evaluate <= 5 AND evaluate>= 0),對(duì)status和evaluate進(jìn)行約束,status為1表示交易開(kāi)始,2表示正在交易,3代表交易完成。評(píng)分則是限制在0.0~5.0范圍內(nèi),一位小數(shù),在數(shù)據(jù)庫(kù)中用numeric(2,1)表示。
管理員注冊(cè)用戶賬號(hào)時(shí),輸入密碼(明文),向后臺(tái)發(fā)送請(qǐng)求,后臺(tái)將密碼加上隨機(jī)生成的鹽并 hash,再將 hash 后的值作為密碼存入數(shù)據(jù)庫(kù),鹽也作為單獨(dú)的字段存起來(lái),生成隨機(jī)的用戶名告知用戶。之后用戶登錄時(shí),輸入用戶名密碼(明文),向后臺(tái)發(fā)送請(qǐng)求[4],后臺(tái)根據(jù)用戶名查詢出鹽,和密碼組合并 hash,將得到的值與數(shù)據(jù)庫(kù)中存儲(chǔ)的密碼比對(duì),若一致則通過(guò)驗(yàn)證。
/user/myself接口用于獲取個(gè)人信息(同時(shí)用于驗(yàn)證登錄),UserUtils.getUser()獲取當(dāng)前用戶的用戶名之后通過(guò)userService.findByUsername()獲取用戶對(duì)象即可。
所有用戶均可以獲取全部競(jìng)賽項(xiàng)目信息,/api/user/allProject調(diào)用competitionService.list()即可。
管理員可以添加競(jìng)賽項(xiàng)目。添加之前先判斷該名稱的競(jìng)賽是否存在,通過(guò)調(diào)用competitionService.haveName()判斷。competitionService.haveName()則是調(diào)用了competitonDAO。existByName()。前端傳的數(shù)據(jù)除了競(jìng)賽的信息,還需要提供所需設(shè)備的id列表,整個(gè)作為Param1對(duì)象,方便處理。Param1在utils包下進(jìn)行了詳細(xì)定義。competitionService.add()添加競(jìng)賽后,使用增強(qiáng)for循環(huán)一一在equip_comp表中添加競(jìng)賽項(xiàng)目id(cid)和競(jìng)賽設(shè)備id(eid)的關(guān)聯(lián)。
管理員修改競(jìng)賽項(xiàng)目,同時(shí)修改與設(shè)備的關(guān)聯(lián),先把現(xiàn)有關(guān)聯(lián)刪除,之后重新添加。同樣使用Param1數(shù)據(jù)類型,使用增強(qiáng)for循環(huán)。
管理員可以刪除競(jìng)賽項(xiàng)目,通過(guò)/api/admin/deleteProject接口。首先需要判斷請(qǐng)求體competition對(duì)象是否在數(shù)據(jù)庫(kù)中存在,判斷equipCompService.haveCid (competition.getId()) 的返回值是否為真即可。刪除操作使用的是 competitionService.deleteById(competition.getId())。所以刪除功能中,前端只需要提供正確的id即可,其余信息不需要提供。這個(gè)設(shè)計(jì)十分重要,在之后的各功能模塊中也都有所體現(xiàn)。
所有用戶都可以查看所有競(jìng)賽所需要的設(shè)備,通過(guò)/api/user/allDevice接口,調(diào)用equipmentsService.list()。
管理員可以添加設(shè)備,判斷請(qǐng)求體是否為空以后,調(diào)用equipmentsService.add(equipments.getName(),equipments.getDesc_(), equipments.getGuiding())。
管理員修改設(shè)備的接口,調(diào)用的 equipmentsService.edit(equipments)的參數(shù)表就簡(jiǎn)單很多了,直接將前端傳來(lái)的equipments對(duì)象傳入即可。在service層中,Competition c=get(competition.getId());得到competition對(duì)象之后,對(duì)其每個(gè)屬性進(jìn)行覆蓋修改,最后competitionDAO.save(c);完成修改設(shè)備。
管理員刪除設(shè)備時(shí),如果該設(shè)備有相關(guān)聯(lián)的競(jìng)賽,則無(wú)法進(jìn)行刪除,調(diào)用equipCompService.haveEid()實(shí)現(xiàn)判斷equip_comp表中是否有該設(shè)備id的關(guān)聯(lián)項(xiàng)。
除了通過(guò)id查找設(shè)備,名稱模糊查找設(shè)備外,還可以通過(guò)競(jìng)賽查找設(shè)備。通過(guò)名稱模糊查找設(shè)備的方法基本和通過(guò)名稱模糊查找競(jìng)賽的方法一致。想要通過(guò)競(jìng)賽查找設(shè)備,必然需要通過(guò)存儲(chǔ)關(guān)聯(lián)項(xiàng)的表equip_comp。該功能在controller/equipCompController中實(shí)現(xiàn)。用戶還可以查詢自己共享了的設(shè)備的信息。根據(jù)id查詢到該用戶共享的設(shè)備列表,由于shareEq對(duì)象內(nèi)部均含有equipments對(duì)象,所以將共享設(shè)備列表中共有的設(shè)備對(duì)象全部提取出來(lái)組成新列表。由于其中可能出現(xiàn)重復(fù)的設(shè)備,所以需要做去重處理。去重最簡(jiǎn)單的方法就是新建一個(gè)hashset,HashSet
管理員可以刪除任意共享設(shè)備,但是用戶只能刪除自己的共享設(shè)備。查詢本用戶的共享設(shè)備時(shí),只需提取前端傳來(lái)用戶對(duì)象中的uid,再調(diào)用shareEqService.listByUser()即可。
上傳圖片的功能,分為首次上傳圖片和修改共享設(shè)備圖片。首次上傳圖片調(diào)用api/user/sharecovers接口,以MultipartFile類型傳輸數(shù)據(jù),請(qǐng)求參數(shù)的value為file。存儲(chǔ)路徑為/share/workspace/img,是Linux系統(tǒng)路徑格式,因?yàn)轫?xiàng)目部署在Linux服務(wù)器上。修改共享設(shè)備圖片除了接受MultipartFile數(shù)據(jù)外,還要提供路徑變量,調(diào)用api/user/sharecovers/{id}接口。需要先從數(shù)據(jù)庫(kù)中獲得該共享設(shè)備的圖片url,判斷本地磁盤上是否存在該文件,如果存在則刪除舊圖片,將新圖片url存入數(shù)據(jù)庫(kù)之后返回url。
添加訂單時(shí),需判斷共享設(shè)備是否有余量,如果有則余量減一再添加訂單。默認(rèn)狀態(tài)為1,評(píng)分為0.0。相應(yīng)的,刪除訂單時(shí)余量重新加一。
通過(guò)調(diào)用/api/user/borrow接口,用戶可以查找自己的借用訂單。調(diào)用/api/user/lend,用戶查找自己的借出訂單。以上2個(gè)功能均需要先驗(yàn)證當(dāng)前登錄用戶是否和請(qǐng)求體用戶對(duì)象相同。
完成了用戶登錄認(rèn)證之后,還需要考慮完善的訪問(wèn)攔截。如果用戶登錄了一次,關(guān)閉瀏覽器后,sessionId就會(huì)消失,再次發(fā)送請(qǐng)求,shiro就會(huì)認(rèn)為用戶已經(jīng)變更。但有時(shí)我們需要保持登錄狀態(tài),不然每次都要重新登錄,所以shiro提供了rememberMe機(jī)制。rememberMe機(jī)制不是單純地設(shè)置cookie存活時(shí)間,而是又單獨(dú)保存了一種新的狀態(tài)。之所以這樣設(shè)計(jì),也是出于安全性考慮,把“記住我”的狀態(tài)與實(shí)際登錄狀態(tài)做出區(qū)分,這樣,就可以控制用戶在訪問(wèn)不太敏感的頁(yè)面時(shí)無(wú)需重新登錄,而訪問(wèn)類似于購(gòu)物車、訂單之類的頁(yè)面時(shí)必須重新登錄。除了rememberMe機(jī)制外,還需要一個(gè)基于URL的路徑過(guò)濾器,URLPathMatchingFilter。filterChainDefinitionMap.put(“/api/admin/**”,“url”);表示所有以/api/admin/為前綴的接口都需要有管理員權(quán)限才能訪問(wèn)。filterChainDefinitionMap.put(“/api/user/password”,“authc”);表示這個(gè)接口需要登錄才能訪問(wèn)。
本項(xiàng)目采取了多種測(cè)試方法結(jié)合。首先后端開(kāi)發(fā)過(guò)程中,通過(guò)學(xué)生資格認(rèn)證申請(qǐng)正版Postman軟件進(jìn)行測(cè)試后端接口,測(cè)試結(jié)果全部通過(guò)。在Postman上需要提供測(cè)試的接口url,以及正確輸入請(qǐng)求體格式、類型、數(shù)據(jù)內(nèi)容。部分接口只接受單個(gè)參數(shù),設(shè)置param即可。還有部分接口需要傳輸Map之類的特殊類型,則需要選擇content-type為application/json,然后寫明原始json格式的數(shù)據(jù)進(jìn)行測(cè)試。之后在IDEA同時(shí)啟動(dòng)前端和后端做結(jié)合測(cè)試,本地計(jì)算機(jī)作為服務(wù)器,瀏覽器訪問(wèn)http://localhost:8080/index,測(cè)試前端按鈕和后端接口的交互。最后項(xiàng)目在服務(wù)器端部署之后,也持續(xù)不斷進(jìn)行測(cè)試,發(fā)現(xiàn)問(wèn)題后及時(shí)修改優(yōu)化,主要是Linux系統(tǒng)文件路徑問(wèn)題。后端jar包版本已經(jīng)迭代13次。目前已經(jīng)能穩(wěn)定運(yùn)行,使用瀏覽器訪問(wèn)ip地址即可使用。
服務(wù)器使用的是華為云Linux服務(wù)器,通過(guò)學(xué)生認(rèn)證申請(qǐng)的正版Xshell軟件遠(yuǎn)程連接服務(wù)器,使用命令行操作文件系統(tǒng)。首先sudo apt-get install lrzsz安裝rz,sz,之后選擇文件傳輸中的ZModem傳輸本地文件至遠(yuǎn)程服務(wù)器。在此之前,后端內(nèi)容需要通過(guò)mevan打包成jar文件包,上傳至服務(wù)器之后,以nohup java -jar項(xiàng)目名.jar &命令運(yùn)行jar包,即可在關(guān)閉服務(wù)器連接之后進(jìn)程仍然運(yùn)行。
最終用瀏覽器訪問(wèn)服務(wù)器的ip地址即可看到界面,開(kāi)始使用本競(jìng)賽設(shè)備共享平臺(tái)。