源碼解析:題庫到自動(dòng)判分全流程)
1. 項(xiàng)目概述與技術(shù)定位1.1 這套系統(tǒng)解決的是什么場(chǎng)景問題說起SSM入黨積極分子考試模擬系統(tǒng)很多Java學(xué)習(xí)者第一反應(yīng)是“又是一套課程設(shè)計(jì)”。但實(shí)際接觸下來這套系統(tǒng)的業(yè)務(wù)邏輯其實(shí)比常見的學(xué)生管理系統(tǒng)、圖書管理系統(tǒng)要復(fù)雜得多因?yàn)樗瑫r(shí)牽扯到題庫管理、自動(dòng)組卷、在線答題、自動(dòng)判分、成績(jī)統(tǒng)計(jì)這幾條完整的業(yè)務(wù)線幾乎把SSM框架在Web開發(fā)中的典型用法全部覆蓋了。項(xiàng)目標(biāo)題里寫了“附源碼”我拿到這套源碼時(shí)第一感受是它不像很多網(wǎng)上流傳的“半成品課設(shè)”運(yùn)行起來就能看到一個(gè)完整體驗(yàn)閉環(huán)。登錄后有三類角色視角管理員維護(hù)題庫和用戶考生在線做題并查看成績(jī)系統(tǒng)自動(dòng)完成抽題和評(píng)分。麻雀雖小五臟俱全。這套系統(tǒng)適合誰三類人最對(duì)口一是正在做Java課程設(shè)計(jì)、需要快速落地一個(gè)完整項(xiàng)目的在校生二是想把SSM框架的CRUD、攔截器、動(dòng)態(tài)SQL、事務(wù)管理完整過一遍的初學(xué)者三是工作中臨時(shí)需要搭一個(gè)“輕量級(jí)在線考試/答題平臺(tái)”做內(nèi)部培訓(xùn)用的開發(fā)者。1.2 為什么選SSM這套技術(shù)棧放到2025年來看Spring Boot已經(jīng)成了Java后端開發(fā)的事實(shí)標(biāo)準(zhǔn)為什么還要回頭用SSM原因很現(xiàn)實(shí)高校課程設(shè)計(jì)、畢業(yè)設(shè)計(jì)的教學(xué)大綱里大量專業(yè)仍然以SSM為主講框架Spring Boot是“提高篇”的內(nèi)容。更重要的是SSM框架的配置過程完全手動(dòng)你能真正看清Spring容器是怎么啟動(dòng)的、SpringMVC是怎么把請(qǐng)求分發(fā)到Controller的、MyBatis是怎么把Mapper接口和XML映射文件綁定在一起的。這套源碼里這些配置全部暴露在你面前是理解“框架底層原理”的絕佳教材。技術(shù)選型方面還藏著一個(gè)務(wù)實(shí)考量這套系統(tǒng)用JSP做視圖層用Bootstrap做前端布局?,F(xiàn)在很多項(xiàng)目前后端分離但在考試系統(tǒng)這種典型的管理系統(tǒng)中JSPJSTL的服務(wù)端渲染反而有優(yōu)勢(shì)——頁面直出、代碼直觀、調(diào)試簡(jiǎn)單而且部署時(shí)只需要一個(gè)Tomcat就能跑起來不需要Node環(huán)境、不需要跨域配置。對(duì)初學(xué)者來說這顯然是一條更友好的路線。注意對(duì)于想直接用于生產(chǎn)環(huán)境的開發(fā)者這套系統(tǒng)的定位是“教學(xué)參考 業(yè)務(wù)原型”。它的業(yè)務(wù)邏輯完整但并發(fā)能力、安全防護(hù)都還是課程設(shè)計(jì)級(jí)別直接商用需要做不少加固這一點(diǎn)在后面章節(jié)我會(huì)具體說。2. 系統(tǒng)核心模塊拆解與業(yè)務(wù)設(shè)計(jì)思路2.1 登錄認(rèn)證與多角色權(quán)限控制系統(tǒng)的入口是登錄認(rèn)證這里并沒有引入Spring Security或者Shiro這種重量級(jí)安全框架而是用SpringMVC的攔截器HandlerInterceptor手動(dòng)實(shí)現(xiàn)了會(huì)話管理和權(quán)限校驗(yàn)。我看到代碼的時(shí)候覺得這個(gè)設(shè)計(jì)很聰明——對(duì)課程設(shè)計(jì)來說引入Shiro會(huì)大大增加配置成本而且容易讓評(píng)委覺得“這塊不是你寫的”而手動(dòng)實(shí)現(xiàn)攔截器反而能把登錄校驗(yàn)、角色判斷的邏輯清清楚楚地展示在代碼里。具體邏輯是這樣的用戶提交賬號(hào)密碼后Controller調(diào)用Service層通過MyBatis查詢數(shù)據(jù)庫比對(duì)密碼成功后把用戶對(duì)象寫入Session攔截器攔截所有以/admin/和/user/開頭的請(qǐng)求從Session中取出用戶對(duì)象取不到就重定向到登錄頁。管理員訪問考生接口時(shí)還會(huì)做一次角色校驗(yàn)防止低權(quán)限用戶繞過菜單直接拼接URL訪問管理接口。這里有個(gè)細(xì)節(jié)值得表揚(yáng)攔截器配置里用mvc:exclude-mapping白名單方式放行了登錄頁、登錄請(qǐng)求和靜態(tài)資源CSS、JS、圖片避免了用戶沒登錄時(shí)頁面樣式全部丟失的問題。很多初學(xué)者寫攔截器時(shí)會(huì)把靜態(tài)資源一起攔住導(dǎo)致登錄頁白屏還以為是CSS路徑寫錯(cuò)了花大量時(shí)間排查——這個(gè)問題我在第5部分還會(huì)專門提到。2.2 題庫管理模塊的設(shè)計(jì)要點(diǎn)題庫管理是整個(gè)系統(tǒng)中業(yè)務(wù)含量最高的模塊。它要解決的不只是“增刪改查”四個(gè)操作而是面對(duì)“選擇題、判斷題、多選題”三種題型時(shí)如何設(shè)計(jì)數(shù)據(jù)表結(jié)構(gòu)才能讓存儲(chǔ)和檢索都高效。這套源碼里題庫表的設(shè)計(jì)字段大致為題目類型、題干內(nèi)容、選項(xiàng)A/B/C/D、正確答案、所屬章節(jié)或者知識(shí)點(diǎn)分類、難度系數(shù)、創(chuàng)建時(shí)間。把所有題型的公共字段放在一張表里選項(xiàng)字段用四個(gè)可空字段保存——這種設(shè)計(jì)在考試系統(tǒng)里非常常見因?yàn)樗鼔蚝?jiǎn)單查詢快適合題量不大的場(chǎng)景。如果題目量大、題型復(fù)雜那就得考慮“試卷主表 試題明細(xì)表”的范式化設(shè)計(jì)但課程設(shè)計(jì)用單表完全夠。題庫管理的操作細(xì)節(jié)也做了不少管理員可以按題型篩選、按章節(jié)篩選可以根據(jù)關(guān)鍵字模糊搜索題干可以點(diǎn)擊編輯直接修改題目?jī)?nèi)容。批量導(dǎo)入功能是通過讀取Excel文件實(shí)現(xiàn)的但源碼里用的是POI操作 —— 這個(gè)功能在演示答辯時(shí)是個(gè)亮點(diǎn)可以讓管理員快速導(dǎo)入幾十道題目效果非常直觀。2.3 自動(dòng)組卷與答題判分邏輯組卷策略是這套系統(tǒng)最核心的亮點(diǎn)。它不是手工指定試卷中的每道題而是管理員只配置“總題數(shù)、各題型數(shù)量、各章節(jié)題目數(shù)”這幾個(gè)參數(shù)系統(tǒng)自動(dòng)從題庫中隨機(jī)抽題組卷。這個(gè)邏輯在考試系統(tǒng)里叫“隨機(jī)抽題組卷”實(shí)現(xiàn)上就是用SQL的ORDER BY RAND()按條件抽取指定數(shù)量的題目。我看到源碼里組卷的Service實(shí)現(xiàn)時(shí)發(fā)現(xiàn)它沒有用一條復(fù)雜SQL完成所有抽取而是拆成了三次查詢先抽選擇題再抽判斷題最后抽多選題每次查詢都帶有章節(jié)、題型、數(shù)量的條件拼接。這種做法的好處是組卷邏輯清晰哪一步出錯(cuò)了能馬上定位缺點(diǎn)是每次組卷都會(huì)多次訪問數(shù)據(jù)庫。對(duì)課程設(shè)計(jì)場(chǎng)景這完全不是問題因?yàn)榭荚嚾藬?shù)少、并發(fā)低。如果以后要支撐大規(guī)模考試這個(gè)環(huán)節(jié)就要改成緩存題目ID列表內(nèi)存中完成抽題。判分邏輯同樣不復(fù)雜但很嚴(yán)謹(jǐn)考生提交答題結(jié)果后系統(tǒng)逐題比對(duì)答案。選擇題和判斷題直接比對(duì)字符串多選題需要先按選項(xiàng)排序再比對(duì)——這是多選題判分最容易踩坑的地方不排序就比對(duì)明明選對(duì)了因?yàn)檫x項(xiàng)順序不同就被判錯(cuò)。在這里我看到源碼里專門對(duì)用戶提交的多選答案做了split之后重新排序拼接的處理說明作者踩過這個(gè)坑。2.4 考試過程控制細(xì)節(jié)考試模塊里還有幾個(gè)容易被忽略但影響體驗(yàn)的細(xì)節(jié)倒計(jì)時(shí)提醒、自動(dòng)交卷、防重復(fù)提交。倒計(jì)時(shí)實(shí)現(xiàn)用的是前端JavaScript的定時(shí)器從考試開始時(shí)間計(jì)算剩余時(shí)間剩余5分鐘時(shí)彈出提示時(shí)間歸零后自動(dòng)觸發(fā)交卷操作。自動(dòng)交卷的本質(zhì)是調(diào)用提交接口并帶上剩余時(shí)間為0的參數(shù)后端再做一次冪等校驗(yàn)同一個(gè)考生對(duì)同一張?jiān)嚲淼奶峤徽?qǐng)求只接受第一次后續(xù)重復(fù)提交直接拒絕。這個(gè)“防重復(fù)提交”邏輯在后端是用數(shù)據(jù)庫里的考試記錄表加唯一約束實(shí)現(xiàn)的我看到實(shí)現(xiàn)時(shí)覺得比純粹用Session標(biāo)記更可靠。3. 數(shù)據(jù)庫設(shè)計(jì)與核心表結(jié)構(gòu)3.1 數(shù)據(jù)表總體規(guī)劃這套系統(tǒng)的數(shù)據(jù)庫一共設(shè)計(jì)了6張核心表我把它整理成下面的對(duì)應(yīng)關(guān)系表名用途關(guān)鍵字段sys_user用戶表id、username、password、real_name、roletb_question題庫表id、q_type、content、option_a~d、answer、chaptertb_paper試卷表id、paper_name、total_score、duration、create_timetb_paper_question試卷題目關(guān)聯(lián)表id、paper_id、question_idtb_exam_record考試記錄表id、user_id、paper_id、score、status、exam_timetb_notice公告表id、title、content、create_time這個(gè)表結(jié)構(gòu)設(shè)計(jì)整體上是合理的。單表題庫在題量不超過幾千條時(shí)性能沒有問題但如果你要往里面塞上萬道題每次隨機(jī)抽題的ORDER BY RAND()就會(huì)明顯變慢——那就要考慮分表或者改用其他抽題策略。試卷題目關(guān)聯(lián)表是關(guān)鍵設(shè)計(jì)一張?jiān)嚲砗皖}目之間是多對(duì)多關(guān)系必須通過中間表來關(guān)聯(lián)。3.2 建表語句中的幾個(gè)經(jīng)典寫法源碼中的建表SQL里有幾個(gè)寫法值得拿出來說一下。sys_user表的密碼字段直接以明文存儲(chǔ)——這在教學(xué)項(xiàng)目里可以接受但生產(chǎn)環(huán)境必須用MD5加鹽或者BCrypt加密。我看到源碼的數(shù)據(jù)庫腳本時(shí)專門留意了一下所有表的id字段都用了AUTO_INCREMENT沒有用UUID這樣做JOIN查詢時(shí)性能更好索引占用也更小。時(shí)間字段統(tǒng)一用DATETIME類型避免了TIMESTAMP在2038年之后溢出問題——雖然對(duì)課程設(shè)計(jì)來說根本運(yùn)行不到那一年但習(xí)慣要養(yǎng)好。題庫表的答案字段設(shè)計(jì)有講究。單選題直接存“A”判斷題存“對(duì)/錯(cuò)”多選題存“ABD”這種拼接字符串。存字符串的最大好處是判分時(shí)直接比對(duì)不需要復(fù)雜的位運(yùn)算或JSON解析。代價(jià)就是統(tǒng)計(jì)每道題的選項(xiàng)分布時(shí)會(huì)稍微麻煩一點(diǎn)但對(duì)考試場(chǎng)景來說利遠(yuǎn)大于弊。3.3 索引設(shè)計(jì)與查詢優(yōu)化點(diǎn)這套源碼里最核心的查詢場(chǎng)景有三個(gè)用戶根據(jù)用戶名登錄、組卷時(shí)按條件抽題、考試記錄按用戶查詢。我們看到對(duì)應(yīng)的三個(gè)字段——username、question表的題型字段、exam_record表的user_id——都建了索引。username建索引是登錄查詢的剛需question表的題型字段建索引能加快組卷篩選exam_record表的user_id建索引則讓“我的考試成績(jī)”頁面響應(yīng)更快。經(jīng)驗(yàn)之談?wù)n程設(shè)計(jì)答辯時(shí)老師經(jīng)常問“你為什么給這些字段建索引”。把這三個(gè)查詢場(chǎng)景說出來比背一百遍索引原理都管用。建索引不是越多越好而是要把“查詢瓶頸在哪里”想清楚。4. 關(guān)鍵代碼實(shí)現(xiàn)與業(yè)務(wù)邏輯解讀4.1 組卷算法的完整邏輯鏈組卷是整個(gè)系統(tǒng)里最值得研究的代碼。管理員填寫組卷參數(shù)后后端收到的請(qǐng)求包含三個(gè)關(guān)鍵參數(shù)試卷名稱、考試時(shí)長(zhǎng)、以及一個(gè)“題目配置”列表。這個(gè)列表的結(jié)構(gòu)類似于JSON數(shù)組每項(xiàng)包含題型、章節(jié)、數(shù)量三個(gè)值。Service層拿到參數(shù)后的處理步驟完全符合我上面說的“多次查詢”模式根據(jù)試卷名稱創(chuàng)建試卷主記錄拿到自增ID。遍歷題目配置列表每次查出該章節(jié)、該題型的全部題目ID。如果可用題目數(shù) 需要抽取數(shù)就隨機(jī)取指定數(shù)量的ID如果不夠就直接取出全部并提示管理員。雙重循環(huán)保存試卷題目關(guān)聯(lián)記錄。這套邏輯里最容易被忽略的是第二步里“可用題數(shù)不足”的處理。源碼里做了數(shù)量校驗(yàn)并返回友好提示比如“《第一章 黨的性質(zhì)》單選題數(shù)量不足需要10道當(dāng)前僅有4道”這種提示在實(shí)際使用中非常有用而不是直接拋空指針異常。給管理員的提示越具體系統(tǒng)就越像個(gè)正經(jīng)產(chǎn)品——很多課設(shè)項(xiàng)目死就死在報(bào)錯(cuò)信息太籠統(tǒng)上。4.2 答題提交與自動(dòng)判分的核心思路考生端交卷時(shí)前端把答題數(shù)據(jù)組裝成一個(gè)Map結(jié)構(gòu)傳給后端Map的key是題目IDvalue是用戶提交的答案。后端判分Service的流程是for (Long questionId : submittedAnswers.keySet()) { 根據(jù) questionId 查出題目對(duì)象 獲取標(biāo)準(zhǔn)答案 standardAnswer 獲取用戶答案 userAnswer 如果題型是多選題將 userAnswer 按逗號(hào)拆分后重新排序再拼接 判斷 userAnswer.equals(standardAnswer) 累加得分 }這段代碼本身不復(fù)雜但有幾個(gè)細(xì)節(jié)值得學(xué)習(xí)。首先是判分時(shí)只遍歷用戶提交的答案而不是遍歷全卷所有題目這樣可以避免考生漏答時(shí)系統(tǒng)還要逐一判斷“是否未答”的額外開銷。其次是多選排序再比對(duì)這個(gè)我在第2部分已經(jīng)提過這里就不再重復(fù)。最后是總分計(jì)算用的是整數(shù)累加沒有引入浮點(diǎn)運(yùn)算避免出現(xiàn)分?jǐn)?shù)精度問題。4.3 攔截器與公共資源的處理SpringMVC攔截器配置是SSM框架里最容易出錯(cuò)的環(huán)節(jié)之一。這套源碼的配置方式很典型我在spring-mvc.xml里看到的攔截器注冊(cè)代碼如下mvc:interceptors mvc:interceptor mvc:mapping path/admin/**/ mvc:mapping path/user/**/ mvc:exclude-mapping path/login/ mvc:exclude-mapping path/loginCheck/ mvc:exclude-mapping path/static/**/ /mvc:interceptor /mvc:interceptors這里mvc:exclude-mapping的路徑配置非常關(guān)鍵。static目錄放的是CSS、JS、圖片資源如果攔截器把這個(gè)目錄攔住了就會(huì)造成登錄頁打開后沒有任何樣式頁面呈現(xiàn)純HTML堆疊。很多初學(xué)者在這個(gè)問題上耗掉大量時(shí)間——甚至有人會(huì)誤以為是Bootstrap的CDN鏈接失效了其實(shí)問題出在攔截器路徑上。還有一個(gè)容易踩坑的地方是mvc:default-servlet-handler/的配置。SSM項(xiàng)目中如果沒有配置這個(gè)處理器SpringMVC的 DispatcherServlet 會(huì)攔截所有請(qǐng)求導(dǎo)致靜態(tài)資源404。源碼里在spring-mvc.xml中明確配置了靜態(tài)資源放行規(guī)則這一點(diǎn)做得很規(guī)范。4.4 前端Bootstrap表格與模態(tài)框交互前端方面這套源碼用了BootStrap 3的柵格系統(tǒng)和組件庫。管理員的題庫管理頁面中題目列表用表格展示每一行后面有“編輯”和“刪除”按鈕。編輯操作彈出模態(tài)框模態(tài)框里的表單通過jQuery的AJAX提交到后端。不用跳轉(zhuǎn)新頁面而是在當(dāng)前頁面完成數(shù)據(jù)更新交互體驗(yàn)明顯比傳統(tǒng)的表單提交順暢。這種“列表模態(tài)框”的模式是管理系統(tǒng)的標(biāo)配源碼里用得非常熟練。Ajax提交成功后重新加載表格數(shù)據(jù)失敗則彈出錯(cuò)誤提示——這段JS代碼也值得初學(xué)者逐行研究它是理解jQuery與后端交互的標(biāo)準(zhǔn)范式。雖然jQuery已經(jīng)過了最火的時(shí)期但在這種管理系統(tǒng)里依然有巨大的存量掌握它沒有壞處。5. 部署運(yùn)行與環(huán)境選配5.1 開發(fā)環(huán)境版本對(duì)應(yīng)關(guān)系我實(shí)際部署測(cè)試時(shí)發(fā)現(xiàn)這套源碼對(duì)環(huán)境版本非常敏感尤其是Tomcat版本和JDK版本。按照項(xiàng)目文檔里的要求推薦的環(huán)境組合是組件推薦版本備注JDK1.8高版本JDK可能出現(xiàn)JSP編譯問題Eclipse2019~2021版本新版Eclipse需自配Tomcat運(yùn)行時(shí)Tomcat8.5 或 9.0Tomcat 10以上包名全改跑不了這套代碼MySQL5.7 或 8.0注意連接驅(qū)動(dòng)版本Maven3.6用于管理依賴Jar包我特別強(qiáng)調(diào)一下Tomcat版本問題。Tomcat 10開始Java EE的包名從javax.*換成了jakarta.*而SSM框架時(shí)期的代碼全部基于javax.servlet編寫換用Tomcat 10以上直接編譯不通過。所以我強(qiáng)烈建議直接用Tomcat 8.5服務(wù)器版本選對(duì)了能避免一半的部署問題。MySQL注意要用8.0版本的話驅(qū)動(dòng)不能再用老的com.mysql.jdbc.Driver得換成com.mysql.cj.jdbc.Driver同時(shí)JDBC連接串要加上serverTimezoneAsia/Shanghai否則會(huì)出現(xiàn)時(shí)區(qū)報(bào)錯(cuò)。源碼里如果寫的是老驅(qū)動(dòng)你部署時(shí)手動(dòng)改一下jdbc.properties文件就行。5.2 從源碼導(dǎo)入到跑通全流程基于我實(shí)際操作的順序從拿到源碼到系統(tǒng)跑起來步驟如下用Eclipse的File - Import - Maven - Existing Maven Projects導(dǎo)入源碼目錄。在MySQL中新建數(shù)據(jù)庫把源碼自帶的.sql腳本導(dǎo)入。修改jdbc.properties文件中的數(shù)據(jù)庫連接地址、用戶名、密碼。右鍵項(xiàng)目 -Run As - Maven Clean再執(zhí)行Maven Install讓依賴全部下載完畢。在Eclipse里配置Tomcat 8.5運(yùn)行時(shí)環(huán)境把項(xiàng)目添加到Tomcat中啟動(dòng)。啟動(dòng)成功后訪問http://localhost:8080/項(xiàng)目名/自動(dòng)跳轉(zhuǎn)到登錄頁。默認(rèn)管理員賬號(hào)和密碼一般是寫在sql腳本里的比如admin/admin123之類的組合。如果登錄不進(jìn)去直接查數(shù)據(jù)庫sys_user表手動(dòng)改一條記錄出來就能用。5.3 常見NoClassDefFoundError排查很多讀者私信問我為什么Maven項(xiàng)目明明已經(jīng)Maven Install成功了啟動(dòng)Tomcat時(shí)還是報(bào)NoClassDefFoundError報(bào)錯(cuò)信息里提到了某個(gè)Jar包里的類找不到。這個(gè)問題的根源在Eclipse的老毛病沒有把Maven依賴部署到Tomcat的發(fā)布目錄中。解決方法很簡(jiǎn)單右鍵項(xiàng)目 -Properties - Deployment Assembly - Add - Java Build Path Entries - Maven Dependencies。添加完成后重新發(fā)布到Tomcat問題就徹底消失了。這屬于Eclipse Maven Tomcat組合的老牌坑SSM時(shí)代幾乎人人都會(huì)遇到一次第一次遇到時(shí)完全摸不著頭腦后來發(fā)現(xiàn)就是一行配置的事。6. 常見問題梳理與避坑經(jīng)驗(yàn)6.1 部署運(yùn)行中典型報(bào)錯(cuò)的排查清單我把實(shí)際測(cè)試這套源碼時(shí)遇到的和網(wǎng)上反饋?zhàn)疃嗟膯栴}做了一個(gè)速查表方便大家直接對(duì)號(hào)入座報(bào)錯(cuò)現(xiàn)象根本原因解決方案訪問所有頁面都報(bào)404項(xiàng)目發(fā)布名不對(duì)或靜態(tài)資源被攔截檢查訪問URL中的項(xiàng)目名檢查攔截器放行路徑數(shù)據(jù)庫連接失敗提示Communications link failureMySQL地址/端口/密碼錯(cuò)誤或驅(qū)動(dòng)版本不匹配核對(duì)jdbc.properties配置確認(rèn)MySQL服務(wù)已啟動(dòng)頁面能開但CSS全部丟失攔截器攔截了static目錄在spring-mvc.xml放行/static/**啟動(dòng)時(shí)Tomcat報(bào)內(nèi)存溢出默認(rèn)堆內(nèi)存不足在catalina.bat中加JAVA_OPTS-Xms256m -Xmx512m中文亂碼JSP編碼與數(shù)據(jù)庫字符集不一致統(tǒng)一UTF-8修改MySQL連接串加characterEncodingutf8添加題目時(shí)提示空指針表單字段名與實(shí)體屬性不對(duì)應(yīng)檢查表單name與pojo屬性名是否一致這里我想重點(diǎn)說下“表單字段名與實(shí)體屬性不對(duì)應(yīng)”導(dǎo)致空指針的問題。SSM框架的自動(dòng)填參有個(gè)特點(diǎn)如果前端提交的name值和后端實(shí)體類的屬性名不一致SpringMVC不會(huì)報(bào)錯(cuò)而是默默注入null等你后續(xù)調(diào)用這個(gè)字段的方法時(shí)才會(huì)爆空指針。排這類問題最快的方式是在需要解析的參數(shù)對(duì)象前加RequestParam注解并指定前端實(shí)際傳的字段名或者在前端F12看提交的參數(shù)格式。6.2 業(yè)務(wù)邏輯上的幾個(gè)隱藏坑第一隨機(jī)抽題的ORDER BY RAND()在數(shù)據(jù)量大時(shí)性能急劇下降。如果你只有幾十道題做測(cè)試根本感覺不到但當(dāng)題庫數(shù)據(jù)超過2000條時(shí)一次組卷需要全表掃描并生成隨機(jī)排序耗時(shí)會(huì)從幾十毫秒漲到數(shù)百毫秒甚至更長(zhǎng)。我的建議是保持表數(shù)據(jù)量在千條以內(nèi)或者改為“先查出全部ID在Java內(nèi)存里用Collections.shuffle洗牌取前N個(gè)”適合當(dāng)前場(chǎng)景。第二多選題答案的排序問題。用戶提交多選時(shí)選項(xiàng)順序可能和標(biāo)準(zhǔn)答案不一致。如果不做排序處理直接比對(duì)字符串明明選對(duì)了也被判錯(cuò)。源碼里做了排序處理這是一個(gè)非常容易遺漏的業(yè)務(wù)細(xì)節(jié)也經(jīng)常在答辯時(shí)被老師問“多選題怎么判分”。第三考試中途瀏覽器關(guān)閉的應(yīng)對(duì)。如果考生通過瀏覽器后退按鈕返回到上一頁并再次提交后端防重復(fù)提交邏輯會(huì)攔截重復(fù)的答題記錄但考生頁面的倒計(jì)時(shí)狀態(tài)可能丟失導(dǎo)致無法繼續(xù)查看這次考試狀態(tài)。實(shí)際使用中只能靠“考試記錄列表”頁面讓考生重新開始或查看已有成績(jī)體驗(yàn)上有一點(diǎn)瑕疵但教學(xué)項(xiàng)目里完全可以接受。6.3 從這套源碼里可以帶走的三個(gè)工程化習(xí)慣源碼本身有一定年代感但里面有幾個(gè)習(xí)慣值得專門提出來供大家借鑒。第一是分層清晰。Controller層只做參數(shù)接收和視圖轉(zhuǎn)發(fā)Service層處理業(yè)務(wù)邏輯Mapper層操作數(shù)據(jù)庫。整個(gè)項(xiàng)目按這個(gè)規(guī)則切分得明明白白沒有出現(xiàn)Controller里直接寫JDBC這種反面教材。這種分層習(xí)慣一旦養(yǎng)成不管以后你用Spring Boot還是其他框架寫出來的代碼結(jié)構(gòu)都不會(huì)太差。第二是命名規(guī)范一目了然。QuestionMapper.xml里每條SQL的id與Mapper接口方法名完全一致parameterType、resultType寫得很明確這給后來接手代碼的人省了巨大的時(shí)間。我見過太多課設(shè)項(xiàng)目Mapper文件名和實(shí)體名對(duì)不上查一個(gè)方法要全局搜索半天。第三是SQL腳本自帶測(cè)試數(shù)據(jù)。導(dǎo)入數(shù)據(jù)庫之后馬上就有可用的賬號(hào)和管理員數(shù)據(jù)不需要自己造數(shù)據(jù)就能把整個(gè)流程走通。這個(gè)細(xì)節(jié)對(duì)評(píng)審老師來說特別加分——他們拿到項(xiàng)目后能直接運(yùn)行不會(huì)因?yàn)椤皵?shù)據(jù)庫里沒有數(shù)據(jù)”而卡住。6.4 如果讓我重寫這套系統(tǒng)我會(huì)升級(jí)哪些地方花了一個(gè)下午把源碼全部看完之后我的整體評(píng)價(jià)是業(yè)務(wù)邏輯完整、代碼層次分明、適合作為課程設(shè)計(jì)和SSM入門的學(xué)習(xí)范本。如果讓我基于它重新做一套系統(tǒng)我會(huì)優(yōu)先做三件事。第一把密碼改成MD5加鹽存儲(chǔ)。明文密碼在當(dāng)前安全意識(shí)下已經(jīng)完全不能接受哪怕不上加密算法至少也要做一層哈希。第二把ORDER BY RAND()改成洗牌算法讓組卷在題庫數(shù)據(jù)量增大后依然流暢。第三把前端從JSPBootstrap升級(jí)為Vue3 Element Plus或直接做前后端分離前端通過AJAX調(diào)用RESTful接口體驗(yàn)會(huì)明顯提升。當(dāng)然這一改的復(fù)雜度會(huì)指數(shù)級(jí)上升走入SSM課程的初衷反而會(huì)偏移所以我的建議是先把當(dāng)前的源碼徹底吃透再談升級(jí)。個(gè)人體會(huì)這套系統(tǒng)真正讓我覺得有價(jià)值的地方不在于代碼有多牛而在于它把“考試”這個(gè)業(yè)務(wù)場(chǎng)景的完整閉環(huán)用一套主流Java技術(shù)棧串了起來。你跟著它從頭到尾走一遍SSM框架的原理、數(shù)據(jù)庫設(shè)計(jì)、狀態(tài)管理、前后端交互這些知識(shí)就都打通了。以后再去看Spring Boot項(xiàng)目你會(huì)發(fā)現(xiàn)很多概念都是似曾相識(shí)。最后再分享一個(gè)實(shí)際操作里的小技巧如果你需要向別人演示這套系統(tǒng)提前在題庫里準(zhǔn)備好50道以上的題目并且保證每個(gè)章節(jié)、每種題型的數(shù)量都超過組卷配置的數(shù)量。這樣演示時(shí)組卷過程順暢試卷內(nèi)容也不會(huì)出現(xiàn)大量重復(fù)題。我第一次演示時(shí)題庫里只有15道題組卷要求抽20道結(jié)果系統(tǒng)直接提示題量不足場(chǎng)面一度很尷尬。所以數(shù)據(jù)準(zhǔn)備永遠(yuǎn)比代碼調(diào)試更值得提前做。