習(xí)管理系統(tǒng)源碼解析與部署實(shí)戰(zhàn))
接手這類高校實(shí)習(xí)管理系統(tǒng)源碼項(xiàng)目對(duì)我來說算是一個(gè)挺熟悉的場(chǎng)景。SpringBootVueMyBatisMySQL這套組合幾乎是國(guó)內(nèi)高校信息管理系統(tǒng)里最常見的“標(biāo)準(zhǔn)答案”但越常見的組合越容易在細(xì)節(jié)上翻車。這篇就把我從拿到這套源碼、拆解業(yè)務(wù)、跑通流程到部署上線的完整過程捋一遍把那些文檔里不會(huì)寫清楚的坑和實(shí)際處理方案一并分享出來給準(zhǔn)備做類似系統(tǒng)或者正在看這份源碼的朋友一些參考。這套系統(tǒng)解決的是高校實(shí)習(xí)業(yè)務(wù)的全流程管理學(xué)生報(bào)崗、教師審批、企業(yè)評(píng)價(jià)、材料提交、成績(jī)?cè)u(píng)定、統(tǒng)計(jì)報(bào)表這些環(huán)節(jié)全塞進(jìn)一個(gè)前后端分離的Web系統(tǒng)里。它的核心價(jià)值在于把原本靠紙質(zhì)表單、Excel匯總、微信群通知才能跑通的實(shí)習(xí)過程落成一條清晰的數(shù)據(jù)流。適合誰參考一個(gè)是正在做畢業(yè)設(shè)計(jì)或者課程項(xiàng)目的本科生、研究生另一個(gè)是高校信息中心或二級(jí)學(xué)院想搭建實(shí)習(xí)管理平臺(tái)的開發(fā)人員再就是打算做“通用業(yè)務(wù)管理系統(tǒng)”練手、想看看成熟項(xiàng)目結(jié)構(gòu)的人。1. 項(xiàng)目全貌與技術(shù)選型思路解構(gòu)1.1 從業(yè)務(wù)痛點(diǎn)反推的系統(tǒng)設(shè)計(jì)高校實(shí)習(xí)管理這件事實(shí)際跑起來遠(yuǎn)比想象中瑣碎。學(xué)生要選實(shí)習(xí)單位、提交申請(qǐng)指導(dǎo)老師要審核崗位是否合規(guī)教學(xué)秘書要匯總學(xué)生是否落實(shí)實(shí)習(xí)企業(yè)導(dǎo)師要給學(xué)生打分最終所有數(shù)據(jù)還要匯成成績(jī)和統(tǒng)計(jì)報(bào)表。沒有系統(tǒng)之前這些數(shù)據(jù)分散在紙質(zhì)表格、聊天記錄、個(gè)人電腦的Excel里一旦涉及轉(zhuǎn)專業(yè)、休學(xué)、實(shí)習(xí)單位變更數(shù)據(jù)追溯極其痛苦。所以這套系統(tǒng)在業(yè)務(wù)設(shè)計(jì)上天生就是“多角色、多狀態(tài)、強(qiáng)流程”的形態(tài)。它不是一個(gè)簡(jiǎn)單的CRUD應(yīng)用而是圍繞“實(shí)習(xí)前—實(shí)習(xí)中—實(shí)習(xí)后”三個(gè)階段構(gòu)建的完整閉環(huán)。實(shí)習(xí)前解決崗位申報(bào)與審批實(shí)習(xí)中解決過程記錄與材料上傳實(shí)習(xí)后解決成績(jī)?cè)u(píng)定與數(shù)據(jù)歸檔。每個(gè)階段都有嚴(yán)格的狀態(tài)流轉(zhuǎn)比如崗位申請(qǐng)必須走“學(xué)生提交—教師審核—通過后生效”的路徑材料提交必須綁定到具體的實(shí)習(xí)項(xiàng)目上成績(jī)?cè)u(píng)定必須引用過程數(shù)據(jù)和最終報(bào)告。這樣一個(gè)系統(tǒng)如果從零開始設(shè)計(jì)最忌諱的就是一上來就寫代碼。正確順序應(yīng)該是反過來的先把業(yè)務(wù)角色、狀態(tài)機(jī)、數(shù)據(jù)結(jié)構(gòu)定義清楚再對(duì)照業(yè)務(wù)流去搭接口和頁面。這套源碼的整體結(jié)構(gòu)恰恰是這種“業(yè)務(wù)驅(qū)動(dòng)”的產(chǎn)物。拿到源碼后第一件事情不是急著啟動(dòng)項(xiàng)目而是打開數(shù)據(jù)庫(kù)腳本和實(shí)體類順著表結(jié)構(gòu)把業(yè)務(wù)邏輯反推一遍這一步能省下后面大量調(diào)試時(shí)間。1.2 為什么是SpringBootVueMyBatisMySQL這套組合選這套技術(shù)棧不是因?yàn)樗唷跋冗M(jìn)”而是因?yàn)樗鼧O其“穩(wěn)”。SpringBoot負(fù)責(zé)后端接口與業(yè)務(wù)邏輯是Java生態(tài)里上手成本和維護(hù)成本最平衡的框架Vue負(fù)責(zé)前端頁面與交互配合Element UI這類組件庫(kù)能在很短時(shí)間里搭出管理后臺(tái)風(fēng)格的界面MyBatis負(fù)責(zé)SQL持久層它的優(yōu)勢(shì)是SQL可控、性能調(diào)優(yōu)直接適合業(yè)務(wù)規(guī)則復(fù)雜、查詢條件多變的系統(tǒng)MySQL負(fù)責(zé)數(shù)據(jù)存儲(chǔ)學(xué)校里部署環(huán)境最普遍的就是它幾乎沒有額外的部署成本。很多人會(huì)問為什么不直接用Spring Data JPA或者換成MyBatis-Plus再或者前端用React我的看法是JPA雖然開發(fā)省事但自定義復(fù)雜查詢和SQL調(diào)優(yōu)很別扭MyBatis-Plus確實(shí)在單表CRUD上更快但如果這套源碼為了保證學(xué)習(xí)者看懂動(dòng)態(tài)SQL和多表關(guān)聯(lián)而保留原生MyBatis寫法那控制力更強(qiáng)React生態(tài)確實(shí)繁榮但Vue在國(guó)內(nèi)高校開發(fā)者和學(xué)生群體中使用基數(shù)更大遇到問題更容易找到現(xiàn)成答案。整套技術(shù)選型說明一個(gè)問題做業(yè)務(wù)系統(tǒng)技術(shù)棧的“團(tuán)隊(duì)熟悉度”優(yōu)先于“技術(shù)先進(jìn)性”。如果團(tuán)隊(duì)里大部分人只熟悉這套組合那它就是最優(yōu)解。這套源碼的價(jià)值也在這里——它用最常見的組合實(shí)現(xiàn)了完整業(yè)務(wù)流程代碼結(jié)構(gòu)天然適合當(dāng)作學(xué)習(xí)模板。1.3 源碼結(jié)構(gòu)與模塊劃分速覽拿到源碼包之后第一感受是目錄結(jié)構(gòu)規(guī)整。后端是標(biāo)準(zhǔn)的Maven工程分為controller、service、mapper、entity、config幾個(gè)包職責(zé)邊界清楚。前端是Vue CLI工程views目錄下按角色劃分頁面比如student、teacher、admin、enterprise四個(gè)子目錄這種劃分方式對(duì)應(yīng)了系統(tǒng)的四類使用角色。前端還有一套獨(dú)立的router配置用路由守衛(wèi)做登錄態(tài)校驗(yàn)。這一點(diǎn)對(duì)實(shí)際項(xiàng)目很重要后端接口有token鑒權(quán)前端路由也要同步攔截否則用戶可以通過改URL直接跳轉(zhuǎn)到未授權(quán)頁面雖然接口會(huì)403但頁面閃爍和報(bào)錯(cuò)體驗(yàn)很糟糕。源碼里已經(jīng)把頂欄、側(cè)邊欄、面包屑這些框架級(jí)組件抽出來了二次開發(fā)時(shí)改起來很方便。2. 架構(gòu)設(shè)計(jì)與核心業(yè)務(wù)模塊拆解2.1 前后端分離與分層設(shè)計(jì)詳解系統(tǒng)采用前后端完全分離的方式前端通過HTTP請(qǐng)求調(diào)用后端Restful API數(shù)據(jù)交互統(tǒng)一使用JSON格式。前端的構(gòu)建產(chǎn)物dist目錄可以后部署在Nginx上后端以SpringBoot內(nèi)嵌Tomcat方式啟動(dòng)跑在獨(dú)立端口。這里的核心好處是前后端可以獨(dú)立開發(fā)、獨(dú)立部署、獨(dú)立擴(kuò)容符合現(xiàn)代Web開發(fā)的通用實(shí)踐也避免了傳統(tǒng)JSP項(xiàng)目前后端耦合導(dǎo)致后期維護(hù)困難的問題。后端按Controller—Service—Mapper三層組織。Controller層只做參數(shù)接收、調(diào)用Service、返回結(jié)果這三件事不允許出現(xiàn)業(yè)務(wù)邏輯代碼。Service層承載核心業(yè)務(wù)規(guī)則比如審核流程的狀態(tài)判斷、成績(jī)計(jì)算的邏輯。Mapper層對(duì)應(yīng)MyBatis接口SQL寫在XML文件中多表關(guān)聯(lián)查詢、條件查詢都在這里實(shí)現(xiàn)。這種分層有一個(gè)明顯的調(diào)試優(yōu)勢(shì)當(dāng)接口返回異常時(shí)只需要定位到對(duì)應(yīng)層即可。比如查詢結(jié)果不符預(yù)期問題大概率在Mapper的SQL中如果是狀態(tài)流轉(zhuǎn)異常問題在Service層的if判斷中如果是參數(shù)格式不對(duì)問題在Controller的校驗(yàn)注解里。很多同學(xué)調(diào)試代碼時(shí)習(xí)慣打一堆斷點(diǎn)從頭跟到尾其實(shí)效率很低。利用分層定位先看接口入口、再看Service斷點(diǎn)、最后看SQL日志三步就能鎖定大部分問題。2.2 四端協(xié)同學(xué)生、教師、企業(yè)、管理員這套系統(tǒng)最大的亮點(diǎn)之一是完整覆蓋了實(shí)習(xí)業(yè)務(wù)中的四類角色而不是像很多簡(jiǎn)化版系統(tǒng)那樣只做學(xué)生端和教師端。先看學(xué)生端。學(xué)生登錄后可以看到可申報(bào)的實(shí)習(xí)崗位列表提交申請(qǐng)時(shí)填寫意向說明、實(shí)習(xí)周期、是否服從調(diào)劑等信息。實(shí)習(xí)開始后學(xué)生還需要按周提交實(shí)習(xí)周記周期結(jié)束后上傳實(shí)習(xí)報(bào)告和實(shí)習(xí)鑒定表。這里有一個(gè)細(xì)節(jié)實(shí)習(xí)周記必須是按周填寫的后端接口做了日期校驗(yàn)防止學(xué)生一次把幾周的內(nèi)容全部提交這是實(shí)際管理中一個(gè)很硬的需求。教師端主要做兩類事情一是審核學(xué)生提交的崗位申請(qǐng)判斷崗位是否與專業(yè)對(duì)口、實(shí)習(xí)單位是否合規(guī)二是查看學(xué)生提交的周記和報(bào)告給出過程評(píng)分。很多系統(tǒng)的教師端只停留在“審批”層面這套系統(tǒng)把過程管理也嵌進(jìn)去了教師在時(shí)間線上可以看到自己名下所有學(xué)生的實(shí)習(xí)進(jìn)度有學(xué)生兩周沒提交周記系統(tǒng)列表里會(huì)高亮標(biāo)記這種細(xì)節(jié)在答辯和實(shí)際演示中很加分。企業(yè)端是很多同類系統(tǒng)缺失的一環(huán)但這家源碼特意加上了。企業(yè)用戶可以注冊(cè)、維護(hù)企業(yè)信息、發(fā)布實(shí)習(xí)崗位并在實(shí)習(xí)結(jié)束后對(duì)學(xué)生的表現(xiàn)打分。崗位和學(xué)生的匹配邏輯是企業(yè)發(fā)布崗位后系統(tǒng)管理員審核通過崗位才出現(xiàn)在學(xué)生端的申報(bào)列表中企業(yè)評(píng)分提交后系統(tǒng)中合計(jì)最終成績(jī)時(shí)企業(yè)評(píng)分和教師評(píng)分按比例加權(quán)合成。這個(gè)“企業(yè)參與”的設(shè)計(jì)讓整個(gè)實(shí)習(xí)鏈條真正閉環(huán)了。管理員端則是全局管理。用戶管理、角色分配、數(shù)據(jù)字典、菜單權(quán)限、實(shí)習(xí)項(xiàng)目創(chuàng)建、統(tǒng)計(jì)報(bào)表全部在這里完成。說白了學(xué)生、教師、企業(yè)都是在“用”系統(tǒng)只有管理員在“管”系統(tǒng)。源碼里采用了基于角色的訪問控制模型用戶和角色關(guān)聯(lián)角色和菜單權(quán)限關(guān)聯(lián)權(quán)限控制粒度到按鈕級(jí)別。2.3 三條核心業(yè)務(wù)流程的狀態(tài)流轉(zhuǎn)實(shí)習(xí)管理系統(tǒng)的復(fù)雜度很大程度體現(xiàn)在流程狀態(tài)機(jī)上。我梳理出三條最關(guān)鍵的業(yè)務(wù)線。第一條是“崗位申報(bào)審核流”。流程是學(xué)生提交崗位申請(qǐng)狀態(tài)為待審核—教師審核通過狀態(tài)為通過實(shí)習(xí)生效—教師審核駁回狀態(tài)為駁回學(xué)生可修改后重新提交。這里有兩個(gè)后端校驗(yàn)必須寫一個(gè)是通過審核時(shí)檢查學(xué)生是否已有生效中的實(shí)習(xí)申請(qǐng)防止一個(gè)學(xué)生同時(shí)綁定多個(gè)崗位另一個(gè)是駁回時(shí)必須填寫駁回原因保證業(yè)務(wù)可追溯。第二條是“實(shí)習(xí)材料提交流”。流程是學(xué)生上傳周記/報(bào)告—指導(dǎo)教師查看并打分—學(xué)生確認(rèn)得分。這里涉及一個(gè)關(guān)鍵設(shè)計(jì)材料上傳后的“確認(rèn)提交”操作是不可逆的用戶確認(rèn)后PDF文件都不允許刪除。這是為了避免學(xué)生反復(fù)修改導(dǎo)致教師在審核時(shí)看到的內(nèi)容發(fā)生變化。如果有特殊修改需求必須走教師退回流程。第三條是“成績(jī)合成流”。流程是期末成績(jī)過程成績(jī)30%企業(yè)評(píng)分30%報(bào)告成績(jī)40%。這個(gè)權(quán)重是典型的實(shí)習(xí)業(yè)務(wù)配置項(xiàng)如果換一個(gè)學(xué)校權(quán)重可能完全不同所以正確做法是做一個(gè)成績(jī)權(quán)重配置表存數(shù)據(jù)庫(kù)而不是寫死在代碼里。源碼在這方面用了配置文件賦默認(rèn)值小規(guī)模使用沒問題但真要推廣部署我建議還是抽成數(shù)據(jù)庫(kù)配置表。3. 數(shù)據(jù)庫(kù)設(shè)計(jì)與核心表結(jié)構(gòu)解析3.1 關(guān)系模型設(shè)計(jì)通過外鍵串起來的實(shí)習(xí)生命周期這套系統(tǒng)的數(shù)據(jù)庫(kù)有一個(gè)很顯著的特點(diǎn)以“實(shí)習(xí)項(xiàng)目”為主軸所有業(yè)務(wù)表都圍繞它來組織。用戶表是最基礎(chǔ)的包含學(xué)生、教師、企業(yè)三類用戶的共性字段用角色字段區(qū)分身份。學(xué)生信息擴(kuò)展表存放學(xué)號(hào)、學(xué)院、班級(jí)、年級(jí)企業(yè)信息擴(kuò)展表存放單位名稱、統(tǒng)一社會(huì)信用代碼、企業(yè)規(guī)模。實(shí)習(xí)項(xiàng)目表是整個(gè)系統(tǒng)的核心它記錄一屆實(shí)習(xí)活動(dòng)的基本信息。學(xué)生申報(bào)表通過用戶ID和項(xiàng)目ID關(guān)聯(lián)到具體的學(xué)生和具體的實(shí)習(xí)項(xiàng)目崗位表與實(shí)習(xí)單位關(guān)聯(lián)。材料表通過實(shí)習(xí)記錄ID關(guān)聯(lián)到學(xué)生的一次實(shí)習(xí)過程成績(jī)表則引用學(xué)生、項(xiàng)目、材料等多個(gè)維度。這樣的關(guān)系設(shè)計(jì)帶來的好處是查詢某學(xué)生的完整實(shí)習(xí)履歷時(shí)不需要在代碼里做大量的內(nèi)存關(guān)聯(lián)幾條SQL就可以把項(xiàng)目、崗位、材料、成績(jī)?nèi)看鰜?。壞處是表的?shù)量比普通管理系統(tǒng)多不少初次接觸的人容易被表關(guān)系圖嚇住。我的辦法是拿到數(shù)據(jù)庫(kù)腳本后不要著急看每張表的字段先畫出表之間的關(guān)系理清“哪張表是主軸的延伸哪張表是業(yè)務(wù)的結(jié)果表”整體一下就清晰了。3.2 核心表結(jié)構(gòu)詳解用戶表的設(shè)計(jì)要點(diǎn)是賬號(hào)、密碼BCrypt加密存儲(chǔ)、真實(shí)姓名、角色1學(xué)生/2教師/3企業(yè)/4管理員、狀態(tài)0禁用/1正常。企業(yè)用戶需要額外綁定一個(gè)企業(yè)ID教師用戶不需要。這張表是所有查詢權(quán)限判斷的根本所以索引要建好特別是在賬號(hào)和角色這兩個(gè)字段上建立聯(lián)合索引。實(shí)習(xí)項(xiàng)目表重要的是“時(shí)間邊界”字段報(bào)名開始時(shí)間、報(bào)名結(jié)束時(shí)間、實(shí)習(xí)開始時(shí)間、實(shí)習(xí)結(jié)束時(shí)間。源碼的Service層用LocalDateTime直接比較時(shí)間所有時(shí)間都用數(shù)據(jù)庫(kù)時(shí)間而非前端傳入時(shí)間這個(gè)設(shè)計(jì)值得學(xué)習(xí)——如果用前端傳入時(shí)間客戶端改一下本地時(shí)間就能繞過報(bào)名限制。學(xué)生崗位申請(qǐng)記錄表的狀態(tài)字段是核心0待審核、1審核通過、2審核駁回、3已撤銷。這張表記錄了學(xué)生每一次申請(qǐng)的動(dòng)作和審核的結(jié)論也保留了教師填寫審核意見的字段。實(shí)習(xí)材料表的設(shè)計(jì)細(xì)節(jié)體現(xiàn)在“材料類型”字段上1周記/2月度總結(jié)/3鑒定表/4最終報(bào)告“周記材料”還要額外關(guān)聯(lián)一個(gè)周序號(hào)字段用于判斷是否連環(huán)提交。這張表大字段存的是文件存儲(chǔ)路徑而不是文件二進(jìn)制內(nèi)容本身。成績(jī)表設(shè)計(jì)相對(duì)簡(jiǎn)單學(xué)生ID、項(xiàng)目ID、企業(yè)評(píng)分、教師評(píng)分、報(bào)告評(píng)分、加權(quán)總分、評(píng)定等級(jí)。但源碼里做了一件挺好的事——每次更新成績(jī)都會(huì)保留一份快照記錄防止成績(jī)被誤改后無法追溯。3.3 字段設(shè)計(jì)上的坑與優(yōu)化建議設(shè)計(jì)數(shù)據(jù)表時(shí)最容易翻車的是“字符集”、“時(shí)間類型”和“唯一約束”。字符集方面建表語句中的字符集必須統(tǒng)一。學(xué)生姓名和企業(yè)名稱里很可能出現(xiàn)生僻字所以建議所有字符串字段統(tǒng)一使用utf8mb4字符集排序規(guī)則用utf8mb4_general_ci。不能用utf8因?yàn)閡tf8在MySQL里實(shí)際上最多存3個(gè)字節(jié)部分生僻字和特殊符號(hào)存儲(chǔ)時(shí)會(huì)報(bào)錯(cuò)或亂碼。時(shí)間類型方面不要用字符串存時(shí)間。我看到過不少老舊系統(tǒng)把時(shí)間字段設(shè)計(jì)成varchar查詢排序全亂套。用datetime類型存儲(chǔ)日期比較交給MySQL計(jì)算代碼里用LocalDateTime接收數(shù)據(jù)這是最省心的組合。唯一約束方面一個(gè)學(xué)生同一時(shí)間只能存在一條生效中的崗位申請(qǐng)這個(gè)約束除了在Service層校驗(yàn)建議直接在表上建唯一索引用“學(xué)生ID項(xiàng)目狀態(tài)”的聯(lián)合索引來控制并發(fā)寫入。否則兩個(gè)管理員同時(shí)在后臺(tái)操作時(shí)代碼層面的校驗(yàn)是防不住的只有唯一索引才能兜底。4. 關(guān)鍵功能實(shí)現(xiàn)與代碼級(jí)拆解4.1 基于JWT的登錄認(rèn)證與權(quán)限控制系統(tǒng)的登錄認(rèn)證采用JWT方案。用戶輸入賬號(hào)密碼后后端先通過BCrypt校驗(yàn)密碼校驗(yàn)通過后生成一個(gè)包含用戶ID、角色、過期時(shí)間的Token返回給前端。前端把Token存到sessionStorage里后續(xù)每一個(gè)請(qǐng)求都在Header中帶上這個(gè)Token。后端使用攔截器統(tǒng)一校驗(yàn)Token的有效性。這里有幾個(gè)細(xì)節(jié)攔截器要放行登錄接口、驗(yàn)證碼接口等公開路徑其余路徑全部校驗(yàn)Token。校驗(yàn)通過后還需要判斷該用戶是否有訪問當(dāng)前接口的權(quán)限源碼里用AOP結(jié)合自定義注解的方式做權(quán)限檢查在Controller方法上標(biāo)注RequirePermission(student:add)沒有權(quán)限時(shí)會(huì)拋出異常并由全局異常處理器統(tǒng)一返回403。實(shí)際使用中我強(qiáng)烈建議為Token設(shè)置一個(gè)合適的過期時(shí)間。太短導(dǎo)致用戶頻繁重新登錄體感很差太長(zhǎng)導(dǎo)致token泄露后風(fēng)險(xiǎn)過高。這個(gè)項(xiàng)目默認(rèn)是2小時(shí)屬于可以接受的折中方案。4.2 實(shí)習(xí)申報(bào)與審批流程的代碼實(shí)現(xiàn)學(xué)生申報(bào)崗位的接口核心邏輯代碼如下public Result submitApply(ApplyDTO dto) { // 1. 校驗(yàn)項(xiàng)目當(dāng)前是否在申報(bào)期內(nèi) LocalDateTime now LocalDateTime.now(); if (now.isBefore(project.getApplyStart()) || now.isAfter(project.getApplyEnd())) { return Result.error(當(dāng)前不在申報(bào)時(shí)間內(nèi)); } // 2. 校驗(yàn)學(xué)生是否已有生效申請(qǐng) int activeCount applyMapper.selectActiveCount(dto.getStudentId()); if (activeCount 0) { return Result.error(您已有生效中的實(shí)習(xí)申請(qǐng)); } // 3. 插入申請(qǐng)記錄狀態(tài)為待審核 ApplyRecord record new ApplyRecord(); record.setStudentId(dto.getStudentId()); record.setProjectId(project.getId()); record.setStatus(0); applyMapper.insert(record); return Result.success(); }這三步的順序很重要先校驗(yàn)時(shí)間窗口再查重最后插入。如果把查重放在時(shí)間校驗(yàn)前面一個(gè)過期項(xiàng)目的申請(qǐng)就可能被卡在第二步但用戶得到的提示是“已有生效申請(qǐng)”會(huì)誤導(dǎo)排查方向。我校這個(gè)項(xiàng)目時(shí)也走過彎路后來把校驗(yàn)順序調(diào)整成“先時(shí)間、后業(yè)務(wù)、再寫入”體驗(yàn)才正常。審批接口的邏輯類似教師審核時(shí)先判斷記錄狀態(tài)是否為待審核不是就直接拒絕操作。通過審核后將狀態(tài)改為1同時(shí)做一次學(xué)生“名下生效崗位數(shù)量”的Redis緩存清理保證下一次查重能讀到最新數(shù)據(jù)。4.3 文件上傳與材料審核實(shí)現(xiàn)實(shí)習(xí)周期里最大的數(shù)據(jù)量來自學(xué)生上傳的周記和報(bào)告。這些文件的大小和格式都要在入口處做限制。源碼里用Spring的MultipartFile接收文件服務(wù)層校驗(yàn)擴(kuò)展名和文件大小上傳成功后將文件存儲(chǔ)在服務(wù)器本地的uploads目錄并返回相對(duì)路徑給前端。這里我強(qiáng)烈建議把文件存儲(chǔ)的根路徑配置成可配置的。當(dāng)前系統(tǒng)默認(rèn)把文件存在項(xiàng)目的相對(duì)路徑下這在開發(fā)環(huán)境很舒服但一旦用Nginx部署了前端再做后端升級(jí)時(shí)相對(duì)路徑的目錄很容易被清空或者錯(cuò)亂。部署時(shí)我會(huì)把上傳目錄配置為服務(wù)器上的獨(dú)立數(shù)據(jù)盤路徑并在Nginx中增加一個(gè)靜態(tài)文件映射將/uploads/路徑指向這個(gè)目錄從而讓前端可以直接通過URL預(yù)覽上傳的文檔。文件審核的流程是教師點(diǎn)開材料列表系統(tǒng)調(diào)接口返回文件URL和材料信息教師在線預(yù)覽后填寫評(píng)分提交。材料審核接口必須綁定材料ID和審核教師ID防止一個(gè)教師修改非自己管轄學(xué)生的材料。4.4 成績(jī)?cè)u(píng)定與統(tǒng)計(jì)報(bào)表的實(shí)現(xiàn)成績(jī)合成邏輯放在Service層核心代碼思路如下public Result calculateScore(Long recordId) { ScoreRecord scoreRecord scoreMapper.selectByRecordId(recordId); BigDecimal finalScore scoreRecord.getProcessScore().multiply(processWeight) .add(scoreRecord.getCompanyScore().multiply(companyWeight)) .add(scoreRecord.getReportScore().multiply(reportWeight)) .setScale(2, RoundingMode.HALF_UP); scoreRecord.setFinalScore(finalScore); scoreMapper.update(scoreRecord); }權(quán)重的來源是配置表在管理端頁面可以調(diào)整這樣不同學(xué)院不同專業(yè)設(shè)置的規(guī)則就分開了。統(tǒng)計(jì)報(bào)表模塊則是從實(shí)習(xí)項(xiàng)目維度匯總數(shù)據(jù)比如各專業(yè)實(shí)習(xí)落實(shí)率、企業(yè)評(píng)價(jià)平均分、未提交材料學(xué)生名單等。報(bào)表查詢SQL里用到了group by和多個(gè)條件動(dòng)態(tài)拼接MyBatis的動(dòng)態(tài)SQL此時(shí)就能發(fā)揮作用了。5. 部署上線與常見問題排查5.1 本地啟動(dòng)到服務(wù)部署的完整步驟我拿到這套源碼后的啟動(dòng)路徑是這樣的。先在本地準(zhǔn)備好MySQL 8.0創(chuàng)建數(shù)據(jù)庫(kù)并執(zhí)行SQL腳本腳本里有建庫(kù)建表語句和初始數(shù)據(jù)。后端項(xiàng)目修改application.yml里的數(shù)據(jù)庫(kù)連接信息啟動(dòng)后默認(rèn)端口是8080。前端項(xiàng)目進(jìn)入目錄執(zhí)行npm install安裝依賴然后執(zhí)行npm run serve啟動(dòng)開發(fā)服務(wù)器默認(rèn)端口9528同時(shí)把vite.config.js里的代理路徑指向后端8080解決開發(fā)環(huán)境跨域問題。正式部署時(shí)前端執(zhí)行npm run build生成dist目錄將其交給Nginx托管后端用mvn clean package打包成JAR用systemd服務(wù)管理啟動(dòng)。Nginx配置里除靜態(tài)資源映射外還要做/api的代理轉(zhuǎn)發(fā)把前端請(qǐng)求轉(zhuǎn)發(fā)到后端的8080端口。這里有一個(gè)需要注意的點(diǎn)Nginx配置的client_max_body_size要根據(jù)實(shí)際情況調(diào)大否則學(xué)生上傳大文件時(shí)會(huì)得到413錯(cuò)誤。5.2 高頻問題排查手冊(cè)第一類問題是數(shù)據(jù)庫(kù)連接失敗典型報(bào)錯(cuò)是Access denied for user或Communications link failure。前者說明賬號(hào)密碼或授權(quán)有問題后者說明服務(wù)沒起來或者端口被防火墻擋住。建議先在本機(jī)用命令行客戶端實(shí)測(cè)一遍連接能連上再排查應(yīng)用配置。第二類問題是前端請(qǐng)求跨域。開發(fā)環(huán)境看瀏覽器控制臺(tái)的CORS報(bào)錯(cuò)解決方式是開啟Vue腳手架自帶的代理生產(chǎn)環(huán)境發(fā)的是Nginx報(bào)錯(cuò)解決方式是配置代理轉(zhuǎn)發(fā)。很多人會(huì)想省事在后端加CrossOrigin注解但這治標(biāo)不治本而且會(huì)把接口暴露給所有域名調(diào)用不建議這么干。第三類問題是文件上傳成功但訪問404。原因通常是存儲(chǔ)路徑和訪問映射不一致。需要記住一個(gè)原則前端訪問的URL路徑和后端實(shí)際存儲(chǔ)的磁盤路徑是兩碼事需要把二者通過配置關(guān)聯(lián)起來。第四個(gè)高頻問題是中文亂碼。從MySQL連接URL的編碼參數(shù)、到前端頁面charset、再到文件存儲(chǔ)名稱的編碼轉(zhuǎn)換必須全程統(tǒng)一UTF-8。任何一個(gè)環(huán)節(jié)斷了數(shù)據(jù)就會(huì)出現(xiàn)問號(hào)或者亂碼。6. 實(shí)戰(zhàn)心得與后續(xù)擴(kuò)展建議做完整套系統(tǒng)的二次開發(fā)我的感受是這個(gè)項(xiàng)目最大的價(jià)值不在于它的代碼量多而在于它的業(yè)務(wù)完整度。從角色劃分、流程控制、文件處理到統(tǒng)計(jì)報(bào)表幾乎覆蓋了高校實(shí)習(xí)管理的全部核心環(huán)節(jié)而且代碼風(fēng)格一致命名規(guī)范非常適合在此基礎(chǔ)上做二次擴(kuò)展。我想額外再說一個(gè)很多人容易忽略的點(diǎn)就是系統(tǒng)運(yùn)行的性能問題。高校實(shí)習(xí)管理系統(tǒng)的并發(fā)量通常不高但如果上傳的資料文件很多數(shù)據(jù)庫(kù)里的材料記錄表會(huì)迅速膨脹。建議定期做歷史數(shù)據(jù)的歸檔比如把畢業(yè)學(xué)生的實(shí)習(xí)數(shù)據(jù)遷移到歸檔表保留快速查詢的數(shù)據(jù)控制在兩年以內(nèi)。后續(xù)如果要擴(kuò)展我個(gè)人覺得有兩個(gè)方向最實(shí)用。第一個(gè)是消息通知模塊當(dāng)前系統(tǒng)只在業(yè)務(wù)狀態(tài)變化時(shí)記錄日志沒有主動(dòng)通知能力。接入郵件或微信消息模板把審核結(jié)果、材料駁回、實(shí)習(xí)截止時(shí)間推送給相關(guān)用戶整個(gè)系統(tǒng)的體驗(yàn)感會(huì)大幅提升。第二個(gè)是移動(dòng)端適配學(xué)生提交周記、看審批結(jié)果這些高頻操作其實(shí)更適合在手機(jī)上完成。可以保留現(xiàn)有的Vue2管理端單獨(dú)開發(fā)一個(gè)小程序端或H5端復(fù)用后端接口成本并不高。最后再分享一個(gè)小技巧如果你打算把這套源碼作為畢業(yè)設(shè)計(jì)或者實(shí)訓(xùn)項(xiàng)目提交建議在項(xiàng)目文檔里增加一份完整的數(shù)據(jù)字典說明把每一張表的業(yè)務(wù)含義、狀態(tài)字段取值說明都寫清楚。評(píng)審老師對(duì)這種“底層數(shù)據(jù)設(shè)計(jì)意識(shí)”非常認(rèn)可這也是開發(fā)人員從“寫代碼”走向“做設(shè)計(jì)”的分水嶺。