設(shè)計(jì)與實(shí)現(xiàn)全解析)
高校大學(xué)生心理咨詢管理系統(tǒng)這種題目在Java Spring Boot的課設(shè)和畢設(shè)里屬于熱度很高的類型。它不像電商、博客那些項(xiàng)目那么爛大街業(yè)務(wù)邏輯又足夠完整有角色、有預(yù)約、有測評、有檔案前后端能串成一條清晰的主線。拿來做畢設(shè)、課程設(shè)計(jì)甚至面試時(shí)當(dāng)作項(xiàng)目經(jīng)歷來講都很能體現(xiàn)工程能力。我這段時(shí)間正好完整走了一遍這個(gè)項(xiàng)目從數(shù)據(jù)庫設(shè)計(jì)到核心模塊實(shí)現(xiàn)再到后期部署和錄講解視頻踩了不少坑也整理出一套可以照著做的方案。下面就把完整的拆解思路和實(shí)操過程分享出來包括角色權(quán)限設(shè)計(jì)、表結(jié)構(gòu)、預(yù)約和測評模塊的實(shí)現(xiàn)細(xì)節(jié)以及最常見的報(bào)錯(cuò)和排查方法。如果你正準(zhǔn)備做類似的系統(tǒng)這篇可以直接當(dāng)作參考路線圖。1. 項(xiàng)目整體設(shè)計(jì)與需求拆解1.1 核心需求到底是什么心理咨詢管理系統(tǒng)表面上是“管理員管理學(xué)生和咨詢師”實(shí)際核心是兩條業(yè)務(wù)線一條是學(xué)生預(yù)約咨詢師一條是咨詢師記錄咨詢過程和結(jié)果。任何功能設(shè)計(jì)都要圍繞這兩條線展開否則做出來的系統(tǒng)就是個(gè)CRUD頁面集合答辯的時(shí)候沒什么能講的。我的建議是先畫出角色用例圖。系統(tǒng)通常包含三類角色學(xué)生、咨詢師、管理員。學(xué)生在系統(tǒng)中做測評、預(yù)約咨詢師、查看自己的咨詢記錄咨詢師處理預(yù)約請求、做咨詢評估、填寫咨詢記錄管理員管理賬號、咨詢師信息、量表配置、公告發(fā)布和數(shù)據(jù)統(tǒng)計(jì)。這個(gè)設(shè)計(jì)里容易忽略的是“預(yù)約審核”環(huán)節(jié)。實(shí)際的咨詢預(yù)約有兩種模式學(xué)生直接選擇時(shí)段就預(yù)約成功還是提交預(yù)約后由咨詢師確認(rèn)。建議采用后者因?yàn)楦N近高校心理咨詢中心真實(shí)流程也能在業(yè)務(wù)上多一個(gè)狀態(tài)節(jié)點(diǎn)答辯時(shí)說有“狀態(tài)機(jī)設(shè)計(jì)”會(huì)更充實(shí)。1.2 功能模塊的劃分與邊界我的拆分方式是這樣的按角色分模塊每個(gè)模塊只處理自己職責(zé)內(nèi)的數(shù)據(jù)學(xué)生端注冊登錄、心理測評、預(yù)約咨詢師、查看咨詢記錄、個(gè)人資料管理咨詢師端處理預(yù)約請求、填寫咨詢記錄、查看學(xué)生基本信息、維護(hù)可預(yù)約時(shí)段管理端學(xué)生和咨詢師賬號管理、咨詢師審核、測評量表管理、預(yù)約數(shù)據(jù)統(tǒng)計(jì)、公告管理心理咨詢是敏感信息這一點(diǎn)在設(shè)計(jì)時(shí)要特別考慮咨詢記錄不能像普通帖子那樣直接列表展示必須限制只能由當(dāng)事咨詢師或管理員查看。如果你沒做訪問控制任何學(xué)生能通過改URL跳轉(zhuǎn)到別人的咨詢記錄頁面這屬于嚴(yán)重安全問題一旦被提問就是減分項(xiàng)。實(shí)現(xiàn)上建議所有涉及咨詢記錄的Mapper查詢都強(qiáng)制帶上當(dāng)前用戶ID條件而不是只在頁面層隱藏按鈕。1.3 用“一次完整咨詢”穿起整個(gè)業(yè)務(wù)架構(gòu)設(shè)計(jì)完成后最好用一條業(yè)務(wù)線驗(yàn)證是否閉環(huán)。以學(xué)生成功完成一次咨詢?yōu)槔鞒虘?yīng)該如下學(xué)生注冊登錄填寫一份SDS抑郁自評量表得到測評結(jié)果和自動(dòng)建議然后瀏覽咨詢師列表選擇一位有可約時(shí)段的咨詢師提交預(yù)約申請咨詢師登錄看到待處理預(yù)約審核通過后按時(shí)間赴約咨詢完成后咨詢師填寫咨詢記錄記錄一段時(shí)間內(nèi)的狀態(tài)和建議學(xué)生可以查看自己的咨詢記錄也可再次預(yù)約或重新測評。這條鏈路走通之后整個(gè)系統(tǒng)的骨架就立住了。你去看很多做得好的畢設(shè)系統(tǒng)本質(zhì)都是這樣“一條主線貫穿所有模塊”而不是一堆沒有關(guān)聯(lián)的獨(dú)立頁面。從代碼層面講這條線也決定了Controller的請求路徑設(shè)計(jì)和數(shù)據(jù)表的外鍵關(guān)系后面寫代碼時(shí)思路會(huì)很順。2. 技術(shù)選型與項(xiàng)目結(jié)構(gòu)規(guī)劃2.1 為什么是Spring Boot MyBatis-Plus這個(gè)項(xiàng)目我為什么推薦Spring Boot而不是SSH或者SSM核心就一句話Spring Boot把配置簡化到了極致內(nèi)置Tomcat打jar包就能跑同時(shí)生態(tài)資料最豐富遇到問題基本都能搜到答案。對于課設(shè)和畢設(shè)來說時(shí)間有限、穩(wěn)定性優(yōu)先這就是最優(yōu)解。JDK和Spring Boot版本的選擇有個(gè)重要原則別追最新版本。Spring Boot 3.x要求JDK 17起步很多學(xué)校機(jī)房和老師本機(jī)還是JDK 8代碼拷過去直接跑不起來。最穩(wěn)妥的方案是JDK 8 Spring Boot 2.7.x這是兼容性最好、問題解決方案最多的組合。我項(xiàng)目里用的就是Spring Boot 2.7.6搭配MyBatis-Plus 3.5.3這套組合非常穩(wěn)。持久層用MyBatis-Plus的理由也直接單表CRUD不用寫SQL內(nèi)置條件構(gòu)造器分頁插件好用。心理咨詢系統(tǒng)的大部分操作都是單表查詢和簡單多表關(guān)聯(lián)MyBatis-Plus的QueryWrapper能省掉至少三分之一的工作量。如果你用原生MyBatis每個(gè)實(shí)體類都要配Mapper XML一個(gè)學(xué)生管理模塊就要寫十幾條SQL時(shí)間成本完全沒必要。2.2 項(xiàng)目包結(jié)構(gòu)與職責(zé)劃分這是我用的包結(jié)構(gòu)每個(gè)包的職責(zé)邊界很清晰com.example.psy ├── controller # 接口層只做參數(shù)接收和結(jié)果返回 ├── service # 業(yè)務(wù)邏輯層事務(wù)、權(quán)限判斷、業(yè)務(wù)規(guī)則 ├── mapper # 數(shù)據(jù)訪問層MyBatis-Plus的Mapper接口 ├── entity # 實(shí)體類對應(yīng)數(shù)據(jù)庫表結(jié)構(gòu) ├── dto # 傳輸對象接收前端參數(shù)、返回前端數(shù)據(jù) ├── config # 配置類攔截器、跨域、WebMvc配置 ├── common # 通用返回結(jié)果、異常處理、常量定義 ├── utils # 工具類JWT工具、日期工具等 └── interceptor # 登錄攔截器、角色權(quán)限攔截器Controller層要注意一個(gè)細(xì)節(jié)Controller只做參數(shù)校驗(yàn)和調(diào)用Service不寫業(yè)務(wù)邏輯。有太多項(xiàng)目把業(yè)務(wù)邏輯全堆在Controller里一個(gè)方法幾百行后期查問題非常痛苦。比如預(yù)約邏輯里的“時(shí)間沖突判斷”必須放在Service層因?yàn)镾ervice層要加Transactional事務(wù)注解保證沖突檢查和預(yù)約創(chuàng)建要么都成功、要么都失敗。service層建議按業(yè)務(wù)模塊拆類StudentService、CounselorService、AppointmentService、AssessmentService、RecordService。其中AppointmentService是最復(fù)雜的包含預(yù)約創(chuàng)建、審核、取消、完成四個(gè)狀態(tài)流轉(zhuǎn)每個(gè)方法都要做冪等判斷。2.3 前端部分的取舍前端有兩種常見路線服務(wù)端渲染用Thymeleaf模板 Bootstrap或者前后端分離用Vue Element UI。我自己的建議是如果你的前端基礎(chǔ)一般時(shí)間又比較緊選Thymeleaf Bootstrap更穩(wěn)妥因?yàn)樗恍枰幚砜缬?、不需要單?dú)部署前端工程項(xiàng)目整體結(jié)構(gòu)也更簡單答辯時(shí)不需要解釋“為什么兩個(gè)服務(wù)才能跑”。如果你選Vue記得有一個(gè)關(guān)鍵點(diǎn)Vue工程打出來的dist目錄把靜態(tài)資源放進(jìn)Spring Boot的src/main/resources/static目錄就能直接訪問不需要Nginx。需要注意路由必須用hash模式不能直接用history模式否則刷新頁面會(huì)404。這個(gè)坑我在后面常見問題里詳細(xì)說。3. 數(shù)據(jù)庫設(shè)計(jì)與核心表結(jié)構(gòu)3.1 表結(jié)構(gòu)總覽與設(shè)計(jì)思路數(shù)據(jù)庫是這個(gè)項(xiàng)目的根基表設(shè)計(jì)好了后面寫代碼基本是順?biāo)浦?。我總共設(shè)計(jì)了8張核心表每張表的存在都能在業(yè)務(wù)上找到對應(yīng)點(diǎn)表名說明關(guān)鍵字段user用戶表學(xué)生、管理員共用id, username, password, role, real_name, gender, student_no, phonecounselor咨詢師擴(kuò)展表id, user_id, title, specialty, introduction, years, max_appointmentsappointment預(yù)約表id, student_id, counselor_id, appoint_date, time_slot_id, status, remarktime_slot可預(yù)約時(shí)段表id, counselor_id, slot_date, start_time, end_time, is_bookedquestionnaire測評量表id, title, description, type, question_countquestion量表題目id, questionnaire_id, content, option_a, option_b, option_c, option_dassessment_record測評記錄與結(jié)果id, student_id, questionnaire_id, score, result_level, answer_detail, create_timeconsultation_record咨詢記錄id, appointment_id, counselor_id, student_id, summary, suggestionuser表存的是賬號信息counselor表存的是咨詢師的職業(yè)信息兩者通過user_id關(guān)聯(lián)。為什么要拆成兩張表而不是把所有字段放在一張表因?yàn)椴皇撬杏脩舳际亲稍儙煱裻itle、specialty這些字段塞到user表里學(xué)生和管理員也要跟著占用這些字段結(jié)構(gòu)會(huì)變得很混亂。這種拆分方式在數(shù)據(jù)庫設(shè)計(jì)里叫“垂直拆分”答辯時(shí)也是一個(gè)可以主動(dòng)講解的設(shè)計(jì)點(diǎn)。3.2 預(yù)約模塊的表結(jié)構(gòu)設(shè)計(jì)細(xì)節(jié)預(yù)約表是業(yè)務(wù)邏輯最重的表幾個(gè)關(guān)鍵字段值得重點(diǎn)設(shè)計(jì)。status字段用Int類型表示狀態(tài)比字符串更省空間比枚舉更適合在Java里做判斷0待審核1已通過2已拒絕3已完成4已取消5已過期。狀態(tài)流轉(zhuǎn)的方向建議在Service層寫死不允許隨意跳到任意狀態(tài)比如只有待審核狀態(tài)可以變成已通過已通過狀態(tài)才能變成已完成。時(shí)間沖突問題靠time_slot表解決。每個(gè)咨詢師先配置一天內(nèi)哪些時(shí)段可約每個(gè)時(shí)段is_booked字段標(biāo)記是否已占用。用戶發(fā)起預(yù)約時(shí)前端把時(shí)段ID傳過來后端在Service里按主鍵查詢并做條件更新UPDATE time_slot SET is_booked 1 WHERE id ? AND is_booked 0如果影響行數(shù)為0說明該時(shí)段已被搶這是最簡單也是最高效的并發(fā)控制方案不需要鎖表。3.3 測評模塊的靈活設(shè)計(jì)測評量表的設(shè)計(jì)很容易讓人糾結(jié)因?yàn)榱勘眍}目數(shù)量是不確定的有的30題、有的20題直接寫在代碼里或者做成固定字段都不合適。我的做法是用兩張表存量表questionnaire表存量表的基本信息和類型question表存題目以及四個(gè)選項(xiàng)。每個(gè)題目用option_a到option_d四個(gè)字段存放選項(xiàng)內(nèi)容每個(gè)選項(xiàng)設(shè)計(jì)一個(gè)標(biāo)準(zhǔn)分值。測評結(jié)果的計(jì)算邏輯放在Service層根據(jù)測評類型取出該問卷全部題目每題按用戶選擇的選項(xiàng)累加分?jǐn)?shù)最后根據(jù)總分區(qū)間映射到結(jié)果等級。比如SDS抑郁自評量表50分以下為正常50到59為輕度抑郁60到69為中度70以上為重度。這種映射規(guī)則寫成配置常量放在一個(gè)專門的結(jié)果規(guī)則類里不要散落在業(yè)務(wù)代碼各個(gè)角落。4. 核心功能模塊實(shí)現(xiàn)與關(guān)鍵細(xì)節(jié)4.1 登錄鑒權(quán)與權(quán)限控制登錄鑒權(quán)方案建議用JWT邏輯清晰也方便在答辯時(shí)展開講。用戶登錄成功后服務(wù)端生成一個(gè)token里面包含用戶ID、用戶名、角色過期時(shí)間設(shè)置為2小時(shí)。前端每次請求在Header里帶上Authorization: Bearer token后端通過攔截器統(tǒng)一解析。攔截器的實(shí)現(xiàn)邏輯是這樣寫一個(gè)LoginInterceptor實(shí)現(xiàn)HandlerInterceptor接口在preHandle方法里取出token并解析解析成功就把用戶信息放到request的attribute里后續(xù)Controller直接用解析失敗返回401狀態(tài)碼。角色權(quán)限則有兩種處理方式簡單項(xiàng)目在攔截器里直接判斷角色或者用RequireRole注解加AOP切面。需要注意攔截器放行名單。登錄接口、注冊接口、靜態(tài)資源、錯(cuò)誤頁面這些必須放行但剩下的接口都要攔截。很多新手把攔截器寫好后發(fā)現(xiàn)前端頁面上不去就是因?yàn)闆]放行靜態(tài)資源調(diào)試起來容易懷疑人生。4.2 預(yù)約模塊的Service層實(shí)現(xiàn)邏輯預(yù)約創(chuàng)建是核心中的核心完整邏輯是這樣Transactional public R createAppointment(Long studentId, AppointmentDTO dto) { // 1. 校驗(yàn)學(xué)生和咨詢師存在且狀態(tài)正常 // 2. 校驗(yàn)預(yù)約日期不能早于今天 // 3. 鎖定時(shí)段并檢查是否已被預(yù)約 TimeSlot slot timeSlotMapper.selectById(dto.getSlotId()); if (slot null || slot.getIsBooked() 1) { return R.error(該時(shí)段已被預(yù)約請選擇其他時(shí)段); } // 4. 原子更新時(shí)段狀態(tài) int rows timeSlotMapper.bookSlot(dto.getSlotId()); if (rows 0) { return R.error(該時(shí)段剛剛被預(yù)約請重試); } // 5. 創(chuàng)建預(yù)約記錄 Appointment appointment new Appointment(); appointment.setStudentId(studentId); appointment.setCounselorId(slot.getCounselorId()); appointment.setStatus(0); appointmentMapper.insert(appointment); return R.ok(); }這里的第3和第4步是防止超賣的關(guān)鍵。如果先select判斷再update兩個(gè)請求同時(shí)讀到is_booked為0就可能都通過校驗(yàn)直接用“條件更新”加“影響行數(shù)判斷”并發(fā)下只有第一個(gè)請求能成功。這是MySQL單條更新語句自帶的行鎖在起效也是面試時(shí)一個(gè)很加分的回答點(diǎn)。時(shí)段查詢要注意排序和過濾。咨詢師端確認(rèn)預(yù)約時(shí)只查status為0的待審核記錄學(xué)生端查看自己的預(yù)約記錄時(shí)默認(rèn)按創(chuàng)建時(shí)間倒序如果預(yù)約日期已經(jīng)過了當(dāng)天日期要自動(dòng)把狀態(tài)從已通過改成已過期這個(gè)邏輯可以在查詢時(shí)用一條UPDATE配合WHERE條件完成也可以在定時(shí)任務(wù)里做項(xiàng)目簡單的話查詢時(shí)同步更新就行。4.3 測評計(jì)分模塊的實(shí)現(xiàn)思路測評模塊的計(jì)分邏輯雖然不復(fù)雜但寫起來容易亂。我的實(shí)現(xiàn)是這樣一個(gè)流程學(xué)生提交測評答案時(shí)參數(shù)是一個(gè)Map結(jié)構(gòu)key是題目IDvalue是選項(xiàng)號。Service層拿到答案后先把所有題目查出來放到一個(gè)List里逐個(gè)判斷選項(xiàng)號把對應(yīng)分值累加。結(jié)果等級的判斷我用了一個(gè)私有方法private String getLevel(Integer score, String type) { if (SDS.equals(type)) { if (score 50) return 正常; if (score 60) return 輕度抑郁; if (score 70) return 中度抑郁; return 重度抑郁; } if (SAS.equals(type)) { // 類似區(qū)間判斷 } return 未知; }測評結(jié)果返回給前端時(shí)除了等級和總分還應(yīng)該返回原始答案明細(xì)。這涉及到answer_detail字段存儲(chǔ)建議用JSON字符串{ questionId: optionA, ... }。這樣以后做“按題目維度查看學(xué)生選擇情況”的功能時(shí)不需要改表結(jié)構(gòu)直接解析JSON就行。存儲(chǔ)時(shí)用ObjectMapper序列化讀取時(shí)反序列化成Map。4.4 咨詢記錄的隱私控制實(shí)現(xiàn)咨詢記錄模塊的隱私控制必須通過后端實(shí)現(xiàn)不能依賴前端隱藏按鈕。我的做法是在Service層強(qiáng)制校驗(yàn)數(shù)據(jù)權(quán)限學(xué)生只允許查詢自己相關(guān)的咨詢記錄咨詢師只允許查詢自己記錄或自己的學(xué)生管理員可查全部記錄并做統(tǒng)計(jì)。實(shí)現(xiàn)的核心就是在Mapper查詢條件里強(qiáng)制帶上當(dāng)前用戶的IDpublic ListConsultationRecord getStudentRecords(Long studentId) { LambdaQueryWrapperConsultationRecord wrapper new LambdaQueryWrapper(); wrapper.eq(ConsultationRecord::getStudentId, studentId) .orderByDesc(ConsultationRecord::getCreateTime); return consultationRecordMapper.selectList(wrapper); }這里要用MyBatis-Plus的LambdaQueryWrapper好處是字段名通過方法引用獲取不會(huì)因?yàn)閿?shù)據(jù)庫字段名寫錯(cuò)而在運(yùn)行時(shí)才報(bào)錯(cuò)。實(shí)體字段的駝峰命名會(huì)自動(dòng)映射到數(shù)據(jù)庫下劃線字段前提是在配置文件里開啟map-underscore-to-camel-case這是默認(rèn)行為但如果你自定義了MyBatis配置容易把這項(xiàng)覆蓋掉需要注意。5. 前端集成與項(xiàng)目運(yùn)行部署5.1 前后端聯(lián)調(diào)與接口規(guī)范不管用Thymeleaf還是Vue接口返回格式必須統(tǒng)一。我定義了一個(gè)通用返回對象R格式如下public class R { private Integer code; // 200成功500失敗401未登錄 private String message; // 提示信息 private Object data; // 業(yè)務(wù)數(shù)據(jù) }所有Controller的返回值都用R包裝前端判斷code等于200再渲染頁面。統(tǒng)一返回對象最大的價(jià)值在于前端可以寫一個(gè)公共的請求攔截方法只要code是401就直接跳到登錄頁不用每個(gè)頁面重復(fù)處理登錄失效的問題。5.2 Vue項(xiàng)目如何正確放進(jìn)Spring Boot這個(gè)環(huán)節(jié)坑很多單獨(dú)拿出來說。Vue工程開發(fā)時(shí)跑在8080端口Spring Boot跑在8081端口此時(shí)需要配置Vue的devServer代理把/api開頭的請求轉(zhuǎn)發(fā)到后端。生產(chǎn)環(huán)境下在Vue工程里執(zhí)行npm run build會(huì)在dist目錄下生成index.html和靜態(tài)資源文件夾把這個(gè)dist下的所有內(nèi)容復(fù)制到Spring Boot項(xiàng)目的src/main/resources/static目錄。有個(gè)必須注意的點(diǎn)Vue路由必須用hash模式不能默認(rèn)使用history模式。history模式需要服務(wù)器端配合做重定向Spring Boot默認(rèn)沒有處理單頁應(yīng)用的路由fallback直接刷新頁面會(huì)白屏報(bào)404。在Vue的vue-router配置中寫router createRouter({ history: createWebHashHistory(), routes })打成包后放到Spring Boot里才能正常運(yùn)行。如果你的URL里出現(xiàn)#號別覺得難看那是保命用的。5.3 從零到跑起來的完整步驟整個(gè)項(xiàng)目從拿到代碼到成功運(yùn)行完整順序是這樣我每次演示都按這個(gè)流程來創(chuàng)建數(shù)據(jù)庫psy執(zhí)行項(xiàng)目根目錄下的psy.sql腳本導(dǎo)入表和初始數(shù)據(jù)修改application.yml把數(shù)據(jù)庫地址、賬號、密碼改成自己本機(jī)的配置spring: datasource: driver-class-name: com.mysql.cj.jdbc.Driver url: jdbc:mysql://localhost:3306/psy?useUnicodetruecharacterEncodingutf8serverTimezoneAsia/ShanghaiuseSSLfalse username: root password: your_password確認(rèn)Maven配置的是阿里云鏡像倉庫否則首次下載依賴會(huì)非常慢用IDEA導(dǎo)入項(xiàng)目等待依賴下載完成找到主啟動(dòng)類PsychologyApplication運(yùn)行瀏覽器訪問http://localhost:8081出現(xiàn)登錄頁則代表啟動(dòng)成功初始管理員賬號admin/123456咨詢師賬號和學(xué)生的初始賬號在SQL腳本里有注釋說明為什么我把修改數(shù)據(jù)庫配置放在第二步而不是第一步因?yàn)槿绻葐?dòng)項(xiàng)目再改配置第一次啟動(dòng)一定報(bào)數(shù)據(jù)庫連接失敗很多新手在這里會(huì)反復(fù)懷疑環(huán)境問題實(shí)際上是配置沒改。先改配置再啟動(dòng)一步到位。6. 常見問題與排查技巧實(shí)錄6.1 啟動(dòng)失敗端口被占用這是出現(xiàn)頻率最高的問題。Spring Boot默認(rèn)端口8080如果你本機(jī)已經(jīng)跑過其他服務(wù)啟動(dòng)時(shí)會(huì)報(bào)Port 8080 was already in use。最簡單的解決方法是換端口在application.yml里寫server.port: 8081。如果只是想臨時(shí)排查是誰占用了端口Windows下執(zhí)行netstat -ano | findstr 8080看最后一列的PID再用taskkill /PID 進(jìn)程號 /F結(jié)束進(jìn)程。6.2 Mapper方法找不到或SQL異常項(xiàng)目啟動(dòng)后訪問某個(gè)接口直接報(bào)Invalid bound statement大概率是MyBatis-Plus的Mapper接口和XML文件對不上。注意兩點(diǎn)主啟動(dòng)類上要有MapperScan注解掃描mapper包如果某些復(fù)雜查詢要寫XML在application.yml里配置mybatis-plus.mapper-locations: classpath*:mapper/**/*.xml并且XML文件里的namespace必須寫Mapper接口的全限定名。另一個(gè)高頻問題是控制臺打印的SQL參數(shù)全是問號但執(zhí)行報(bào)錯(cuò)。這類問題通常不是MyBatis的問題而是參數(shù)類型不匹配比如前端傳過來的是String類型的ID實(shí)體類字段是Long類型SQL執(zhí)行時(shí)隱式轉(zhuǎn)換導(dǎo)致索引失效甚至類型轉(zhuǎn)換異常。在Controller接收參數(shù)時(shí)用RequestParam Long id顯式聲明不要都用String接。6.3 中文亂碼和時(shí)區(qū)問題中文顯示成問號或者亂碼幾乎都是數(shù)據(jù)庫連接URL和表的字符集設(shè)置不對。連接URL里加characterEncodingutf8同時(shí)建庫時(shí)指定字符集CREATE DATABASE psy DEFAULT CHARACTER SET utf8mb4兩張表都設(shè)為utf8mb4。如果建庫時(shí)忘了指定后面填進(jìn)去的數(shù)據(jù)也會(huì)亂需要改表字符集后重新插入數(shù)據(jù)光改連接URL對已存在的數(shù)據(jù)無效。日期數(shù)據(jù)差8小時(shí)的問題是因?yàn)镸ySQL驅(qū)動(dòng)連接時(shí)區(qū)默認(rèn)取的是UTC。在連接URL里加serverTimezoneAsia/Shanghai即可解決。另外使用LocalDate和LocalDateTime類型接收日期字段時(shí)如果前端傳的格式是yyyy-MM-dd HH:mm:ss需要在application.yml或者Jackson配置里設(shè)置統(tǒng)一格式化否則前后端日期格式不一致會(huì)導(dǎo)致反序列化報(bào)錯(cuò)。6.4 攔截器導(dǎo)致靜態(tài)資源無法訪問配置了登錄攔截器之后發(fā)現(xiàn)CSS、JS、圖片全都加載不了頁面樣式全丟。原因是攔截器攔截了所有路徑包括靜態(tài)資源。解決方法是攔截器注冊時(shí)設(shè)置排除列表把靜態(tài)資源路徑全部放行registry.addInterceptor(loginInterceptor) .addPathPatterns(/**) .excludePathPatterns(/login, /register, /css/**, /js/**, /img/**, /favicon.ico);還有個(gè)隱蔽的坑如果你的項(xiàng)目采用前后端分離前端頁面在另一個(gè)端口請求后端時(shí)會(huì)出現(xiàn)跨域問題表現(xiàn)為瀏覽器控制臺報(bào)CORS錯(cuò)誤。此時(shí)在Spring Boot里配一個(gè)跨域配置類實(shí)現(xiàn)WebMvcConfigurer的addCorsMappings方法允許前端來源、常用請求頭和請求方法。6.5 答辯和講解視頻里應(yīng)該重點(diǎn)講什么項(xiàng)目跑通之后錄講解視頻或者答辯時(shí)不要浪費(fèi)時(shí)間去念每個(gè)頁面的功能。評委和面試官真正想聽到的是這幾個(gè)問題的回答表結(jié)構(gòu)是怎么設(shè)計(jì)的為什么這么設(shè)計(jì)預(yù)約模塊怎么解決并發(fā)沖突鑒權(quán)怎么實(shí)現(xiàn)攔截器的執(zhí)行流程測評結(jié)果怎么計(jì)算結(jié)果規(guī)則怎么擴(kuò)展咨詢記錄怎么保證數(shù)據(jù)隱私。這五個(gè)問題能講透項(xiàng)目印象分會(huì)明顯上一個(gè)臺階。我的講解視頻一般控制在25分鐘左右結(jié)構(gòu)是這樣的前5分鐘演示系統(tǒng)全部頁面和核心功能中間15分鐘講代碼結(jié)構(gòu)、數(shù)據(jù)庫設(shè)計(jì)和關(guān)鍵業(yè)務(wù)實(shí)現(xiàn)最后5分鐘現(xiàn)場演示運(yùn)行步驟和最常見的報(bào)錯(cuò)場景。講代碼的時(shí)候不要照著讀而是先說思路再指著關(guān)鍵代碼說明實(shí)現(xiàn)方式。最后再分享一個(gè)實(shí)際經(jīng)驗(yàn)如果你用Spring Boot 2.7.x建議代碼里統(tǒng)一使用javax.servlet包不要用jakarta.servlet因?yàn)镾pring Boot 3才改到j(luò)akarta命名空間。很多從網(wǎng)上復(fù)制的代碼片段在這上面有差異稍微不注意就會(huì)碰到NoClassDefFoundError。做這類管理系統(tǒng)核心技術(shù)棧選穩(wěn)定版本比追求新版重要得多把業(yè)務(wù)邏輯做扎實(shí)、把數(shù)據(jù)表設(shè)計(jì)說明白項(xiàng)目的質(zhì)量自然就出來了。