:從需求到部署的完整實踐)
一個“文物征集管理系統(tǒng)”聽起來好像很小眾但放在畢設和課設的背景下這其實是一個非常經(jīng)典、非常聰明的選題。它表面上是一個面向“紅色革命文物”的業(yè)務管理系統(tǒng)實際上內(nèi)核是一個標準的信息管理平臺有用戶權限、有業(yè)務流程流轉(zhuǎn)、有文件上傳、有數(shù)據(jù)統(tǒng)計難度適中業(yè)務故事也好講。很多同學拿到類似的源碼要么只會對著教程跑起來要么不知道怎么在答辯時講清楚更別說萬一遇到問題如何排查。這篇文章我就以這套 SpringBoot Vue 的 MVC 模式管理系統(tǒng)為例從頭到尾拆一遍它解決什么問題、技術架構(gòu)怎么選、核心代碼怎么寫、數(shù)據(jù)庫怎么建、怎么從零跑起來以及我在實際調(diào)試中踩過的一些坑。先說結(jié)論這套東西你把它吃透了不只是完成一個畢設而是真正理解了企業(yè)級前后端分離開發(fā)的基本套路。1. 需求先想清楚再動手這個系統(tǒng)到底要管什么很多同學拿到項目第一件事就是打開 IDEA 開始跑代碼其實這是比較低效的做法。先搞清楚業(yè)務再看代碼事半功倍。1.1 別被“文物征集”四個字勸退拆解核心業(yè)務紅色革命文物征集系統(tǒng)的核心業(yè)務說白了就是一件事把散落在民間的文物線索和實物征集信息系統(tǒng)化地管起來。傳統(tǒng)的線下流程是發(fā)布征集公告 - 群眾來電來信提供線索 - 工作人員登記紙質(zhì)信息 - 專家鑒定審核 - 入庫登記 - 建立臺賬。這個過程里問題很多紙質(zhì)表格容易丟、想查一條記錄翻半天檔案柜、審核進度誰也不知道、統(tǒng)計本月征集了多少文物全靠人工數(shù)。而管理系統(tǒng)要做的就是把這條鏈路搬到線上。我建議你把整個系統(tǒng)抽象成一條“征集業(yè)務線”所有功能都圍繞這條線展開線索登記群眾或者征集員錄入文物基本信息名稱、年代、類別、來源、保存現(xiàn)狀、征集方式等。材料附件上傳文物照片、相關證明文件。審核流轉(zhuǎn)管理員或者專家對征集信息進行審核可能通過、可能退回補充材料。入庫登記審核通過后文物正式進入館藏臺賬生成唯一編號。統(tǒng)計展示按年代、類別、征集狀態(tài)等維度做統(tǒng)計讓管理者掌握征集進度。理解了這條線你再看代碼里的實體類、數(shù)據(jù)庫表、接口設計就會覺得“原來如此”而不是“這是什么鬼”。1.2 功能模塊劃分與權限設計系統(tǒng)里不是所有人都能干所有事的這就是權限設計的由來。一個完整的征集管理系統(tǒng)至少要考慮三類角色系統(tǒng)管理員擁有全部權限包括用戶管理、數(shù)據(jù)字典維護、審核管理、統(tǒng)計查看。征集員/錄入員可以新增征集信息、錄入文物資料、維護自己創(chuàng)建的記錄。專家/審核員主要負責審核征集線索和文物信息給出審核意見。有些系統(tǒng)里還會把“普通社會公眾”角色放進來用于在線提交征集線索這就是另一個典型的業(yè)務場景了。模塊和權限的合理劃分是你答辯時可以重點講的一個亮點。比如“為什么同一個登錄接口要返回不同的菜單權限”“為什么征集的文物信息要用狀態(tài)字段而不是直接刪除”這些都是體現(xiàn)你對系統(tǒng)思考深度的點。權限設計做得好這個系統(tǒng)的框架感就出來了后面加功能也只是在模塊里堆接口而已。2. 技術選型解析SpringBoot Vue MVC 這個組合為什么“穩(wěn)”選技術棧不要追求新奇要追求“能跑、好講、有問題能搜到答案”。SpringBoot Vue 這個組合可以說是當下Java Web前后端分離項目的“標準答案”。2.1 后端SpringBoot 到底幫我們省了哪些事SpringBoot 是一個建立在 Spring 框架之上的快速開發(fā)腳手架。沒有它的時候你要配置 SpringMVC、配置 Tomcat、配置 MyBatis 的 SqlSessionFactory、寫一大堆 XML。有了 SpringBoot 之后很多配置都變成了“約定大于配置”你只需要引入依賴寫上application.yml就能快速啟動一個 Web 服務。在這套系統(tǒng)里SpringBoot 的核心作用有幾個內(nèi)嵌 Tomcat打包成 jar直接java -jar就能啟動不用單獨裝 Tomcat 服務器。Starter 機制引入spring-boot-starter-web就完成了 Web 環(huán)境搭建引入mybatis-plus-boot-starter就有了數(shù)據(jù)庫操作能力。統(tǒng)一配置數(shù)據(jù)庫連接、文件上傳大小、端口號等都在application.yml里管理改配置不用重編譯。還有一個很實用的點是SpringBoot 項目現(xiàn)在幾乎都配套 MyBatis-Plus 使用。它幫你封裝了單表的增刪改查不需要寫基礎的 SQL。比如你要按 ID 查一條文物信息只需要CulturalRelic relic culturalRelicMapper.selectById(id);連 SQL 都不用寫。對于這類業(yè)務相對標準化的管理系統(tǒng)來說MyBatis-Plus 能減少大量低級重復勞動你只需要把心思放在業(yè)務流程上。2.2 MVC 三層架構(gòu)在前后端分離項目里怎么落地項目標題里有一個詞“MVC模式”這個是答辯的時候老師幾乎必問的。MVC 不是前后端分離時代的專用概念但它依然適用于這種架構(gòu)。我們來說清楚它在前后端分離里分別對應什么。后端里的嚴格分層是這樣的Controller 層表現(xiàn)層只負責接收前端請求、解析參數(shù)、調(diào)用 Service、封裝返回結(jié)果。它不寫業(yè)務邏輯也不直接操作數(shù)據(jù)庫。一個干凈的 Controller 方法大概長這樣RestController RequestMapping(/api/relic) public class CulturalRelicController { Resource private CulturalRelicService relicService; PostMapping(/add) public Result add(RequestBody CulturalRelicDTO dto) { return Result.success(relicService.addRelic(dto)); } }Service 層業(yè)務邏輯層這是整個系統(tǒng)的核心負責處理業(yè)務流程、做事務控制、調(diào)用數(shù)據(jù)層接口。判斷狀態(tài)、校驗參數(shù)、組織數(shù)據(jù)都在這一層完成。Mapper/DAO 層數(shù)據(jù)訪問層跟數(shù)據(jù)庫打交道。MyBatis-Plus 的 Mapper 接口繼承BaseMapperT后就自動擁有單表的 CRUD 能力你需要關注的就是那些自定義的復雜 SQL 和分頁查詢。至于前端 Vue它本身是一個 MVVM 框架Model-View-ViewModel但這里的“View”只負責渲染和交互并沒有承擔業(yè)務處理和數(shù)據(jù)庫操作的職責。所以整套系統(tǒng)的業(yè)務歸屬是清晰的Vue 管界面SpringBoot 的 Controller 管接口Service 管業(yè)務Mapper 管數(shù)據(jù)。這就是 MVC 思想在后端落地的方式。2.3 數(shù)據(jù)庫與中間件選型數(shù)據(jù)庫用的 MySQL 8.0這是目前最主流的選擇。跟這套系統(tǒng)配合你需要注意兩點驅(qū)動選擇連接 URL 里建議寫成com.mysql.cj.jdbc.Driver這是 MySQL 8.x 的驅(qū)動類老的com.mysql.jdbc.Driver在新版本下會報警告甚至報錯。時區(qū)問題URL 后面一定要加serverTimezoneAsia/Shanghai否則數(shù)據(jù)庫連接很容易報時區(qū)錯誤。這套系統(tǒng)里基本不需要 Redis、消息隊列這類中間件除非你想給它加分比如用 Redis 存登錄 token、用 RabbitMQ 做提交征集信息后的異步通知。基礎版本MySQL 足夠。3. 數(shù)據(jù)庫設計核心表結(jié)構(gòu)這樣建才合理數(shù)據(jù)庫設計決定了整個系統(tǒng)代碼好不好寫。很多同學在建表的時候喜歡一股腦把所有字段堆在一張表里后面做狀態(tài)流轉(zhuǎn)就各種別扭。這里我直接把核心表拆給你看。3.1 文物征集表業(yè)務主表怎么設計這張表是整個系統(tǒng)的核心建議叫cultural_relic字段設計要有業(yè)務導向思維。注意名稱、類別、年代這種是很多查詢條件的來源必須單獨設計字段。字段名類型注釋idbigint主鍵IDrelic_namevarchar(100)文物名稱relic_categoryvarchar(50)文物類別文件/實物/照片/其他relic_eravarchar(50)所屬年代source_typevarchar(20)征集方式捐贈/移交/購買/借展current_ownervarchar(50)當前持有人contact_phonevarchar(20)聯(lián)系電話descriptiontext文物描述/歷史背景說明statustinyint狀態(tài)0草稿/1待審核/2審核通過/3已入庫/4已退回submitter_idbigint提交人IDcreate_timedatetime創(chuàng)建時間update_timedatetime更新時間這里有幾個很容易踩的坑一種是“所有字段都用 varchar ”比如描述文本也用 varchar(255)后面存超過 255 字就報錯了。描述類字段用text日期用datetime金額用decimal該用什么類型就用什么答辯的時候被問到字段類型選擇這也是一個加分點。另一種是遺漏status狀態(tài)字段。沒有狀態(tài)字段你就無法區(qū)分一條記錄是“剛登記”還是“已經(jīng)入庫”審核流程的代碼根本寫不出來。狀態(tài)字段一定要預留而且建議用tinyint0 到 255 的取值空間足夠用十年。3.2 輔助表與狀態(tài)流轉(zhuǎn)除了主表至少還需要這幾張輔助表文物圖片表relic_image一張文物對應多張圖片。字段就是id、relic_id、image_url、is_primary是否主圖。這就是一對多關系的標準建模方式前端展示圖片列表時直接WHERE relic_id ?查詢即可。審核記錄表audit_record記錄誰在什么時間審核了哪條文物意見是什么。字段包含id、relic_id、auditor_id、audit_status、audit_comment、audit_time。用戶表sys_user賬戶、密碼、姓名、角色、手機號、創(chuàng)建時間。這是數(shù)據(jù)庫層面必須有的“家底”。建好這幾張表整個系統(tǒng)的數(shù)據(jù)流轉(zhuǎn)就順理成章了。文物征集的狀態(tài)流轉(zhuǎn)建議用狀態(tài)機思想來控制而不是隨隨便便在代碼里賦值草稿(0) - 待審核(1) - 審核通過(2) - 已入庫(3) \- 已退回(4) - 修改后重新提交 - 待審核(1)這個流轉(zhuǎn)關系畫成圖貼在論文里也是很有說服力的。4. 后端核心功能實現(xiàn)細節(jié)4.1 文物征集登記接口DTO VO 分層不能省后端代碼的復雜度往往不是功能有多難而是代碼組織得好不好。比如一個“新增征集信息”的接口很多初學者會直接把實體類CulturalRelic作為接收參數(shù)PostMapping(/add) public Result add(RequestBody CulturalRelic relic) { ... }這在簡單場景下沒什么問題但在實際項目中前端傳來的字段和后端實體類的字段往往不是完全一致的。比如前端要傳一個“擬征集方式”而實體類里根本沒有這個字段或者前端會把“圖片列表”也一起傳過來你總不能指望實體類里包含一個 List。更好的做法是給前端單獨定義一個接收對象 DTOData Transfer Object比如CulturalRelicDTOData public class CulturalRelicDTO { private String relicName; private String relicCategory; private String relicEra; private String sourceType; private String currentOwner; private String contactPhone; private String description; private ListString imageUrls; // 圖片地址列表 }然后在 Service 層把 DTO 轉(zhuǎn)換成實體類再保存到數(shù)據(jù)庫。同理接口返回給前端的數(shù)據(jù)最好也用 VOView Object包裝不要直接把數(shù)據(jù)庫實體暴露出去。這樣做的好處很明顯接口字段可控數(shù)據(jù)庫表結(jié)構(gòu)調(diào)整不會影響前端聯(lián)調(diào)代碼也更規(guī)范。答辯時你可以說“這是為了接口層與持久層解耦”這個話一出來檔次就上去了。4.2 審核流程事務控制是關鍵“審核”是征集系統(tǒng)里最核心的業(yè)務操作。審核通過一條文物記錄意味著兩件事同時發(fā)生修改cultural_relic表的status為“審核通過”。往audit_record表插入一條審核記錄。這兩個操作必須同時成功或者同時失敗。如果你只改了主表狀態(tài)插入審核記錄時數(shù)據(jù)庫報錯了那么就會出現(xiàn)“狀態(tài)已經(jīng)變了但沒有任何審核記錄”的數(shù)據(jù)不一致問題。解決辦法就是加事務Transactional(rollbackFor Exception.class) public Result audit(AuditDTO auditDTO) { // 1. 修改文物狀態(tài) CulturalRelic relic culturalRelicMapper.selectById(auditDTO.getRelicId()); relic.setStatus(auditDTO.getAuditStatus()); culturalRelicMapper.updateById(relic); // 2. 插入審核記錄 AuditRecord record new AuditRecord(); record.setRelicId(relic.getId()); record.setAuditorId(...); record.setAuditStatus(auditDTO.getAuditStatus()); record.setAuditComment(auditDTO.getAuditComment()); auditRecordMapper.insert(record); return Result.success(); }Transactional就是告訴 Spring這個方法里的所有數(shù)據(jù)庫操作要么都提交要么都回滾。這是 Java 后端開發(fā)者必須掌握的基礎知識點也是所有業(yè)務系統(tǒng)里保護數(shù)據(jù)一致性的常規(guī)手段。4.3 文件圖片上傳別把文件存進數(shù)據(jù)庫文物征集系統(tǒng)里圖片上傳是必不可少的功能。這里有一個最常見的新手誤區(qū)想都不想就把圖片轉(zhuǎn)成 Base64 字符串存進數(shù)據(jù)庫或者干脆把圖片的二進制數(shù)據(jù)用blob類型存進去。這個方案在畢設項目里能跑但一旦圖片多了數(shù)據(jù)庫體積會膨脹得非??鞌?shù)據(jù)庫備份和查詢性能都會受拖累。正確的常規(guī)做法是文件存磁盤數(shù)據(jù)庫只存文件路徑。在 SpringBoot 中上傳文件的 Controller 方法可以這樣寫PostMapping(/upload) public Result upload(RequestParam(file) MultipartFile file) { // 1. 生成唯一文件名防止重名覆蓋 String originalFilename file.getOriginalFilename(); // 例如 xxx.jpg String fileSuffix originalFilename.substring(originalFilename.lastIndexOf(.)); String newFileName UUID.randomUUID().toString().replace(-, ) fileSuffix; // 2. 指定存儲目錄注意目錄要先創(chuàng)建 String dirPath D:/upload/relic/; File dir new File(dirPath); if (!dir.exists()) { dir.mkdirs(); } // 3. 保存文件 file.transferTo(new File(dirPath newFileName)); // 4. 返回可訪問的 URL 地址 return Result.success(/files/relic/ newFileName); }這里要注意的是第 3 步的transferTo如果報錯大概率是目錄沒有創(chuàng)建權限Linux 下尤為常見。文件存好之后還差一步讓 URL 能訪問到它。在 SpringBoot 里你需要配置一個靜態(tài)資源映射把/files/**路徑映射到磁盤目錄Configuration public class WebMvcConfig implements WebMvcConfigurer { Override public void addResourceHandlers(ResourceHandlerRegistry registry) { registry.addResourceHandler(/files/**) .addResourceLocations(file:D:/upload/); } }這樣前端拿到/files/relic/xxx.jpg瀏覽器直接就能打開圖片。4.4 統(tǒng)計報表的 SQL 寫法統(tǒng)計分析模塊在畢設里很受老師青睞因為它能直接體現(xiàn)“數(shù)據(jù)是有價值的”。常用的統(tǒng)計有兩個維度一是按文物類別統(tǒng)計看看征集到的文物里文件類、實物類、照片類各占多少。SQL 其實非常簡單SELECT relic_category AS category, COUNT(*) AS count FROM cultural_relic WHERE status 3 GROUP BY relic_category;二是按年代統(tǒng)計比如解放戰(zhàn)爭時期、土地革命時期等同樣用 GROUP BY 就能完成。這種統(tǒng)計結(jié)果傳給前端之后配合 ECharts 畫餅圖、柱狀圖視覺效果非常好答辯的時候把圖一展示比空口說“系統(tǒng)很完善”有用得多。5. 前端 Vue 實現(xiàn)要點5.1 項目初始化與路由設計前端的工程化開發(fā)一定要從“創(chuàng)建一個標準項目”開始。Vue 官方推薦的腳手架是 Vite但很多老教程用的是 Vue CLI也就是 webpack 方案。這里我的建議是如果你選 Vue 2用 Vue CLInpm install -g vue/cli然后vue create 項目名。如果你選 Vue 3直接用 Vitenpm create vitelatest 項目名 -- --template vue。無論哪種生成的項目骨架都是標準的 src 結(jié)構(gòu)。路由設計上一個典型的管理系統(tǒng)包含這些頁面/login 登錄頁 /layout 主布局包含側(cè)邊欄和頂欄 ├── /dashboard 數(shù)據(jù)統(tǒng)計首頁 ├── /relic/list 文物征集列表 ├── /relic/add 新增征集信息 ├── /relic/detail/:id 文物詳情 ├── /audit/list 審核管理 └── /user/list 用戶管理管理員可見路由配置要配合權限來做。最簡單的做法是登錄時后端返回當前用戶的角色前端根據(jù)角色動態(tài)決定渲染哪些菜單和路由。比如“專家”角色就不顯示“用戶管理”菜單而“管理員”則全部可見。這個功能實現(xiàn)起來不難但非常能體現(xiàn)“系統(tǒng)的完整性”。5.2 列表頁與表單頁的實操要點列表展示是整個前端最常用的功能。我們可以用 Element UI 的el-table加上el-pagination來做分頁。關鍵點在于前端不要把后端返回的全量數(shù)據(jù)在內(nèi)存里做分頁應該把當前頁碼和第頁條數(shù)傳給后端由后端 SQL 分頁返回。頁面第一次加載時調(diào)用后端接口查詢第一頁數(shù)據(jù)this.loadData();async loadData() { const res await this.$http.get(/api/relic/list, { params: { pageNum: this.pageNum, pageSize: this.pageSize, relicName: this.searchForm.relicName, status: this.searchForm.status } }); this.tableData res.data.records; this.total res.data.total; }然后每次切換頁碼、修改搜索條件重新調(diào)用loadData()就行。這里有一個“經(jīng)典坑”很多同學在搜索時把搜索條件里的空字符串也傳給后端導致 SQL 里出現(xiàn)WHERE relic_name 的情況。穩(wěn)妥的做法是在后端接口里對空字符串做一次判斷或者用 MyBatis-Plus 的StringUtils.isNotBlank()配合 QueryWrapper 動態(tài)拼接條件QueryWrapperCulturalRelic wrapper new QueryWrapper(); if (StringUtils.isNotBlank(relicName)) { wrapper.like(relic_name, relicName); }這樣搜索條件為空時就不會拼接 SQL避免了很多隱性問題。5.3 axios 封裝與攔截器前端拿不到數(shù)據(jù)、接口報錯很多時候不是后端問題而是 axios 沒有封裝好。我建議項目一開始就封裝一個統(tǒng)一的請求模塊給所有接口設置一個 baseURL同時配置請求攔截器和響應攔截器import axios from axios; import { Message } from element-ui; import router from ../router; const request axios.create({ baseURL: /api, timeout: 10000 }); // 請求攔截器附加 token request.interceptors.request.use(config { const token localStorage.getItem(token); if (token) { config.headers[Authorization] token; } return config; }); // 響應攔截器統(tǒng)一處理錯誤 request.interceptors.response.use( response { const res response.data; if (res.code ! 200) { Message.error(res.message || 請求失敗); return Promise.reject(new Error(res.message)); } return res; }, error { Message.error(網(wǎng)絡異常請稍后重試); return Promise.reject(error); } ); export default request;有了這個封裝前端每個頁面調(diào)用接口時只需要關心業(yè)務數(shù)據(jù)不需要每個地方都寫錯誤處理。尤其是登錄功能里“token 過期自動跳轉(zhuǎn)登錄頁”的邏輯在響應攔截器里統(tǒng)一寫一次就夠了。這個模塊如果寫好了整個前端工程質(zhì)量立刻提升一個檔次。6. 從源碼到跑起來環(huán)境準備與部署啟動拿到源碼之后很多同學卡在最開始的環(huán)境搭配上。這里給出一份經(jīng)過實測的版本組合能少走很多彎路。6.1 后端環(huán)境搭配與啟動流程推薦這套組合JDK 1.8或者 JDK 11SpringBoot 2.x 都支持Maven 3.6MySQL 8.0SpringBoot 2.7.x不是 3.x3.x 基于 JDK17學生項目沒必要追新2.7 的資料最多遇到問題最好搜啟動前要做的三件事第一在 MySQL 里創(chuàng)建數(shù)據(jù)庫并導入 SQL 文件。一般源碼包里會有一個.sql文件用 Navicat 或者命令行執(zhí)行mysql -u root -p relic_system.sql執(zhí)行前看一眼 SQL 文件里的建庫語句如果沒有CREATE DATABASE那就要自己先在 Navicat 里新建一個數(shù)據(jù)庫再導入。第二修改application.yml里的數(shù)據(jù)庫連接配置。把你的數(shù)據(jù)庫名、用戶名、密碼改對spring: datasource: url: jdbc:mysql://localhost:3306/relic_system?useSSLfalseserverTimezoneAsia/ShanghaicharacterEncodingutf8 username: root password: 你自己的密碼 driver-class-name: com.mysql.cj.jdbc.Driver這里特別強調(diào)useSSLfalse和serverTimezoneAsia/Shanghai。MySQL 8 默認開啟了 SSL本地開發(fā)不需要不加這個參數(shù)可能報SSL connection error時區(qū)不配的話數(shù)據(jù)庫連接池初始化就可能直接報錯。第三啟動項目。在 IDEA 里打開項目等待 Maven 依賴下載完成找到主啟動類右鍵 Run。如果看到類似Tomcat started on port(s): 8080的日志說明后端已經(jīng)起來了。6.2 前端環(huán)境配置與啟動流程前端需要安裝 Node.js建議版本 14 或者 16。啟動步驟npm install npm run servenpm install第一次執(zhí)行時可能很慢尤其是安裝 electron 那類跨平臺依賴。常規(guī)做法是改 registry 鏡像源npm config set registry https://registry.npmmirror.com然后再跑npm install速度會快很多。前端默認端口是 8080后端的端口也是 8080就會沖突。處理方法有兩個推薦改前端的 devServer 端口比如改成 8081然后配置代理轉(zhuǎn)發(fā)到 8080。Vue CLI 項目在vue.config.js里配置代理module.exports { devServer: { port: 8081, proxy: { /api: { target: http://localhost:8080, changeOrigin: true } } } };或者直接改后端端口為 8080 之外的端口如 9090。就算只用一套系統(tǒng)前端 8081 代理到后端 9090 也是可以的。順帶一提前端發(fā)請求時用/api/relic/list這種以/api開頭的相對路徑代理會幫它轉(zhuǎn)發(fā)到后端就不會有跨域問題了。如果你的前端直接寫http://localhost:8080/api/relic/list全路徑請求那必須讓后端開啟跨域CrossOrigin否則瀏覽器會攔截響應。7. 常見問題與排查技巧實錄這部分內(nèi)容真的是你在實際開發(fā)和學習過程中大概率會遇到的比看一百遍教程都有用。我把這套系統(tǒng)里高頻故障整理成速查表。癥狀常見原因解決辦法后端啟動時數(shù)據(jù)庫報 SSL 連接錯誤連接 URL 缺少useSSLfalse在application.yml的 url 里加上useSSLfalse時區(qū)報錯The server time zone value連接 URL 缺少serverTimezone加上serverTimezoneAsia/Shanghai前端請求后端接口 404代理沒配好或請求路徑不對檢查vue.config.js的 proxy 配置路徑是否以/api開頭接口 405 錯誤請求方式不匹配檢查是 POST 還是 GET前后端保持一致請求 401/無權限沒帶 token或 token 過期看前端請求攔截器有沒有附加 token后端是否校驗圖片上傳后訪問 404靜態(tài)資源映射沒配置在 WebMvcConfig 里配置/files/**映射到磁盤目錄前端頁面白屏控制臺報錯路由或組件引入路徑不對檢查路由配置和 import 路徑有些源碼里的路徑直接用絕對路徑需要改成相對路徑npm install非常慢默認源在國外用registry.npmmirror.com鏡像MyBatis-Plus 分頁查詢返回 total 是 0少了分頁插件配置檢查是否配置了MybatisPlusInterceptor且添加了PaginationInnerInterceptor數(shù)據(jù)庫導入 SQL 報錯SQL 文件編碼不是 UTF-8用 Navicat 導入時選擇 UTF-8 編碼或在命令行執(zhí)行時指定--default-character-setutf87.1 排查問題的通用方法論光有速查表還不夠我給你一個通用的排查思路這個方法比任何具體答案都管用。很多同學遇到報錯的第一反應就是把報錯信息復制粘貼到百度這本身沒錯但效率太低了。正確順序是第一步看控制臺最底部的 Caused by 信息。Java 的報錯信息往往很長真正的原因藏在最下面。比如你在 IDEA 的紅色報錯信息里往上翻幾頁找到Caused by: java.sql.SQLException: Access denied for user rootlocalhost (using password: YES)這時候就直接知道是數(shù)據(jù)庫賬號密碼不對而不是被上面的什么 NullPointerException 帶偏。第二步確認錯誤屬于哪個層級。是數(shù)據(jù)庫連接失敗還是 SQL 拼寫錯誤還是業(yè)務代碼空指針還是前端網(wǎng)絡請求失敗層級判斷準確查找范圍縮小一半。第三步斷點調(diào)試。不要覺得斷點調(diào)試很難。在 IDEA 里打個斷點用調(diào)試模式啟動一步步看代碼執(zhí)行到哪一步出錯、某個變量的值是什么十次里有八次能直接揪出問題。這比盲猜變量值高效得多。第四步保留原始日志。排查問題之前先復制完整的日志片段再去查。很多問題單看報錯標題判斷不出來但結(jié)合完整的異常堆棧很容易定位。問別人問題的時候也一定要把完整日志貼出來而不是只說“報錯了”。7.2 跨域問題關鍵是不重蹈覆轍我單獨把跨域拎出來說因為這個是前后端分離項目里最容易困擾新人的問題。瀏覽器有一個同源策略A 網(wǎng)站的 JavaScript 代碼默認無法直接訪問 B 網(wǎng)站的接口。如果前端跑在http://localhost:8081后端跑在http://localhost:8080端口不同就算域名相同也算跨域。最省事的方案就是上面說的代理轉(zhuǎn)發(fā)。前端的所有請求路徑都寫成相對路徑以/api開頭由 Vite 或 webpack-dev-server 代理轉(zhuǎn)發(fā)到后端。這樣瀏覽器看到的請求就是同源的都在 8081跨域問題不存在。如果確實不走代理要后端開跨域你可以在 SpringBoot 里寫一個全局的跨域配置比在每個 Controller 上加CrossOrigin注解更干凈Configuration public class CorsConfig implements WebMvcConfigurer { Override public void addCorsMappings(CorsRegistry registry) { registry.addMapping(/**) .allowedOrigins(http://localhost:8081) .allowedMethods(GET, POST, PUT, DELETE, OPTIONS) .allowedHeaders(*) .allowCredentials(true); } }需要提醒的是allowedOrigins寫死前端地址如果是線上部署用 nginx 反代這句通常卻是不需要的有些情況下反而會導致登錄的 cookie 帶不上。8. 二開擴展讓這個系統(tǒng)從“能交”變成“出彩”基礎功能跑通只是第一步如果想在答辯時拿高分或者自己在技術上真的有收獲一定要嘗試做一兩個擴展功能。我給你幾個方向難度從低到高。第一個方向是接入 JWT 做無狀態(tài)登錄。現(xiàn)在很多管理系統(tǒng)的登錄方案還是基于 Session 的但前后端分離環(huán)境下更通用的是 JWT。原理是用戶登錄成功后后端生成一個帶簽名的 token 返回給前端前端把 token 存在 localStorage 里之后每次請求在請求頭帶上這個 token。后端用攔截器校驗 token 是否有效、是否是當前用戶。這個機制不復雜但做完之后你對“登錄態(tài)”的理解會通透很多。第二個方向是引入對象存儲服務。我上面文章里寫的圖片上傳是存本地磁盤這在教學項目里足夠。但如果你想讓項目更接近生產(chǎn)環(huán)境可以考慮把文件上傳到云端的對象存儲服務比如 MinIO。MinIO 是開源的對象存儲方案可以部署在你自己的服務器上文檔也比較友好。SpringBoot 整合 MinIO 有對應的 SDK接口兩三行代碼就能實現(xiàn)上傳。這個改造既解決了“文件存在服務器本地”的擴容難題也讓你提前接觸了企業(yè)里常用的文件存儲方案。熱搜詞里還提到了“minio加入到springboot”這正好是一個加分項。第三個方向是引入工作流引擎。比如 Flowable 或者 Activiti把審核流程做成可配置的動態(tài)流程。這個難度高但對于“征集審核”“入庫審批”這種多環(huán)節(jié)業(yè)務來說確實更貼切。如果你時間充??梢越ㄗh團隊里一兩個人專門研究這個方向作為進階功能展示。這幾個擴展方向里我個人最推薦第二個方案。理由很簡單在你們整個前后端分離的架構(gòu)里文件存儲是不可或缺的一個環(huán)節(jié)MinIO 的引入不會打斷現(xiàn)有代碼邏輯又能在答辯時展示你對“生產(chǎn)級文件存儲”的認知性價比最高。9. 寫在后面做項目最忌諱“跑起來就完事”最后分享一點我自己做這些項目時的心得。很多人把一個項目拉下來之后跑起來截幾張圖論文一貼就覺得自己完成任務了。實際上等到答辯的時候老師隨便問一個“審核狀態(tài)是怎么流轉(zhuǎn)的”立刻就露餡了。我的建議是你拿到任何系統(tǒng)源碼不要急著跑而是先干兩件事第一件事用筆在紙上畫一遍數(shù)據(jù)庫關系圖。不用畫得多華麗就畫出用戶表、文物征集表、審核記錄表、圖片表之間誰關聯(lián)誰就能明確這個系統(tǒng)的業(yè)務主鏈路。畫完你就能發(fā)現(xiàn)原來從這個系統(tǒng)里隨便找一個功能點都離不開這幾張表的聯(lián)動。第二件事把其中一個核心功能從頭到尾寫一遍。不需要完整重寫整個系統(tǒng)但你可以試著自己寫一個“文物征集信息新增”功能從建表、寫實體、寫 Mapper、寫 Service、寫 Controller到前端寫一個表單頁、調(diào)接口、刷新列表。親手寫完這一個閉環(huán)你就掌握了前后端分離項目里 80% 的套路。剩下的功能無非是列表查詢、狀態(tài)修改、刪除、統(tǒng)計套路都是同一個。這套項目值不值得學關鍵不在于它的功能有多炫而在于它把 Java Web 后端開發(fā)最常用的一整套知識串了起來MVC 分層、ORM 框架、事務管理、文件上傳、權限控制、前后端聯(lián)調(diào)、部署啟動。你把這個項目吃透自己做畢業(yè)設計的時候完全不需要再到處找模板只需要在這個基礎上改業(yè)務字段、加模塊就行——因為骨架已經(jīng)在你腦子里。