實(shí)戰(zhàn):高校綜合測(cè)評(píng)系統(tǒng)的設(shè)計(jì)與權(quán)限控制解析)
畢業(yè)設(shè)計(jì)選了這個(gè)題目的人我懂你。高校學(xué)生綜合測(cè)評(píng)說(shuō)白了一句話把輔導(dǎo)員每學(xué)期末對(duì)著Excel表格算分、加分、排名、還要簽字蓋章的那套活兒搬到Web系統(tǒng)里讓學(xué)生自己看得到分?jǐn)?shù)老師審核得了依據(jù)系統(tǒng)自動(dòng)出得了排名和報(bào)表。作為Java方向的課題它覆蓋了面向?qū)ο缶幊獭pring Boot框架、數(shù)據(jù)庫(kù)設(shè)計(jì)、權(quán)限控制、事務(wù)一致性這幾個(gè)核心知識(shí)點(diǎn)難度適中、演示效果好、論文也好寫算是JavaWeb類畢設(shè)里性價(jià)比很高的一類。這篇內(nèi)容我不會(huì)跟你甩一堆教科書概念直接按我實(shí)際做過的思路拆開講這個(gè)系統(tǒng)涉及哪些角色、表結(jié)構(gòu)怎么設(shè)計(jì)、綜合分算法怎么寫、什么地方容易踩坑、答辯怎么講才不心虛。你不用做過完整項(xiàng)目只要跟著把每個(gè)模塊的“為什么”和“怎么落地”理清楚代碼自己寫出來(lái)或者看懂都沒問題。1. 課題拆解綜合測(cè)評(píng)系統(tǒng)到底在解決什么問題1.1 傳統(tǒng)綜合測(cè)評(píng)的三座大山先想清楚一個(gè)事教務(wù)系統(tǒng)、成績(jī)系統(tǒng)在很多學(xué)校早就有了為什么還要單獨(dú)做一個(gè)“綜合測(cè)評(píng)系統(tǒng)”因?yàn)榫C合測(cè)評(píng)不只是看考試成績(jī)它要整合學(xué)習(xí)成績(jī)、思想品德、文體活動(dòng)、競(jìng)賽獲獎(jiǎng)、違紀(jì)扣分等維度的數(shù)據(jù)然后按照學(xué)院制定的權(quán)重算出一個(gè)總分作為獎(jiǎng)學(xué)金評(píng)定、評(píng)優(yōu)評(píng)先、保研推薦的依據(jù)。傳統(tǒng)做法里最讓人崩潰的一個(gè)是數(shù)據(jù)分散成績(jī)?cè)诮虅?wù)系統(tǒng)里活動(dòng)加分在學(xué)生會(huì)組織部手里違紀(jì)記錄在學(xué)工辦那輔導(dǎo)員要做的是把這些數(shù)據(jù)手動(dòng)匯總到一張Excel大表里。第二個(gè)是加分標(biāo)準(zhǔn)不透明學(xué)生參加一個(gè)比賽到底加0.5分還是1分經(jīng)常是“看情況”學(xué)生申訴起來(lái)沒依據(jù)。第三個(gè)是重復(fù)勞動(dòng)每學(xué)期都要重新算一遍公式復(fù)制錯(cuò)一個(gè)單元格整個(gè)專業(yè)排名就崩了。所以系統(tǒng)要解決的就是“數(shù)據(jù)集中錄入、加分流程可追溯、綜合分自動(dòng)計(jì)算、排名和報(bào)表一鍵導(dǎo)出”。1.2 系統(tǒng)的角色與核心業(yè)務(wù)流程明確了痛點(diǎn)就很好抽象角色了。高校綜合測(cè)評(píng)系統(tǒng)的用戶基本逃不開四個(gè)角色下面這個(gè)表可以直接寫進(jìn)課題需求分析角色核心訴求主要操作學(xué)生查看自己的各項(xiàng)得分、排名、申訴加分提交加分申請(qǐng)、查看測(cè)評(píng)明細(xì)與總分班委/輔導(dǎo)員錄入班級(jí)同學(xué)的基礎(chǔ)加分、審核申請(qǐng)班級(jí)管理、審核加分材料、錄入班級(jí)互評(píng)學(xué)院管理員/評(píng)審小組制定測(cè)評(píng)模板、審核班級(jí)結(jié)果、導(dǎo)出匯總權(quán)重配置、評(píng)定管理、數(shù)據(jù)導(dǎo)出超級(jí)管理員維護(hù)用戶、班級(jí)、基礎(chǔ)數(shù)據(jù)角色授權(quán)、用戶管理、系統(tǒng)日志業(yè)務(wù)流程其實(shí)是一條直線管理員先配置好評(píng)分規(guī)則比如成績(jī)占70%、德育占15%、文體占15%學(xué)生或?qū)W委提交加分申請(qǐng)并上傳佐證材料輔導(dǎo)員逐條審核審核通過后進(jìn)入分?jǐn)?shù)臺(tái)賬系統(tǒng)根據(jù)權(quán)重自動(dòng)匯總出每個(gè)學(xué)生的綜合測(cè)評(píng)總分再按專業(yè)或班級(jí)排名最后管理員導(dǎo)出匯總表用于獎(jiǎng)學(xué)金評(píng)定。這里面最關(guān)鍵的隱性需求是“留痕”——誰(shuí)加的、為什么加、誰(shuí)審核的、什么時(shí)候?qū)徍说亩家懿榈玫剿悦織l分?jǐn)?shù)記錄必須有創(chuàng)建人、審核人和時(shí)間字段。2. 技術(shù)選型Java這套組合拳為什么能打2.1 后端框架Spring Boot MyBatis-Plus畢設(shè)選題是Java那后端框架基本繞不開Spring Boot。你不需要糾結(jié)SSH那套老古董也不用上來(lái)就整微服務(wù)單體應(yīng)用加經(jīng)典分層就是最適合畢設(shè)的度。Spring Boot幫我把Tomcat內(nèi)嵌、自動(dòng)配置、依賴管理全處理掉了我從JDK和Maven配好之后一個(gè)注解就能啟動(dòng)Web服務(wù)這對(duì)畢設(shè)來(lái)說(shuō)節(jié)省了大量環(huán)境折騰時(shí)間。持久層我建議直接用MyBatis-Plus不要手寫純MyBatis。原因很簡(jiǎn)單綜合測(cè)評(píng)系統(tǒng)的CRUD極其標(biāo)準(zhǔn)班級(jí)、學(xué)生、評(píng)分項(xiàng)目、分?jǐn)?shù)明細(xì)都是單表操作MyBatis-Plus的BaseMapper能直接省掉80%的Mapper XML分頁(yè)插件也內(nèi)置好了。你只要把精力留給核心的測(cè)評(píng)算法和審核流程而不是把時(shí)間花在寫“selectById”這種機(jī)械代碼上。這里還涉及一個(gè)Java畢業(yè)生常被問的問題“MyBatis和JDBC有什么關(guān)系”我的理解是JDBC是Java訪問數(shù)據(jù)庫(kù)的那座橋MyBatis在這座橋上做了映射封裝把ResultSet到對(duì)象的轉(zhuǎn)換、連接管理、SQL參數(shù)綁定都自動(dòng)化了。2.2 前端方案Vue Element UI 還是模板引擎前端這個(gè)選擇決定了你后期聯(lián)調(diào)的心態(tài)。有兩個(gè)明確路線一種是前后端分離用Vue 2 Element UI另一種是服務(wù)端渲染直接用Thymeleaf寫頁(yè)面。我個(gè)人的建議是如果你前端基礎(chǔ)一般或者時(shí)間已經(jīng)很緊張果斷選Thymeleaf加Bootstrap服務(wù)端渲染不用處理跨域Controller里return一個(gè)視圖名模型數(shù)據(jù)直接往模板里賽項(xiàng)目能少踩一半的坑。但說(shuō)句實(shí)話現(xiàn)在畢設(shè)答辯老師對(duì)前后端分離的“期待值”已經(jīng)抬高了如果你的簡(jiǎn)歷里寫了Vue那還是老老實(shí)實(shí)走Restful API Vue Element UI的路子。Element UI組件現(xiàn)成表格的分頁(yè)、表單校驗(yàn)、彈窗都很快后端只需提供JSON接口前端把token放到請(qǐng)求頭里。我做的版本就是分離式的開發(fā)時(shí)用Vite起前端代理轉(zhuǎn)發(fā)到8080端口聯(lián)調(diào)很順。這個(gè)選擇的成本主要集中在跨域和鑒權(quán)上后面我會(huì)專門講這兩個(gè)坑。2.3 環(huán)境與中間件準(zhǔn)備從零起步的人先花半天把環(huán)境弄齊后面效率能翻倍。我建議的最小環(huán)境是JDK 1.8畢設(shè)足夠別去追JDK 17除非你想演示新特性、Maven 3.6、MySQL 5.7或8.0、Redis可選。有人會(huì)問Redis不是標(biāo)配嗎我的看法是能不上就不上綜合測(cè)評(píng)系統(tǒng)的數(shù)據(jù)量級(jí)和并發(fā)量遠(yuǎn)沒到需要用緩存的程度引入Redis只會(huì)增加部署復(fù)雜度還容易在答辯時(shí)被追問緩存一致性。如果你的項(xiàng)目里確實(shí)需要“防止重復(fù)提交加分申請(qǐng)”用MySQL的唯一索引完全能解決未必非得Redis。Java環(huán)境變量配置我提一句這是最容易被卡住的細(xì)節(jié)變量名JAVA_HOME指向JDK安裝目錄Path里加%JAVA_HOME%\bin然后cmd里java -version驗(yàn)證。很多人的坑是裝了JDK后忘了配JAVA_HOME結(jié)果IDE能跑但命令行找不到后面用Maven就報(bào)錯(cuò)。Windows下配置完記得重新開命令行窗口環(huán)境變量不會(huì)自動(dòng)刷新。3. 數(shù)據(jù)庫(kù)設(shè)計(jì)與分層架構(gòu)3.1 分層架構(gòu)設(shè)計(jì)工程結(jié)構(gòu)方面我強(qiáng)烈建議一個(gè)模塊化分包方式既符合實(shí)際開發(fā)習(xí)慣答辯時(shí)也說(shuō)得清controller接收參數(shù)、調(diào)用service、封裝返回結(jié)果不做業(yè)務(wù)邏輯service / service.impl寫具體業(yè)務(wù)規(guī)則比如測(cè)評(píng)分?jǐn)?shù)計(jì)算、審核狀態(tài)流轉(zhuǎn)mapper繼承BaseMapper必要時(shí)自定義SQLentity對(duì)應(yīng)數(shù)據(jù)庫(kù)表的實(shí)體類common統(tǒng)一返回結(jié)果、異常處理、工具類config全局配置比如攔截器、跨域配置這里其實(shí)就是Java的核心思想“面向接口編程”。Service層接口定義好impl里寫實(shí)現(xiàn)Controller只依賴接口不依賴實(shí)現(xiàn)將來(lái)想換實(shí)現(xiàn)類不用改調(diào)用方。答辯老師很喜歡在這個(gè)點(diǎn)展開你最好能說(shuō)一兩句“高內(nèi)聚低耦合”“開閉原則”就來(lái)自于面向?qū)ο笤O(shè)計(jì)原則。這個(gè)項(xiàng)目里的典型體現(xiàn)是計(jì)算綜合分的算法可能在學(xué)期末調(diào)整權(quán)重你只要改Service實(shí)現(xiàn)不碰Controller層。3.2 核心表結(jié)構(gòu)與設(shè)計(jì)思路數(shù)據(jù)庫(kù)是整個(gè)項(xiàng)目的底盤你表設(shè)計(jì)得爛后面寫代碼處處別扭。我實(shí)際設(shè)計(jì)中核心表大概有八張左右下面說(shuō)幾張關(guān)鍵的user表用戶賬號(hào)表字段id、username、password、role、student_id、class_id用一個(gè)role字段區(qū)分四種角色。密碼務(wù)必用MD5或者BCrypt加密千萬(wàn)別明文存。student表學(xué)生基本信息學(xué)號(hào)、姓名、性別、班級(jí)、專業(yè)、入學(xué)年份。這張表的學(xué)號(hào)字段建議設(shè)計(jì)成唯一索引但主鍵用自增id。class表班級(jí)信息班級(jí)名稱、專業(yè)、年級(jí)、輔導(dǎo)員id。evaluation_template表測(cè)評(píng)模板表配置名稱、學(xué)期、權(quán)重配置比如成績(jī)權(quán)重0.7、德育0.15、文體0.15。evaluation_item表測(cè)評(píng)項(xiàng)目表比如“學(xué)科競(jìng)賽獲獎(jiǎng)加分”“曠課扣分”“志愿服務(wù)加分”包含項(xiàng)目名稱、項(xiàng)目類型、默認(rèn)分值。這張表是加分申請(qǐng)的基礎(chǔ)。score_detail表分?jǐn)?shù)明細(xì)表也是整個(gè)系統(tǒng)數(shù)據(jù)量最大的一張表。核心字段有student_id、item_id、score正負(fù)值、reason、status待審核/通過/駁回、apply_time、audit_time、auditor_id。為什么單獨(dú)建表而不直接更新學(xué)生總分因?yàn)榭鄯趾图臃侄家勺匪菝骷?xì)留痕后總分可以通過SQL聚合算出也方便學(xué)生查看自己每一筆加分的來(lái)源。這里有一個(gè)常被問的數(shù)據(jù)一致性問題如果每筆加減分都實(shí)時(shí)更新student表里的總成績(jī)那么一旦某條申請(qǐng)被駁回或刪除總分就很可能算錯(cuò)。我的方案是“明細(xì)為準(zhǔn)總分可算”綜合測(cè)評(píng)總成績(jī)不落庫(kù)展示時(shí)通過聚合查詢動(dòng)態(tài)計(jì)算。數(shù)據(jù)量上來(lái)后性能有影響但畢設(shè)階段完全沒問題而且這個(gè)設(shè)計(jì)后來(lái)成了我答辯的一個(gè)亮點(diǎn)。3.3 關(guān)鍵索引與數(shù)據(jù)一致性索引設(shè)計(jì)看著不起眼但它是評(píng)判你數(shù)據(jù)庫(kù)功底的地方。我實(shí)際建表時(shí)重點(diǎn)加了這幾個(gè)索引score_detail表的student_id和status聯(lián)合索引用來(lái)加速查詢某個(gè)學(xué)生的明細(xì)和待審核數(shù)據(jù)evaluation_template表加一個(gè)semester唯一索引確保一個(gè)學(xué)期只有一個(gè)生效模板student表的class_id索引用來(lái)做班級(jí)維度的數(shù)據(jù)隔離查詢。說(shuō)到數(shù)據(jù)一致性的“怎么保證”這個(gè)在Java面試?yán)镆彩歉哳l題。落到這個(gè)項(xiàng)目里就是同一個(gè)學(xué)生同時(shí)提交多筆加分或者并發(fā)批量加分時(shí)分?jǐn)?shù)不能丟。我的做法分兩層數(shù)據(jù)庫(kù)層加唯一性約束比如“同一學(xué)期同一學(xué)生同一項(xiàng)目不能重復(fù)申請(qǐng)”防止按鈕狂點(diǎn)產(chǎn)生的重復(fù)數(shù)據(jù)應(yīng)用層給Service方法加Transactional事務(wù)注解保證審核通過時(shí)更新狀態(tài)和寫入流水在同一個(gè)事務(wù)里完成要么都成功要么都回滾。這里你最好能說(shuō)出事務(wù)的ACID特性尤其是原子性和一致性怎么在這個(gè)場(chǎng)景體現(xiàn)。4. 核心功能實(shí)現(xiàn)詳解4.1 綜合分計(jì)算邏輯的實(shí)現(xiàn)綜合測(cè)評(píng)系統(tǒng)最核心的算法其實(shí)就是加權(quán)求和。公式很簡(jiǎn)單綜合測(cè)評(píng)總分 學(xué)業(yè)成績(jī)分 × 學(xué)業(yè)權(quán)重 德育分 × 德育權(quán)重 文體分 × 文體權(quán)重。但落到代碼里難點(diǎn)在于“學(xué)業(yè)成績(jī)分從哪里來(lái)”。學(xué)生成績(jī)一般可以手動(dòng)錄入也可以通過Excel導(dǎo)入。我建議的做法是成績(jī)單獨(dú)立一張score表每次考核結(jié)束后管理員導(dǎo)入各科成績(jī)系統(tǒng)先用平均算法算出學(xué)生學(xué)期平均學(xué)分績(jī)點(diǎn)再映射成百分制的學(xué)業(yè)成績(jī)分。計(jì)算邏輯的代碼結(jié)構(gòu)可以用策略模式正好呼應(yīng)Java設(shè)計(jì)模式這個(gè)熱門考點(diǎn)。定義一個(gè)接口ScoreCalculator分別實(shí)現(xiàn)AcademicScoreCalculator、MoralScoreCalculator、SportsScoreCalculator模板里配置了哪些維度就調(diào)用對(duì)應(yīng)的計(jì)算器。這個(gè)設(shè)計(jì)的好處是學(xué)校以后新增一個(gè)“創(chuàng)新創(chuàng)業(yè)加分”維度加一個(gè)實(shí)現(xiàn)類比改一大段if-else強(qiáng)得多。如果你覺得策略模式有點(diǎn)重至少把維度計(jì)算拆到不同方法別用一個(gè)巨型方法寫到底。計(jì)算完成后的排名我用的是Java 8的Stream對(duì)列表流式排序按總分降序排同分時(shí)再按學(xué)業(yè)分排序。這里要小心一個(gè)細(xì)節(jié)排序時(shí)用Comparator.comparing然后用reversed()容易把比較器順序搞反。我踩過這個(gè)坑最后直接用lambda手寫比較邏輯反而一眼能看明白。4.2 加減分申請(qǐng)與審批節(jié)點(diǎn)加分不是一個(gè)學(xué)生填個(gè)分就完了必須有審批流。我做的流程是學(xué)生選擇加分項(xiàng)目、填理由、上傳圖片或證書附件提交后status為0待審核輔導(dǎo)員看到待審核列表點(diǎn)通過status變1分?jǐn)?shù)生效點(diǎn)駁回status變2并填寫駁回原因。這個(gè)流程雖然簡(jiǎn)單但覆蓋了RBAC權(quán)限模型和狀態(tài)機(jī)思想。你在答辯時(shí)可以畫一張狀態(tài)流轉(zhuǎn)表把“待審核→通過/駁回”兩個(gè)分支講清楚就不怕老師問審核邏輯。代碼實(shí)現(xiàn)上有兩個(gè)關(guān)鍵點(diǎn)。一個(gè)是權(quán)限控制學(xué)生只能看到和操作自己的申請(qǐng)輔導(dǎo)員能看到本班所有人的申請(qǐng)。這屬于行級(jí)權(quán)限我用MyBatis-Plus的Wrapper查詢?cè)赟ervice層傳入當(dāng)前登錄用戶的class_id來(lái)過濾。千萬(wàn)別把過濾條件寫在SQL之外不然數(shù)據(jù)就漏了。另一個(gè)是文件的處理上傳的附件建議存服務(wù)器本地目錄數(shù)據(jù)庫(kù)只存文件路徑命名用UUID加原始文件名避免中文名亂碼和重名覆蓋。如果想省事用OSS對(duì)象存儲(chǔ)也可以但畢設(shè)沒必要引入外部依賴。4.3 排名統(tǒng)計(jì)與可視化排名不能光出一個(gè)數(shù)字學(xué)生端至少要有“我的總分、班級(jí)排名、專業(yè)排名、各維度雷達(dá)圖或柱狀圖”老師端要有“班級(jí)整體分?jǐn)?shù)分布、加分項(xiàng)分布”。這部分我用ECharts實(shí)現(xiàn)后端只需要提供一個(gè)按班級(jí)或?qū)I(yè)聚合的統(tǒng)計(jì)接口比如統(tǒng)計(jì)班級(jí)各分?jǐn)?shù)段人數(shù)就寫一條帶Case When的SQL分組。數(shù)據(jù)聚合這塊我要提醒SQL聚合和Java內(nèi)存聚合是兩條路線。我建議簡(jiǎn)單的統(tǒng)計(jì)用SQL比如按班級(jí)平均值、按分?jǐn)?shù)段分組復(fù)雜的跨維度匯總比如計(jì)算某專業(yè)所有學(xué)生的綜合分并排名就用Java先查出明細(xì)再在內(nèi)存里算。這樣代碼好寫也好解釋。涉及到的Java基礎(chǔ)包括數(shù)組、List、Map轉(zhuǎn)換、Stream分組collectingAndThen這些答辯問起來(lái)你能巴拉巴拉說(shuō)半天。4.4 Excel批量導(dǎo)入導(dǎo)出綜合測(cè)評(píng)項(xiàng)目逃不開Excel。我做的時(shí)候用了Apache POI。很多Java面經(jīng)里都問“Java POI能不能操作圖表”答案是可以的POI的XSSFWorkbook能創(chuàng)建圖表但說(shuō)實(shí)話畢設(shè)階段用POI主要做三件事導(dǎo)入學(xué)生名單、導(dǎo)入成績(jī)、導(dǎo)出測(cè)評(píng)匯總表。圖表顯示用ECharts更高效沒必要在Excel里生成圖表。寫導(dǎo)入需要注意的點(diǎn)是先格式校驗(yàn)再逐行解析最后批量插入。格式校驗(yàn)包括必填字段為空、學(xué)號(hào)是不是數(shù)字、成績(jī)是否在0到100的范圍內(nèi)。千萬(wàn)別邊讀邊插不然數(shù)據(jù)量一大事務(wù)又長(zhǎng)又慢。用EasyExcel阿里開源的封裝會(huì)比原生POI省很多事一個(gè)ExcelProperty注解就能映射列名我這里直接用的EasyExcel它底層封裝了POI讀寫效率更高。我的做法是先上傳文件到臨時(shí)目錄再用EasyExcel的監(jiān)聽器逐行讀取最后一次性saveBatch到MySQL。這里有個(gè)Java數(shù)據(jù)類型的細(xì)節(jié)導(dǎo)入時(shí)Excel里的學(xué)號(hào)很容易變成科學(xué)計(jì)數(shù)法或丟掉末尾的0因?yàn)閱卧癖划?dāng)成數(shù)字類型。我處理的方式是Excel列上強(qiáng)制設(shè)置文本格式Java側(cè)用String接收然后用正則校驗(yàn)“學(xué)號(hào)必須為純數(shù)字”。這個(gè)問題不解決導(dǎo)入一萬(wàn)條數(shù)據(jù)九千個(gè)學(xué)號(hào)不對(duì)別問我怎么知道的。4.5 角色權(quán)限與數(shù)據(jù)隔離權(quán)限這塊雖然需求上只是不同角色看到不同菜單但代碼里不能只靠前端隱藏按鈕安全性在后端接口必須攔一遍。我用Spring Security加JWT做認(rèn)證和授權(quán)。登錄成功后返回token前端把token存在本地并放到Authorization頭里后端寫一個(gè)OncePerRequestFilter的JWT過濾器解析token拿到用戶角色再配合Spring Security的注解PreAuthorize(hasRole(ADMIN))控制接口訪問。數(shù)據(jù)隔離是按行做的學(xué)生只能查自己輔導(dǎo)員只能查本班學(xué)院管理員能查整個(gè)學(xué)院。最簡(jiǎn)單實(shí)現(xiàn)就是在各Service方法里根據(jù)當(dāng)前登錄用戶的角色拼裝條件。別小看這塊這是我實(shí)測(cè)遇到權(quán)限漏洞最多的地方。有次測(cè)試發(fā)現(xiàn)學(xué)生把請(qǐng)求里的studentId改成別人的學(xué)號(hào)竟然能查到別人的加分明細(xì)。所以不要信任前端傳來(lái)的任何id一律從token的上下文中取當(dāng)前用戶id這是行級(jí)權(quán)限安全的鐵律。5. 實(shí)測(cè)高頻問題與排查記錄5.1 并發(fā)加分時(shí)分?jǐn)?shù)莫名丟失有一個(gè)很經(jīng)典的并發(fā)問題同一學(xué)生同時(shí)提交了兩筆加分后端查出來(lái)都是當(dāng)前總分90分各自加上0.5分后寫回結(jié)果總分變成90.5而不是91。我剛開始做的時(shí)候還沒用“明細(xì)為準(zhǔn)”方案直接在student表維護(hù)totalScore字段就踩了這個(gè)坑。解決方式正如前面所說(shuō)改為明細(xì)表準(zhǔn)實(shí)時(shí)聚合外加對(duì)同一student_id的申請(qǐng)使用數(shù)據(jù)庫(kù)唯一索引兜底這樣并發(fā)重復(fù)數(shù)據(jù)直接被數(shù)據(jù)庫(kù)擋住業(yè)務(wù)層根本不用加分布式鎖。這也是為什么我在3.2節(jié)強(qiáng)調(diào)“總分不落庫(kù)”這個(gè)設(shè)計(jì)省了很多事。排查這類問題我有兩個(gè)小技巧第一把所有加減分寫操作都打上日志包括操作者、操作時(shí)間、變更前值、變更后值一把鎖一條鏈第二MySQL開general_log看實(shí)際執(zhí)行的SQL順序基本能還原現(xiàn)場(chǎng)。說(shuō)實(shí)話畢設(shè)階段的并發(fā)量很難觸發(fā)這類問題但答辯老師很愛問“你如何保證數(shù)據(jù)的一致性”你要是能把這個(gè)真實(shí)踩坑和解決過程講出來(lái)印象分會(huì)高不少。5.2 學(xué)號(hào)當(dāng)主鍵的坑一開始圖省事我把學(xué)號(hào)直接當(dāng)成student表主鍵結(jié)果問題接二連三學(xué)號(hào)有變更需求轉(zhuǎn)專業(yè)、復(fù)學(xué)、外鍵關(guān)聯(lián)數(shù)據(jù)一大片要跟著改、數(shù)據(jù)庫(kù)性能在關(guān)聯(lián)查詢時(shí)也沒自增id友好。后來(lái)還是改成自增主鍵學(xué)號(hào)改成一個(gè)唯一索引的業(yè)務(wù)字段。這是個(gè)典型的設(shè)計(jì)教訓(xùn)業(yè)務(wù)主鍵和代理主鍵要分開學(xué)號(hào)是業(yè)務(wù)上唯一自然存在的代碼負(fù)責(zé)展示和檢索自增id負(fù)責(zé)關(guān)系穩(wěn)定性誰(shuí)也不要越界管誰(shuí)的事。還有一個(gè)連帶坑Java實(shí)體類里L(fēng)ong類型id在某些前端框架下會(huì)丟失精度超過JavaScript安全數(shù)范圍導(dǎo)致更新操作誤傷別的數(shù)據(jù)。解決方式是讓JSON序列化時(shí)把Long轉(zhuǎn)成StringJackson加一個(gè)ToStringSerializer全局配置就行。這個(gè)細(xì)節(jié)不處理前端刪除或修改時(shí)可能出現(xiàn)“id一直對(duì)不上”的詭異bug。5.3 事務(wù)注解失效的場(chǎng)景在同一個(gè)類里調(diào)用另一個(gè)帶Transactional的方法事務(wù)會(huì)失效因?yàn)镾pring事務(wù)走的是代理機(jī)制同類內(nèi)部調(diào)用直接走原對(duì)象不會(huì)經(jīng)過代理。我寫過一段“審核批量通過”的代碼scoresService的一個(gè)方法里循環(huán)調(diào)用了同類里的auditOne方法結(jié)果中間有一條審核出錯(cuò)前面成功的記錄居然沒回滾。排查了半天最后把批量操作抽到另一個(gè)Service類里或者用TransactionTemplate手動(dòng)管理事務(wù)才解決。另一個(gè)易忽略的問題是事務(wù)加在Controller上把HTTP請(qǐng)求的整個(gè)生命周期都給包進(jìn)事務(wù)了。如果這個(gè)接口里既做了Excel解析又批量寫庫(kù)事務(wù)時(shí)間過長(zhǎng)會(huì)導(dǎo)致數(shù)據(jù)庫(kù)連接被長(zhǎng)時(shí)間占用高一點(diǎn)并發(fā)就報(bào)連接池耗盡。我的原則是事務(wù)只放在真正需要原子性的寫操作上查詢和文件解析別放在事務(wù)里尤其大文件導(dǎo)入先解析校驗(yàn)再開事務(wù)批量寫全程不要在一個(gè)大事務(wù)里鎖太久。5.4 前后端聯(lián)調(diào)與部署前后端分離的項(xiàng)目聯(lián)調(diào)期最煩的兩個(gè)問題跨域和token失效??缬蛭矣玫姆桨甘呛蠖思覥orsFilter配置允許前端地址的跨域請(qǐng)求同時(shí)允許攜帶Authorization頭。這里有個(gè)容易踩的坑添加了自定義header后allowedHeaders必須顯式包含Authorization不然前端每次請(qǐng)求都報(bào)CORS錯(cuò)誤而你后端明明已經(jīng)配置了跨域。本地開發(fā)時(shí)我建議直接給Vite配代理.server.proxy把/api轉(zhuǎn)發(fā)到localhost:8080這樣能做到前端代碼里寫相對(duì)路徑上線后不用改任何接口地址。部署時(shí)最省心的是打jar包用java -jar跑再把前端dist目錄讓Nginx托管Nginx里配一個(gè)/api反向代理到后端端口。這個(gè)“前后端一體部署”方案比單獨(dú)部署兩個(gè)服務(wù)省心太多一個(gè)服務(wù)器一個(gè)Nginx服務(wù)全搞定。部署前記得數(shù)據(jù)庫(kù)的時(shí)區(qū)參數(shù)要配置成serverTimezoneAsia/Shanghai不然日期字段差8個(gè)小時(shí)。5.5 定時(shí)任務(wù)與系統(tǒng)擴(kuò)展綜合測(cè)評(píng)里有個(gè)需求很容易被忽略每學(xué)期結(jié)束后系統(tǒng)要重新生成新一輪測(cè)評(píng)周期把上一年的加分?jǐn)?shù)據(jù)歸檔。這個(gè)功能我用了Spring的Scheduled注解寫一個(gè)每天凌晨掃一次的歸檔任務(wù)把過期學(xué)期的score_detail遷移到history_score_detail表。Java定時(shí)任務(wù)框架這塊也是面試常問你用Scheduled就夠不用為了體現(xiàn)技術(shù)深度硬上Quartz或XXL-Job。不過你要能說(shuō)清楚Scheduled默認(rèn)單線程多個(gè)任務(wù)會(huì)互相阻塞需要配置線程池或者用Async注解讓任務(wù)異步執(zhí)行這個(gè)點(diǎn)講出來(lái)老師會(huì)覺得你有真實(shí)項(xiàng)目經(jīng)驗(yàn)。6. 從課題到答辯講清楚比做完更重要6.1 論文結(jié)構(gòu)與寫作順序很多同學(xué)代碼寫完了論文排版一塌糊涂。我的論文結(jié)構(gòu)可以給你參考第一章緒論寫背景與意義從高校素質(zhì)教育、評(píng)獎(jiǎng)評(píng)優(yōu)的實(shí)際痛點(diǎn)切入第二章需求分析把角色、用例圖、數(shù)據(jù)流圖放進(jìn)去第三章系統(tǒng)設(shè)計(jì)包括總體架構(gòu)、功能模塊圖和數(shù)據(jù)庫(kù)E-R圖第四章系統(tǒng)實(shí)現(xiàn)按模塊貼核心代碼并配運(yùn)行截圖第五章測(cè)試寫功能測(cè)試用例和結(jié)果最后是總結(jié)與展望。注意一個(gè)原則論文里的代碼不要長(zhǎng)段貼只貼關(guān)鍵方法每段代碼后面要有三到五句解釋說(shuō)明這段代碼實(shí)現(xiàn)了什么邏輯解決了什么問題。選題背景里不要空談“隨著計(jì)算機(jī)技術(shù)的發(fā)展”我建議落點(diǎn)放在“高校學(xué)生綜合測(cè)評(píng)是學(xué)生工作管理的重要環(huán)節(jié)傳統(tǒng)手動(dòng)統(tǒng)計(jì)方式存在標(biāo)準(zhǔn)不統(tǒng)一、數(shù)據(jù)分散、公示不透明等問題設(shè)計(jì)一套在線測(cè)評(píng)系統(tǒng)可以提升效率并規(guī)范流程”這樣一句就點(diǎn)題了。6.2 演示時(shí)的功能動(dòng)線答辯演示不超過十分鐘挑四個(gè)高光功能就夠了。我的演示動(dòng)線是先登錄管理員賬號(hào)進(jìn)測(cè)評(píng)模板配置頁(yè)演示設(shè)置學(xué)業(yè)、德育、文體的權(quán)重然后切到學(xué)生賬號(hào)提交一個(gè)比賽獲獎(jiǎng)加分并上傳附件再切回輔導(dǎo)員賬號(hào)通過這條申請(qǐng)最后回到排名頁(yè)看綜合分變動(dòng)和排名刷新。這個(gè)過程完整覆蓋了錄入、申請(qǐng)、審核、計(jì)算、排名五件事邏輯閉環(huán)老師一看就懂。排名出來(lái)之后再用ECharts的班級(jí)分?jǐn)?shù)分布圖展示可視化然后導(dǎo)出Excel匯總表展示POI的實(shí)際效果。這些功能我在演示前會(huì)準(zhǔn)備一個(gè)專用測(cè)試賬號(hào)和幾套固定數(shù)據(jù)避免現(xiàn)場(chǎng)臨時(shí)錄數(shù)據(jù)翻車??貓?chǎng)的要點(diǎn)是先講“邊界”——我是誰(shuí)我在這個(gè)系統(tǒng)里能做什么再講“閉環(huán)”——一條加分從申請(qǐng)到展示的全鏈路最后講“保障”——權(quán)限、事務(wù)、異常怎么處理。6.3 可擴(kuò)展方向如果論文還想加創(chuàng)新點(diǎn)有幾個(gè)方向可以參考一是引入學(xué)生綜合評(píng)價(jià)畫像用雷達(dá)圖展示學(xué)生在學(xué)業(yè)、德育、體育、創(chuàng)新能力各維度的優(yōu)劣勢(shì)二是增加輔導(dǎo)員批量審核支持勾選多條申請(qǐng)一鍵通過三是做消息通知加分申請(qǐng)通過后站內(nèi)信或郵件通知學(xué)生四是把成績(jī)導(dǎo)出功能擴(kuò)展成按模板定制報(bào)表不同學(xué)院可以配置不同的導(dǎo)出格式。這些擴(kuò)展未必都做出來(lái)但在論文的“展望”部分寫出來(lái)答辯時(shí)會(huì)顯得你想過系統(tǒng)后續(xù)怎么演進(jìn)。萬(wàn)一老師問“你這個(gè)系統(tǒng)還能怎么改進(jìn)”你有話可接而不是憋出一句“還可以加個(gè)手機(jī)App”。做了這個(gè)完整項(xiàng)目我個(gè)人最大的體會(huì)是綜合測(cè)評(píng)系統(tǒng)的難點(diǎn)不在技術(shù)而在“把模糊的業(yè)務(wù)規(guī)則變成清晰的代碼邏輯”。你需要跟真實(shí)的用戶輔導(dǎo)員、學(xué)工辦老師去聊搞清楚他們到底怎么算分、怎么審核、怎么排查爭(zhēng)議然后才能設(shè)計(jì)出能用的表結(jié)構(gòu)和流程。Java技術(shù)的每一環(huán)都有明確的業(yè)內(nèi)標(biāo)準(zhǔn)解法——Spring Boot讓工程搭建變簡(jiǎn)單MyBatis-Plus讓數(shù)據(jù)庫(kù)操作變高效JWT加攔截器保障了權(quán)限安全POI處理了文件流轉(zhuǎn)。這些都是我在這個(gè)課題里親手試錯(cuò)后沉淀下來(lái)的經(jīng)驗(yàn)照著走你大概率能順很多。遇到項(xiàng)目里某個(gè)細(xì)節(jié)不確定時(shí)別急著抄代碼先想想“這條數(shù)據(jù)從哪來(lái)、到哪去、誰(shuí)能改、改完影響什么”想通了代碼自然而然就寫出來(lái)了。