實(shí)戰(zhàn):從架構(gòu)設(shè)計(jì)到部署全流程解析)
1. 項(xiàng)目定位與核心功能拆解做校園招聘系統(tǒng)最容易踩的坑就是一上來就想著“把BOSS直聘做成學(xué)生版”。真正上手這個(gè)SpringBoot大學(xué)生招聘系統(tǒng)之后我最大的感受是校園場(chǎng)景和社招場(chǎng)景的需求差異比大多數(shù)人想象的大得多。先說這個(gè)系統(tǒng)解決的典型問題。高校就業(yè)場(chǎng)景里學(xué)生找實(shí)習(xí)、投校招崗位企業(yè)HR招應(yīng)屆生兩邊信息不對(duì)稱的問題非常突出。學(xué)生散落在各個(gè)平臺(tái)投簡(jiǎn)歷企業(yè)又覺得簡(jiǎn)歷質(zhì)量參差不齊。這個(gè)系統(tǒng)做的事情就是把招聘流程拆成一條清晰的業(yè)務(wù)鏈學(xué)生注冊(cè)并維護(hù)簡(jiǎn)歷企業(yè)注冊(cè)并發(fā)布職位學(xué)生檢索職位并投遞簡(jiǎn)歷企業(yè)篩選簡(jiǎn)歷并發(fā)起面試邀約最終完成錄用流程。管理員在整個(gè)過程中做審核和治理保證平臺(tái)數(shù)據(jù)干凈。從我實(shí)際做過的項(xiàng)目來看這個(gè)系統(tǒng)的用戶角色必須拆成三類否則后續(xù)權(quán)限控制會(huì)非常痛苦。學(xué)生端注冊(cè)登錄、簡(jiǎn)歷維護(hù)、職位檢索、投遞簡(jiǎn)歷、收藏職位、查看投遞反饋被查看、被邀約、被拒絕企業(yè)端企業(yè)資料認(rèn)證、職位發(fā)布與管理、收到的簡(jiǎn)歷管理、簡(jiǎn)歷篩選與面試邀約、錄用操作管理端學(xué)生/企業(yè)賬號(hào)審核、職位信息審核、基礎(chǔ)數(shù)據(jù)統(tǒng)計(jì)這三個(gè)角色之間的權(quán)限邊界如果不在設(shè)計(jì)階段就理清后面寫接口的時(shí)候就會(huì)出現(xiàn)到處查角色、到處加if判斷的情況代碼會(huì)越寫越亂。技術(shù)選型上這個(gè)項(xiàng)目用的是SpringBoot MyBatis-Plus MySQL的組合前端采用Vue ElementUI前后端分離部署。選SpringBoot的原因很直接它把Spring家族里那些繁瑣的XML配置全部干掉用自動(dòng)配置機(jī)制讓項(xiàng)目可以快速啟動(dòng)。對(duì)一個(gè)以業(yè)務(wù)功能為主的系統(tǒng)來說SpringBoot能讓你把精力花在業(yè)務(wù)邏輯上而不是花在“怎么把框架跑起來”這件事上。整個(gè)項(xiàng)目還附帶完整的源碼適合作為畢業(yè)設(shè)計(jì)或課程設(shè)計(jì)的參考項(xiàng)目也能直接改改拿去用。2. 系統(tǒng)架構(gòu)與數(shù)據(jù)庫設(shè)計(jì)背后的門道2.1 前后端分離的分層架構(gòu)到底怎么分先說項(xiàng)目結(jié)構(gòu)。一個(gè)規(guī)范的分層架構(gòu)代碼結(jié)構(gòu)直接決定后續(xù)維護(hù)的心情。這個(gè)招聘系統(tǒng)的后端按經(jīng)典的三層架構(gòu)組織Controller層接收請(qǐng)求、參數(shù)校驗(yàn)、調(diào)用Service層、返回統(tǒng)一響應(yīng)結(jié)果Service層業(yè)務(wù)邏輯處理、事務(wù)控制、調(diào)用Mapper層Mapper層數(shù)據(jù)庫訪問負(fù)責(zé)SQL執(zhí)行在SpringBoot項(xiàng)目里包結(jié)構(gòu)建議這樣規(guī)劃config配置類、controller、service、mapper、entity/domain實(shí)體、dto數(shù)據(jù)傳輸對(duì)象、vo視圖對(duì)象、common通用工具和返回結(jié)果封裝。我第一次寫這種系統(tǒng)的時(shí)候把所有類都堆在幾個(gè)包下面后來改需求的時(shí)候找文件找得懷疑人生所以包結(jié)構(gòu)一定要一開始就規(guī)劃好。用MyBatis-Plus而不是原生MyBatis核心原因就一個(gè)字快。單表CRUD完全不需要手寫SQL內(nèi)置的BaseMapper已經(jīng)提供了insert、updateById、selectById、selectPage這些方法。真正需要手寫SQL的場(chǎng)景主要集中在多表聯(lián)查和統(tǒng)計(jì)報(bào)表比如查詢職位列表時(shí)聯(lián)查企業(yè)名稱或者統(tǒng)計(jì)各企業(yè)發(fā)布的職位數(shù)量。這個(gè)項(xiàng)目的核心業(yè)務(wù)邏輯也是圍繞MyBatis-Plus的LambdaQueryWrapper做條件拼接比寫XML文件里的動(dòng)態(tài)SQL要直觀得多。2.2 核心數(shù)據(jù)表設(shè)計(jì)與關(guān)聯(lián)關(guān)系詳解數(shù)據(jù)表設(shè)計(jì)是這個(gè)系統(tǒng)最值得細(xì)看的部分。我結(jié)合實(shí)際使用場(chǎng)景給你拆解一下幾張核心表用戶表sys_user存的是登錄憑證字段包括id、username、passwordBCrypt加密存儲(chǔ)、phone、email、role學(xué)生/企業(yè)/管理員、status啟用/禁用、create_time。注意登錄賬號(hào)和角色信息放在一張表里但學(xué)生和企業(yè)的詳細(xì)資料各自拆表存儲(chǔ)避免一張表里塞太多無關(guān)字段。學(xué)生信息表student_info關(guān)聯(lián)用戶表的userId存放姓名、學(xué)校、專業(yè)、學(xué)歷、畢業(yè)年份、籍貫、聯(lián)系方式等教育背景字段。要針對(duì)校園場(chǎng)景把“專業(yè)”字段單獨(dú)拎出來建索引因?yàn)檫@是學(xué)生端最主要的篩選條件之一。企業(yè)信息表company_info關(guān)聯(lián)userId存企業(yè)名稱、所屬行業(yè)、規(guī)模、融資階段、辦公地址、企業(yè)介紹、營業(yè)執(zhí)照?qǐng)D片路徑。這塊有個(gè)實(shí)操細(xì)節(jié)要提醒企業(yè)認(rèn)證不能省。哪怕做個(gè)簡(jiǎn)單的人工審核也要讓企業(yè)上傳營業(yè)執(zhí)照否則平臺(tái)上會(huì)出現(xiàn)大量虛假招聘這是招聘類平臺(tái)的底線問題。職位表job_position關(guān)聯(lián)companyId核心字段有崗位名稱、職位類別實(shí)習(xí)/校招/兼職、薪資范圍用最低值和最高值兩個(gè)字段存儲(chǔ)便于區(qū)間查詢、工作城市、學(xué)歷要求、招聘人數(shù)、職位描述、發(fā)布時(shí)間、狀態(tài)待審核/已發(fā)布/已下架/已拒絕。投遞記錄表delivery_record這一張表是整個(gè)系統(tǒng)業(yè)務(wù)閉環(huán)的關(guān)鍵。關(guān)聯(lián)studentId和jobId核心字段是status待處理0、已查看1、面試邀約2、已通過3、已拒絕4、已取消5。這六個(gè)狀態(tài)組成了一條完整的投遞生命周期。我見過有項(xiàng)目把狀態(tài)設(shè)計(jì)成布爾字段“是否通過”后來需求一變就全廢了所以狀態(tài)的擴(kuò)展性一定要先想好。這幾張表的關(guān)系也很直觀學(xué)生表對(duì)投遞表是一對(duì)多職位表對(duì)投遞表也是一對(duì)多學(xué)生收藏職位是多對(duì)多需要單獨(dú)建一張收藏關(guān)聯(lián)表字段id、studentId、jobId、create_time。2.3 狀態(tài)機(jī)設(shè)計(jì)投遞狀態(tài)流轉(zhuǎn)的業(yè)務(wù)邏輯狀態(tài)機(jī)這個(gè)詞聽起來高大上實(shí)際上就是一組“什么狀態(tài)下能執(zhí)行什么操作”的規(guī)則。投遞記錄的狀態(tài)流轉(zhuǎn)必須嚴(yán)格限制不能讓學(xué)生已經(jīng)把簡(jiǎn)歷投出去了還能重復(fù)投。整個(gè)流程里狀態(tài)流轉(zhuǎn)的合法路徑是待處理學(xué)生投遞簡(jiǎn)歷后進(jìn)入的初始狀態(tài)已查看企業(yè)HR點(diǎn)擊查看簡(jiǎn)歷后更新為已查看面試邀約企業(yè)發(fā)起面試邀約后狀態(tài)變?yōu)槊嬖囇s同時(shí)系統(tǒng)給學(xué)生發(fā)送站內(nèi)消息已通過面試通過企業(yè)執(zhí)行錄取操作已拒絕企業(yè)查看后覺得不符合或者面試后不通過已取消學(xué)生在待處理狀態(tài)下主動(dòng)撤回投遞為什么不建議讓學(xué)生隨意撤回已經(jīng)進(jìn)入面試環(huán)節(jié)的投遞從現(xiàn)實(shí)業(yè)務(wù)角度看如果學(xué)生能隨便撤回企業(yè)的招聘流程會(huì)被反復(fù)打斷數(shù)據(jù)統(tǒng)計(jì)也會(huì)亂掉。我在實(shí)現(xiàn)狀態(tài)變更的時(shí)候在每個(gè)更新的Service方法里都加了狀態(tài)冪等校驗(yàn)用update ... where status 上一狀態(tài)這種方式防止并發(fā)操作導(dǎo)致數(shù)據(jù)不一致。3. 關(guān)鍵功能模塊的實(shí)現(xiàn)剖析3.1 登錄鑒權(quán)與角色權(quán)限控制方案登錄這塊別看簡(jiǎn)單很多人在權(quán)限上翻車。我建議用JWTJSON Web Token做無狀態(tài)登錄配合攔截器做接口權(quán)限控制。具體流程是用戶登錄成功后后端生成一個(gè)JWT令牌返回給前端前端存在本地存儲(chǔ)中每次請(qǐng)求在請(qǐng)求頭里帶Authorization后端攔截器解析這個(gè)JWT獲取用戶id和角色存入ThreadLocal供后續(xù)業(yè)務(wù)代碼使用。JWT的三段式結(jié)構(gòu)——Header、Payload、Signature——里Payload部分不要放敏感信息只需放userId和role這兩個(gè)核心字段就行。實(shí)際項(xiàng)目中密鑰要放在配置文件中不要硬編碼在代碼里。過期時(shí)間建議設(shè)置成2小時(shí)前端需要配合做token過期后的自動(dòng)跳轉(zhuǎn)登錄頁。角色權(quán)限這塊我強(qiáng)烈建議用攔截器做基于路徑的權(quán)限控制而不是在每個(gè)Controller方法內(nèi)部手動(dòng)判斷。具體做法是定義一個(gè)權(quán)限攔截器在preHandle方法里解析當(dāng)前用戶的角色然后根據(jù)請(qǐng)求路徑前綴做判斷/admin/** 需要管理員角色 /company/** 需要企業(yè)角色 /student/** 需要學(xué)生角色這樣寫的好處是權(quán)限校驗(yàn)邏輯集中在一處新增接口時(shí)只要URL規(guī)范正確權(quán)限控制就自動(dòng)生效不用每個(gè)接口都寫一遍角色判斷的重復(fù)代碼。這里有一個(gè)我在實(shí)際項(xiàng)目中踩過的坑JWT解析失敗和過期處理的異常不要直接拋給前端應(yīng)該封裝一個(gè)統(tǒng)一異常處理器RestControllerAdvice返回固定的錯(cuò)誤碼和提示信息防止認(rèn)證失敗時(shí)前端拿到一個(gè)五顏六色的異常堆棧。3.2 職位檢索與條件組合查詢的實(shí)現(xiàn)職位檢索是學(xué)生端的核心功能也是我在這個(gè)項(xiàng)目里花時(shí)間最多的地方。檢索條件至少包括關(guān)鍵詞崗位名稱模糊匹配、城市、職位類別實(shí)習(xí)/校招/兼職、學(xué)歷要求、薪資范圍。這幾個(gè)條件可以任意組合用MyBatis-Plus的LambdaQueryWrapper做動(dòng)態(tài)SQL拼接非常順手。LambdaQueryWrapperJobPosition wrapper new LambdaQueryWrapper(); wrapper.eq(JobPosition::getStatus, 1) // 只查詢已發(fā)布的職位 .like(StringUtils.hasText(keyword), JobPosition::getJobName, keyword) .eq(StringUtils.hasText(city), JobPosition::getCity, city) .eq(StringUtils.hasText(category), JobPosition::getCategory, category) .ge(minSalary ! null, JobPosition::getSalaryMin, minSalary) .le(maxSalary ! null, JobPosition::getSalaryMax, maxSalary) .orderByDesc(JobPosition::getCreateTime);這段代碼的核心邏輯在于每個(gè)查詢條件都帶上一個(gè)前置的布爾判斷比如StringUtils.hasText(keyword)為false時(shí)這個(gè)條件就自動(dòng)跳過。這樣可以優(yōu)雅地處理“用戶只填了城市、沒填關(guān)鍵詞”這類情況不用寫一堆if判斷。但職位列表不能只返回職位表的數(shù)據(jù)學(xué)生列表里要顯示企業(yè)名稱和企業(yè)Logo所以需要聯(lián)查企業(yè)信息表。我的做法是在Mapper層自定義一個(gè)分頁查詢SQL通過LEFT JOIN把企業(yè)名稱字段查出來封裝到VO對(duì)象中返回給前端頁面。這里還有個(gè)小細(xì)節(jié)MyBatis-Plus的分頁插件要單獨(dú)配置分頁攔截器PaginationInnerInterceptor不配置的話page方法只返回全部數(shù)據(jù)分頁不生效。這個(gè)坑很多人踩過在配置類里補(bǔ)上即可。3.3 簡(jiǎn)歷上傳與在線預(yù)覽的文件處理方案簡(jiǎn)歷管理是學(xué)生端的另一個(gè)高頻操作。文件上傳的方案有兩條路存本地磁盤或者接對(duì)象存儲(chǔ)服務(wù)。這個(gè)項(xiàng)目里可以選一個(gè)務(wù)實(shí)的方式——本地磁盤存儲(chǔ)加靜態(tài)資源映射。具體做法是在配置文件中定義上傳路徑然后通過WebMvcConfigurer添加一個(gè)資源映射器把本地目錄映射成URL虛擬路徑。spring: servlet: multipart: max-file-size: 10MB max-request-size: 20MB file: upload: path: /data/recruitment/files/上傳文件時(shí)FileUtil工具類負(fù)責(zé)按日期生成子目錄用UUID作為文件新名稱防止文件名沖突和路徑穿越攻擊。特別注意文件存儲(chǔ)的目錄不能被代碼里寫死成用戶輸入的文件名。簡(jiǎn)歷支持PDF和Word兩種格式在線預(yù)覽用兩種方案PDF直接通過瀏覽器內(nèi)置的PDF預(yù)覽插件加載Word文件建議用OpenOffice轉(zhuǎn)成PDF之后再預(yù)覽否則前端很難處理。如果你項(xiàng)目部署在云服務(wù)器上我更推薦接入MinIO做對(duì)象存儲(chǔ)。MinIO作為私有化部署的對(duì)象存儲(chǔ)服務(wù)和SpringBoot的整合非常順暢只需在pom.xml中加入依賴然后配置endpoint、accessKey和secretKey。它最大的優(yōu)勢(shì)是把文件和業(yè)務(wù)系統(tǒng)解耦后續(xù)擴(kuò)展、備份、權(quán)限管理都更方便這也是為什么現(xiàn)在項(xiàng)目里越來越多人把MinIO加入SpringBoot。文件上傳成功后返回的是MinIO中文件的訪問URL前端直接使用該URL加載。3.4 投遞流程與面試邀約的業(yè)務(wù)閉環(huán)投遞流程是本系統(tǒng)業(yè)務(wù)閉環(huán)的核心鏈路。學(xué)生瀏覽職位詳情頁點(diǎn)擊“立即投遞”按鈕時(shí)后端要做兩件事判斷該學(xué)生對(duì)該公司這個(gè)職位是否已經(jīng)投遞過查投遞記錄表如果已存在且狀態(tài)不是已取消或已拒絕就提示“重復(fù)投遞”如果不存在則創(chuàng)建一條新的投遞記錄狀態(tài)置為待處理。投遞成功后企業(yè)端會(huì)看到新的投遞記錄點(diǎn)擊查看簡(jiǎn)歷時(shí)投遞狀態(tài)從待處理更新為已查看。企業(yè)看完簡(jiǎn)歷如果覺得合適可以填寫面試時(shí)間、面試地點(diǎn)和備注信息發(fā)起面試邀約。此時(shí)系統(tǒng)要做的事情是更新投遞記錄狀態(tài)為面試邀約同時(shí)給學(xué)生發(fā)送站內(nèi)消息通知。這個(gè)“投遞-查看-邀約”的閉環(huán)用數(shù)據(jù)庫事務(wù)確保數(shù)據(jù)一致性。每一個(gè)狀態(tài)流轉(zhuǎn)接口我都會(huì)在Controller層做參數(shù)校驗(yàn)Spring Validation在Service層做狀態(tài)冪等校驗(yàn)盡可能減少臟數(shù)據(jù)。另外站內(nèi)消息這塊不要做得太重。學(xué)生和企業(yè)之間的溝通初期版本用簡(jiǎn)化的狀態(tài)通知就夠了不做實(shí)時(shí)聊天。如果要做聊天功能復(fù)雜度會(huì)翻倍需要上WebSocket。等核心招聘流程跑通了再加這才是合理的迭代節(jié)奏。4. 核心配置與部署實(shí)操記錄4.1 從零到一SpringBoot環(huán)境搭建與配置項(xiàng)目要用起來第一步是環(huán)境準(zhǔn)備。我以Windows開發(fā)環(huán)境為例把整個(gè)環(huán)境的搭建過程完整跑一遍。本機(jī)需要安裝的工具有JDK 8及以上版本、Maven 3.6、MySQL 5.7或8.0、IDEA編譯器、Navicat或命令行工具。JDK方面我建議安裝JDK 8雖然現(xiàn)在有不少新項(xiàng)目起步就是JDK 17但考慮到畢業(yè)設(shè)計(jì)和課程設(shè)計(jì)場(chǎng)景的兼容性JDK 8的生態(tài)最成熟遇到問題網(wǎng)上都有現(xiàn)成解決方案。創(chuàng)建一個(gè)SpringBoot項(xiàng)目有兩種方式一種是去Spring Initializr網(wǎng)站生成然后導(dǎo)入IDEA另一種是直接在IDEA的Spring Initializr引導(dǎo)界面創(chuàng)建。選SpringBoot 2.7.x版本比較穩(wěn)妥不要一上來就追最新的SpringBoot 3.x因?yàn)?.x版本要求JDK 17且部分依賴的命名和配置方式有變化遷移成本不低。parent groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-parent/artifactId version2.7.18/version relativePath/ /parent創(chuàng)建項(xiàng)目時(shí)勾選的核心依賴有Spring Web、MyBatis-Plus需要手動(dòng)加入坐標(biāo)、MySQL Driver、Lombok、Validation。在pom.xml中Lombok依賴需要配置annotationProcessorPaths否則會(huì)出現(xiàn)注解不生效的問題。配置文件里最重要的就是MySQL連接和日志配置。注意MySQL 8.0和5.7的驅(qū)動(dòng)類名有區(qū)別8.0用com.mysql.cj.jdbc.Driver而且必須配置時(shí)區(qū)serverTimezoneAsia/Shanghai否則默認(rèn)時(shí)區(qū)是美國時(shí)區(qū)查出來的時(shí)間與本地不一致會(huì)讓你懷疑人生。4.2 關(guān)鍵配置逐項(xiàng)說明數(shù)據(jù)庫連接與MyBatis-Plus設(shè)置把a(bǔ)pplication.yml中幾個(gè)關(guān)鍵配置項(xiàng)拎出來單獨(dú)說一說都是實(shí)操中驗(yàn)證過的重要點(diǎn)位。spring: datasource: driver-class-name: com.mysql.cj.jdbc.Driver url: jdbc:mysql://localhost:3306/recruitment?useUnicodetruecharacterEncodingutf8useSSLfalseserverTimezoneAsia/Shanghai username: root password: 123456 jackson: date-format: yyyy-MM-dd HH:mm:ss time-zone: Asia/Shanghai mybatis-plus: mapper-locations: classpath:mapper/*.xml configuration: log-impl: org.apache.ibatis.logging.stdout.StdOutImpl map-underscore-to-camel-case: true global-config: db-config: id-type: auto數(shù)據(jù)源配置里的useUnicodetrue和characterEncodingutf8這兩個(gè)參數(shù)直接決定中文能不能正常存入數(shù)據(jù)庫缺了會(huì)出現(xiàn)亂碼。jackson的時(shí)間格式配置同樣不能漏否則前端拿到的日期是時(shí)間戳數(shù)字還得額外寫工具轉(zhuǎn)換。MyBatis-Plus的map-underscore-to-camel-case必須設(shè)為true這樣才能把數(shù)據(jù)庫的create_time字段自動(dòng)映射到實(shí)體類的createTime屬性。id-type設(shè)為auto表示主鍵自增這是最省事的方案。為了調(diào)試方便開發(fā)階段我建議把SQL日志輸出到控制臺(tái)。log-impl設(shè)置為StdOutImpl后每次執(zhí)行SQL都會(huì)打印完整語句和參數(shù)排查問題效率翻倍。4.3 前端聯(lián)調(diào)Vue項(xiàng)目打包并整合進(jìn)SpringBoot的快捷路徑這個(gè)系統(tǒng)是前后端分離的開發(fā)階段前端用Vue的devServer代理轉(zhuǎn)發(fā)請(qǐng)求到后端接口但是到了部署階段前后端要能用一個(gè)端口跑起來。最省事的方式是把前端項(xiàng)目打包后的dist目錄直接放到SpringBoot的src/main/resources/static目錄下然后重新打包后端項(xiàng)目這樣SpringBoot會(huì)自動(dòng)將靜態(tài)文件作為資源服務(wù)。訪問http://localhost:8080就直接打開前端頁面后端接口在同一個(gè)端口下用/api前綴區(qū)分無需額外配置Nginx。但這里有幾個(gè)注意點(diǎn)需要提前處理好??缬騿栴}開發(fā)階段前端在8080端口、后端在8081端口必須處理后端跨域配置。我一般寫一個(gè)全局CORS配置類允許本地開發(fā)環(huán)境的所有來源跨域Configuration public class CorsConfig { Bean public CorsFilter corsFilter() { CorsConfiguration config new CorsConfiguration(); config.addAllowedOriginPattern(*); config.addAllowedHeader(*); config.addAllowedMethod(*); config.setAllowCredentials(true); UrlBasedCorsConfigurationSource source new UrlBasedCorsConfigurationSource(); source.registerCorsConfiguration(/**, config); return new CorsFilter(source); } }實(shí)際聯(lián)調(diào)中還有另一個(gè)常見問題前端頁面是打包后的靜態(tài)文件但頁面里的接口請(qǐng)求路徑是相對(duì)路徑部署到Tomcat二級(jí)目錄時(shí)會(huì)出現(xiàn)路徑錯(cuò)亂。解決辦法是在前端的axios配置中設(shè)置baseURL為/api然后SpringBoot里配置context-path為空或者直接使用根路徑部署。4.4 項(xiàng)目啟動(dòng)與部署注意事項(xiàng)啟動(dòng)項(xiàng)目有兩種方式。開發(fā)階段直接IDEA里運(yùn)行主類生產(chǎn)環(huán)境部署推薦把項(xiàng)目打成JAR包使用java -jar命令啟動(dòng)。打JAR包前要確認(rèn)pom.xml中已經(jīng)配置了SpringBoot的Maven插件否則會(huì)打出普通JAR包直接運(yùn)行會(huì)報(bào)“沒有主清單屬性”。mvn clean package -DskipTests java -jar recruitment-system-1.0.0.jar --server.port8080啟動(dòng)之后第一次進(jìn)入系統(tǒng)時(shí)注意看日志。SpringBoot啟動(dòng)時(shí)打印的端口、數(shù)據(jù)源、MyBatis-Plus配置等信息如果發(fā)現(xiàn)數(shù)據(jù)源連接失敗優(yōu)先檢查MySQL服務(wù)是否啟動(dòng)、賬號(hào)密碼是否正確、數(shù)據(jù)庫是否已創(chuàng)建并導(dǎo)入SQL文件。這些基礎(chǔ)問題排查完系統(tǒng)一般就能正常跑起來了。5. 實(shí)操中常見的坑與排查技巧實(shí)錄5.1 數(shù)據(jù)庫連接失敗與字符集問題這是新手最容易卡住的一關(guān)?,F(xiàn)象是項(xiàng)目啟動(dòng)時(shí)報(bào)Communications link failure或者HikariPool連接池創(chuàng)建失敗??赡艿脑蛴蠱ySQL服務(wù)沒啟動(dòng)、端口不是默認(rèn)的3306、賬號(hào)密碼錯(cuò)誤、數(shù)據(jù)庫不存在。另一種情況更隱蔽數(shù)據(jù)庫表創(chuàng)建好了但查詢中文條件時(shí)結(jié)果為空。這通常是因?yàn)閿?shù)據(jù)庫字符集和連接字符串字符集不一致。我遇到過MySQL數(shù)據(jù)庫使用的是latin1字符集中文插入后變成亂碼。排查方法是執(zhí)行SQL查看字符集SHOW VARIABLES LIKE character%;如果是latin1需要修改數(shù)據(jù)庫和數(shù)據(jù)表的字符集為utf8mb4。utf8mb4比utf8更完善支持存emoji表情是現(xiàn)在的標(biāo)準(zhǔn)選擇。5.2 文件上傳大小超限問題開發(fā)時(shí)上傳PDF簡(jiǎn)歷調(diào)用接口直接報(bào)MultipartException: The field file exceeds its maximum permitted size of 1048576 bytes。這是因?yàn)镾pringBoot默認(rèn)單文件大小限制為1MB多文件請(qǐng)求體限制為10MB。配置文件中把限制調(diào)大即可spring: servlet: multipart: max-file-size: 10MB max-request-size: 20MB注意調(diào)整之后要重啟項(xiàng)目。如果項(xiàng)目部署在Nginx后面Nginx的client_max_body_size也要同步修改否則請(qǐng)求根本到不了后端。5.3 JWT登錄失效與重復(fù)登錄問題JWT方案最常見的兩個(gè)問題一是用戶退出登錄后token還能繼續(xù)使用二是修改密碼后舊token依然有效。這兩個(gè)問題本質(zhì)上都是無狀態(tài)登錄方案的天然缺陷。輕量級(jí)解決方案有兩種思路。第一種是把JWT的token版本號(hào)存入Redis每次登錄生成新版本號(hào)退出或改密時(shí)刪除對(duì)應(yīng)token。這個(gè)方案需要額外引入Redis依賴。第二種更輕量退出登錄的操作在前端刪掉localStorage里的token即可后端維持無狀態(tài)改密后強(qiáng)制前端跳轉(zhuǎn)登錄頁重新登錄。項(xiàng)目初期我建議用第二種代碼最少邏輯清晰。等到系統(tǒng)并發(fā)量上來了再引入Redis做token的統(tǒng)一管理。5.4 IDEA中SpringBoot項(xiàng)目導(dǎo)入與依賴下載失敗跑不起來附帶的源碼拿到之后導(dǎo)入IDEA最常見的報(bào)錯(cuò)是Maven依賴下不下來pom文件一直飄紅。原因一般兩個(gè)本地倉庫沒有對(duì)應(yīng)依賴包且Maven中央倉庫訪問慢或者IDE使用的Maven配置不對(duì)。解決步驟打開IDEA的File - Settings - Maven確認(rèn)Maven home path指向自己安裝的Maven不要用IDEA內(nèi)置的有時(shí)候緩存會(huì)出問題確認(rèn)User settings file指向Maven的conf/settings.xml文件。然后在settings.xml中配置阿里云鏡像倉庫下載速度會(huì)有質(zhì)的提升。mirror idaliyunmaven/id mirrorOf*/mirrorOf name阿里云公共倉庫/name urlhttps://maven.aliyun.com/repository/public/url /mirror配置完鏡像之后執(zhí)行Maven的clean命令再reload項(xiàng)目依賴就會(huì)嘩嘩往下拉。5.5 端口被占用與項(xiàng)目啟動(dòng)沖突啟動(dòng)SpringBoot項(xiàng)目時(shí)提示Port 8080 was already in use。這個(gè)問題的排查最快的方式是查端口占用進(jìn)程netstat -ano | findstr 8080 taskkill /F /PID 進(jìn)程號(hào)如果不想每次啟動(dòng)前手動(dòng)殺進(jìn)程直接在application.yml里把server.port換成8081或者其他常用端口徹底避開沖突。5.6 跨域問題前端調(diào)用后端接口報(bào)No Access-Control-Allow-Origin如果你從Vue開發(fā)服務(wù)器比如localhost:5173直接訪問后端接口瀏覽器會(huì)攔截跨域請(qǐng)求控制臺(tái)報(bào)錯(cuò)CORS。這個(gè)問題非常高頻我這里再強(qiáng)調(diào)一遍后端必須配置CORS過濾器或使用CrossOrigin注解。我見過很多人在網(wǎng)上搜了一堆妥協(xié)方案比如強(qiáng)行關(guān)閉瀏覽器的跨域限制這非常不可取只在本地調(diào)試勉強(qiáng)能跑部署上線必炸。正確的做法是后端在網(wǎng)關(guān)層或全局配置類中統(tǒng)一處理跨域并只允許可信的域名來源。上面CorsConfig的代碼直接可用配置之后一般不會(huì)再報(bào)跨域問題。6. 項(xiàng)目二次開發(fā)與功能擴(kuò)展建議源碼拿到手之后下一個(gè)問題往往是“我想要的功能源碼里面沒有該怎么改”。從實(shí)際經(jīng)驗(yàn)出發(fā)我給大家?guī)讉€(gè)低風(fēng)險(xiǎn)高回報(bào)的二次開發(fā)方向。數(shù)據(jù)看板在管理端增加一個(gè)招聘數(shù)據(jù)統(tǒng)計(jì)面板用ECharts可視化展示每日投遞量、職位發(fā)布量、學(xué)生注冊(cè)量趨勢(shì)。這些統(tǒng)計(jì)數(shù)據(jù)都是現(xiàn)成的寫幾個(gè)COUNT加GROUP BY的SQL再通過接口把聚合結(jié)果返回前端加上圖表組件就夠了。畢業(yè)生答辯時(shí)這塊功能非常加印象分。簡(jiǎn)歷模板多樣化學(xué)生端提供2到3套簡(jiǎn)歷模板讓用戶選擇布局和配色。實(shí)現(xiàn)思路是將簡(jiǎn)歷內(nèi)容拆成結(jié)構(gòu)化JSON保存前端根據(jù)JSON數(shù)據(jù)渲染對(duì)應(yīng)模板。這個(gè)功能不涉及后端業(yè)務(wù)邏輯的改動(dòng)只增加一個(gè)模板字段即可。企業(yè)信用評(píng)價(jià)在職位詳情頁增加企業(yè)評(píng)分展示學(xué)生投遞或面試后可以對(duì)企業(yè)和職位評(píng)分。這個(gè)功能要設(shè)計(jì)好評(píng)價(jià)維度和防惡意刷評(píng)邏輯初期可以做簡(jiǎn)化版只保留總體評(píng)分。郵件通知面試邀約和錄用通知除了站內(nèi)消息外額外發(fā)送郵件提醒。SpringBoot中發(fā)送郵件非常方便引入spring-boot-starter-mail依賴配置郵箱SMTP參數(shù)調(diào)用JavaMailSender即可。從我個(gè)人維護(hù)這類項(xiàng)目的體會(huì)來看一個(gè)畢業(yè)設(shè)計(jì)級(jí)別的招聘系統(tǒng)源碼本身已經(jīng)能把核心業(yè)務(wù)鏈路走通真正拉開差距的是數(shù)據(jù)完整度、細(xì)節(jié)體驗(yàn)和能否自圓其說的設(shè)計(jì)文檔。二次開發(fā)選一個(gè)合適的點(diǎn)做深做透比堆砌十個(gè)半成品功能更值得。7. 寫在最后的一點(diǎn)實(shí)際體會(huì)做這個(gè)SpringBoot大學(xué)生招聘系統(tǒng)的整個(gè)實(shí)踐過程我最大的收獲倒不是寫了多少行代碼而是想明白了“一個(gè)業(yè)務(wù)系統(tǒng)跑通比跑完美更重要”這個(gè)道理。招聘領(lǐng)域本身是個(gè)天然的復(fù)雜業(yè)務(wù)場(chǎng)景涉及三方角色、狀態(tài)流轉(zhuǎn)、文件交互如果一開始就想著把所有功能都做成大而全的產(chǎn)品項(xiàng)目大概率爛尾。如果你正在拿這套源碼做畢業(yè)設(shè)計(jì)或課程設(shè)計(jì)我的建議是先把項(xiàng)目完整跑起來逐條梳理業(yè)務(wù)鏈路看清楚每一個(gè)表字段的來龍去脈然后再動(dòng)手改代碼。改動(dòng)的時(shí)候每改一個(gè)功能點(diǎn)都要考慮會(huì)不會(huì)影響相鄰狀態(tài)流轉(zhuǎn)。我見過不少同學(xué)改了學(xué)生端簡(jiǎn)歷字段結(jié)果企業(yè)端查看簡(jiǎn)歷的頁面報(bào)空指針就是沒有全局梳理數(shù)據(jù)依賴導(dǎo)致的。最后分享一個(gè)小技巧在IDEA中全局搜索TODO注釋源碼作者往往會(huì)在關(guān)鍵業(yè)務(wù)節(jié)點(diǎn)留下注釋說明這些注釋是你快速理解整個(gè)項(xiàng)目邏輯的捷徑。把每個(gè)TODO對(duì)應(yīng)的業(yè)務(wù)場(chǎng)景理清整個(gè)系統(tǒng)的設(shè)計(jì)思路就會(huì)像拼圖一樣在腦子里完整呈現(xiàn)出來。