修管理系統(tǒng)完整復(fù)現(xiàn):SpringBoot+Vue+MySQL全棧實(shí)戰(zhàn))
簡(jiǎn)介面向高校畢業(yè)設(shè)計(jì)、課程實(shí)踐或崗位實(shí)訓(xùn)的宿舍報(bào)修管理系統(tǒng)項(xiàng)目資料適合需要完成同類課題的計(jì)算機(jī)專業(yè)學(xué)生與開發(fā)者參考。系統(tǒng)采用Java語(yǔ)言與SpringBoot框架構(gòu)建后端搭配Vue前端與MySQL數(shù)據(jù)庫(kù)涵蓋用戶管理、維修員管理、宿舍信息管理、配件設(shè)備管理、報(bào)修接單、配件申請(qǐng)和維修進(jìn)度跟蹤等完整業(yè)務(wù)模塊能夠支撐校園宿舍后勤報(bào)修的日常流程。資源為1個(gè)docx文檔大小4.78MB內(nèi)容包括背景分析、國(guó)內(nèi)外研究現(xiàn)狀、相關(guān)理論與技術(shù)、系統(tǒng)分析、功能設(shè)計(jì)與測(cè)試驗(yàn)證等章節(jié)并給出了基于IDEA、Navicat、Tomcat等環(huán)境的開發(fā)與運(yùn)行思路。已有433人學(xué)習(xí)下載通過(guò)查閱這篇論文可以較快梳理出宿舍報(bào)修系統(tǒng)的數(shù)據(jù)庫(kù)表結(jié)構(gòu)、核心接口劃分與前后端交互邏輯對(duì)畢業(yè)設(shè)計(jì)答辯、系統(tǒng)二次開發(fā)或?qū)W習(xí)SpringBootVue項(xiàng)目都有直接的參考價(jià)值。1. 宿舍報(bào)修管理系統(tǒng)從論文到可運(yùn)行源碼的一次完整復(fù)現(xiàn)做后勤類管理系統(tǒng)的人應(yīng)該都有感觸報(bào)修流程看起來(lái)簡(jiǎn)單但一旦涉及多角色協(xié)同業(yè)務(wù)狀態(tài)流轉(zhuǎn)就會(huì)迅速膨脹。這份基于 Java 的宿舍報(bào)修管理系統(tǒng)資源是一個(gè)把「報(bào)修」這件事做得很完整的畢業(yè)設(shè)計(jì)項(xiàng)目技術(shù)棧是 SpringBoot Vue MySQL角色分管理員、用戶、維修員三種覆蓋了從宿舍分配、配件申請(qǐng)到維修進(jìn)度追蹤的完整閉環(huán)。論文部分把系統(tǒng)分析、數(shù)據(jù)庫(kù)設(shè)計(jì)、功能實(shí)現(xiàn)和測(cè)試用例都寫清楚了源碼部分則能直接跑起來(lái)做二次開發(fā)。適合正在做類似課題的學(xué)生或者想把校園報(bào)修場(chǎng)景數(shù)字化、需要一套基礎(chǔ)代碼的開發(fā)者。我拆完這套資源最大的感受是它的價(jià)值不在某個(gè)高深算法而在于把報(bào)修業(yè)務(wù)里那些容易被忽略的狀態(tài)節(jié)點(diǎn)設(shè)計(jì)得很完整這對(duì)課程設(shè)計(jì)和實(shí)際小規(guī)模部署都?jí)蛴谩?. 技術(shù)棧選型拆解為什么是 SpringBoot Vue MySQL2.1 后端框架SpringBoot 在報(bào)修系統(tǒng)里的真實(shí)定位這套資源后端用 Java SpringBoot 搭建SpringBoot 在這里解決的核心問(wèn)題不是高性能而是「開發(fā)效率」。報(bào)修管理這類業(yè)務(wù)系統(tǒng)本質(zhì)上是圍繞數(shù)據(jù)庫(kù)表的增刪改查加上一點(diǎn)業(yè)務(wù)狀態(tài)流轉(zhuǎn)SpringBoot 的自動(dòng)配置和約定優(yōu)于配置特性能極大減少 XML 配置和模板代碼讓開發(fā)者把精力集中在業(yè)務(wù)邏輯上。資源里的實(shí)現(xiàn)方式走的是典型的前后端分離思路。后端提供 RESTful API前端用 Vue 通過(guò) HTTP 請(qǐng)求調(diào)用接口數(shù)據(jù)以 JSON 格式交互。這種模式的優(yōu)點(diǎn)是前端頁(yè)面調(diào)整不會(huì)影響后端邏輯對(duì)后續(xù)要擴(kuò)展移動(dòng)端或者小程序端也比較方便。在一個(gè)典型的報(bào)修信息查詢接口里你可以看到這樣的 Controller 層代碼RestController RequestMapping(/baoxiu) public class BaoxiuInfoController { Autowired private BaoxiuInfoService baoxiuInfoService; GetMapping(/page) public Result queryPage(RequestParam(defaultValue 1) Integer pageNum, RequestParam(defaultValue 10) Integer pageSize, RequestParam(required false) String baoxiuren) { QueryWrapperBaoxiuInfo wrapper new QueryWrapper(); if (StringUtils.isNotBlank(baoxiuren)) { wrapper.like(baoxiuren, baoxiuren); } wrapper.orderByDesc(addtime); PageBaoxiuInfo page new Page(pageNum, pageSize); baoxiuInfoService.page(page, wrapper); return Result.success(page); } }這里需要說(shuō)明幾個(gè)關(guān)鍵點(diǎn)。QueryWrapper是 MyBatis-Plus 提供的條件構(gòu)造器用like方法實(shí)現(xiàn)模糊查詢orderByDesc按時(shí)間倒序排列——報(bào)修信息通常需要展示最新記錄。分頁(yè)用的是 MyBatis-Plus 的Page對(duì)象前端傳入 pageNum 和 pageSize 兩個(gè)參數(shù)就能控制每頁(yè)數(shù)據(jù)量。Result.success是統(tǒng)一響應(yīng)封裝把返回?cái)?shù)據(jù)包一層前端就能根據(jù) code 判斷請(qǐng)求是否成功。資源里的后端結(jié)構(gòu)不是簡(jiǎn)單的單文件堆砌而是分包明確controller 層處理請(qǐng)求路由service 層寫業(yè)務(wù)邏輯mapper 層負(fù)責(zé)數(shù)據(jù)庫(kù)操作entity 層定義實(shí)體類。這種分層做法在應(yīng)對(duì)報(bào)修業(yè)務(wù)里那些多表關(guān)聯(lián)查詢時(shí)很有用比如報(bào)修信息要關(guān)聯(lián)用戶表查姓名、關(guān)聯(lián)宿舍表查位置、關(guān)聯(lián)維修員表查分配情況。2.2 前端與數(shù)據(jù)庫(kù)Vue 組件化開發(fā)和 MySQL 表設(shè)計(jì)思路前端的 Vue 部分資源里用的是 Vue 2 Element UI 組件庫(kù)。Element UI 的表格、表單、彈窗組件能快速搭建管理后臺(tái)界面對(duì)做課設(shè)或者內(nèi)部管理系統(tǒng)來(lái)說(shuō)是最省力的方案。頁(yè)面結(jié)構(gòu)上登錄頁(yè)、注冊(cè)頁(yè)、用戶報(bào)修頁(yè)、管理員后臺(tái)頁(yè)、維修員任務(wù)頁(yè)是分開的通過(guò) Vue Router 做路由跳轉(zhuǎn)。數(shù)據(jù)庫(kù)設(shè)計(jì)是這套資源里比較扎實(shí)的部分。MySQL 數(shù)據(jù)庫(kù)配合 Navicat 可視化工具管理核心表包括管理員表 users、用戶表 yonghu、維修員表 weixiuyuan、宿舍信息表 sushexinxi、報(bào)修信息表 baoxiuxinxi、配件設(shè)備表 peijianshebei 等一共十幾張表每張表的字段設(shè)計(jì)都和業(yè)務(wù)場(chǎng)景對(duì)應(yīng)。以報(bào)修信息表為例字段設(shè)計(jì)基本覆蓋了報(bào)修業(yè)務(wù)流程的所有節(jié)點(diǎn)字段名類型說(shuō)明備注idbigint主鍵自增baoxiubianhaovarchar報(bào)修編號(hào)唯一baoxiurenvarchar報(bào)修人關(guān)聯(lián)用戶賬號(hào)baoxiuneirongtext報(bào)修內(nèi)容故障描述baoxiushijiandatetime報(bào)修時(shí)間默認(rèn)當(dāng)前時(shí)間weixiuyuanvarchar維修員接單后回填weixiuzhuangtaivarchar維修狀態(tài)待接單/維修中/已完成wanchengshijiandatetime完成時(shí)間狀態(tài)變更時(shí)更新這套表設(shè)計(jì)的巧妙之處在于它把報(bào)修業(yè)務(wù)的狀態(tài)流轉(zhuǎn)直接體現(xiàn)在表的字段變更上。報(bào)修單創(chuàng)建時(shí)只有報(bào)修人和報(bào)修內(nèi)容維修員接單后回填維修員字段維修過(guò)程中更新狀態(tài)完成后再寫入完成時(shí)間。這個(gè)思路比額外建一張狀態(tài)流轉(zhuǎn)表要簡(jiǎn)單直接對(duì)業(yè)務(wù)規(guī)模不大的場(chǎng)景完全夠用。2.3 權(quán)限控制三種角色在資源里是怎么落地的角色權(quán)限是管理系統(tǒng)的核心這套資源在登錄接口里實(shí)現(xiàn)了基于角色的路由控制。登錄成功后后端根據(jù)不同角色返回不同菜單權(quán)限前端再根據(jù)權(quán)限渲染對(duì)應(yīng)的導(dǎo)航欄。PostMapping(/login) public Result login(RequestBody Users user) { QueryWrapperUsers wrapper new QueryWrapper(); wrapper.eq(username, user.getUsername()) .eq(password, user.getPassword()); Users loginUser usersService.getOne(wrapper); if (loginUser null) { return Result.error(用戶名或密碼錯(cuò)誤); } String token UUID.randomUUID().toString().replace(-, ); redisTemplate.opsForValue().set(token, loginUser.getUsername(), 2, TimeUnit.HOURS); MapString, Object data new HashMap(); data.put(token, token); data.put(role, loginUser.getRole()); data.put(username, loginUser.getUsername()); return Result.success(data); }這段登錄邏輯有幾個(gè)地方值得注意。密碼校驗(yàn)用的是最簡(jiǎn)單直接的等值查詢這是很多課設(shè)項(xiàng)目的常見做法但生產(chǎn)環(huán)境必須換成加密存儲(chǔ)。UUID.randomUUID()生成的隨機(jī)字符串作為 token存到 Redis 里并設(shè)置 2 小時(shí)過(guò)期時(shí)間這樣能控制會(huì)話有效期。返回的 role 字段告訴前端當(dāng)前用戶是什么角色前端拿這個(gè)字段控制菜單顯示。資源里的權(quán)限控制粒度是「角色-菜單」級(jí)別的也就是不同角色看到不同菜單但同角色內(nèi)沒(méi)有更細(xì)分的操作權(quán)限區(qū)分。對(duì)于課程設(shè)計(jì)和小型后勤系統(tǒng)來(lái)說(shuō)這個(gè)粒度夠了但如果要做更嚴(yán)格的權(quán)限管理可以考慮引入 Spring Security 或者 Shiro 做細(xì)粒度控制。3. 數(shù)據(jù)庫(kù)物理設(shè)計(jì)從 E-R 圖到建表 SQL 的落地細(xì)節(jié)3.1 核心表關(guān)系用戶、宿舍、維修員、報(bào)修單怎么關(guān)聯(lián)這套資源的數(shù)據(jù)庫(kù)設(shè)計(jì)遵循了第三范式的基本要求各表之間通過(guò)外鍵邏輯建立關(guān)聯(lián)。用戶表 yonghu 和宿舍分配表 sushefenpei 通過(guò)用戶賬號(hào)字段關(guān)聯(lián)宿舍信息表 sushexinxi 和宿舍分配表通過(guò)宿舍名稱關(guān)聯(lián)報(bào)修信息表 baoxiuxinxi 同時(shí)關(guān)聯(lián)用戶表和維修員表。有一個(gè)設(shè)計(jì)細(xì)節(jié)值得展開說(shuō)說(shuō)。宿舍信息表和宿舍分配表是分開的宿舍信息表記錄的是靜態(tài)數(shù)據(jù)——宿舍名稱、類型、樓棟、房間號(hào)、可住人數(shù)、已住人數(shù)、床位數(shù)量宿舍分配表記錄的是動(dòng)態(tài)數(shù)據(jù)——誰(shuí)住在哪個(gè)宿舍的哪個(gè)床位、分配日期、備注。這種拆分避免了一個(gè)學(xué)生換宿舍時(shí)要去修改宿舍表的問(wèn)題只需要在分配表里更新記錄。建表 SQL 里能看到一些常見的設(shè)計(jì)約定。所有表都有 id 主鍵和 addtime 時(shí)間戳字段id 用 bigint 類型自增addtime 默認(rèn)值設(shè)為CURRENT_TIMESTAMP。這類約定在 MyBatis-Plus 的自動(dòng)填充功能下很好用插入數(shù)據(jù)時(shí)不用手動(dòng)維護(hù)創(chuàng)建時(shí)間字段。CREATE TABLE baoxiuxinxi ( id bigint(20) NOT NULL AUTO_INCREMENT, addtime timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, baoxiubianhao varchar(20) NOT NULL, baoxiuren varchar(20) DEFAULT NULL, baoxiuneirong longtext, weixiuyuan varchar(20) DEFAULT NULL, weixiuzhuangtai varchar(20) DEFAULT 待接單, PRIMARY KEY (id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4;這里有幾個(gè)細(xì)節(jié)要注意。longtext類型用于報(bào)修內(nèi)容字段因?yàn)楣收厦枋隹赡鼙容^長(zhǎng)。weixiuzhuangtai字段設(shè)置了默認(rèn)值 待接單這樣創(chuàng)建報(bào)修單時(shí)不用顯式傳這個(gè)字段。字符集用utf8mb4而不是utf8原因是utf8mb4支持完整的 Unicode 字符包括 emoji 表情避免用戶在報(bào)修內(nèi)容里輸入特殊字符時(shí)出現(xiàn)亂碼。3.2 權(quán)限表的 token 處理機(jī)制管理員表 users 和 token 表的設(shè)計(jì)有一個(gè)值得學(xué)習(xí)的地方。token 表和用戶表是一對(duì)一關(guān)系token 表里有 userid、username、tablename、role、token、expiratedtime 字段。這里的 tablename 字段有點(diǎn)意思它記錄的是當(dāng)前 token 對(duì)應(yīng)哪個(gè)用戶表的表名。為什么需要這個(gè)字段因?yàn)橄到y(tǒng)有三類用戶——管理員、普通用戶、維修員——他們可能存儲(chǔ)在三張不同的表里。token 表里的 tablename 字段就是為了區(qū)分這個(gè) token 是哪個(gè)登錄主體的。這種設(shè)計(jì)在課設(shè)項(xiàng)目里比較常見雖然用統(tǒng)一的用戶表加角色字段也能實(shí)現(xiàn)但對(duì)于已有三張表的數(shù)據(jù)庫(kù)結(jié)構(gòu)這種擴(kuò)展方式改動(dòng)最小。過(guò)期時(shí)間expiratedtime的處理邏輯也需要留意。資源里的實(shí)現(xiàn)是在登錄時(shí)生成過(guò)期時(shí)間在攔截器里校驗(yàn)當(dāng)前時(shí)間是否超過(guò)過(guò)期時(shí)間。如果你把這套代碼用于生產(chǎn)建議把這個(gè)校驗(yàn)邏輯統(tǒng)一放到攔截器或者過(guò)濾器里避免在每個(gè) Controller 里重復(fù)寫。3.3 常用查詢場(chǎng)景的索引視角從檢索能力角度看這套表設(shè)計(jì)報(bào)修信息表的 baoxiubianhao 字段值得建唯一索引因?yàn)闃I(yè)務(wù)上需要通過(guò)報(bào)修編號(hào)快速定位單子。用戶表的 yonghuzhanghao用戶賬號(hào)字段在登錄時(shí)做等值查詢也應(yīng)該建索引。宿舍信息表的 sushemingcheng 字段在宿舍分配時(shí)做關(guān)聯(lián)查詢加普通索引就夠了。資源里的 SQL 腳本已經(jīng)包含了一些索引定義但在 Navicat 里導(dǎo)入后建議再檢查一遍重點(diǎn)看那些經(jīng)常出現(xiàn)在 WHERE 條件和 JOIN 條件里的字段是否建了索引。實(shí)際操作中我遇到過(guò)這種情況數(shù)據(jù)量小的時(shí)候感覺不到區(qū)別一旦報(bào)修記錄累積到幾千條全表掃描的查詢延遲就會(huì)明顯增加。4. 核心功能實(shí)現(xiàn)報(bào)修閉環(huán)的三個(gè)關(guān)鍵模塊4.1 報(bào)修流程從用戶提單到維修員接單的狀態(tài)機(jī)報(bào)修管理是這套系統(tǒng)的核心業(yè)務(wù)流整個(gè)流程可以抽象成一條狀態(tài)鏈用戶提交報(bào)修 → 管理員審核或直接進(jìn)入待接單池 → 維修員接單 → 維修中 → 完成。資源里的實(shí)現(xiàn)方式是用戶在前端填寫報(bào)修內(nèi)容提交報(bào)修單進(jìn)入待接單狀態(tài)維修員在待接單列表里查看所有未接單的工單接單后狀態(tài)變?yōu)榫S修中維修完成后更新狀態(tài)為已完成。后端的接單接口是流程的關(guān)鍵節(jié)點(diǎn)代碼大致是PostMapping(/jiedan) public Result jiedan(RequestBody BaoxiuInfo baoxiuInfo) { BaoxiuInfo info baoxiuInfoService.getById(baoxiuInfo.getId()); if (info null) { return Result.error(報(bào)修單不存在); } if (!待接單.equals(info.getWeixiuzhuangtai())) { return Result.error(該報(bào)修單已被接單); } info.setWeixiuyuan(baoxiuInfo.getWeixiuyuan()); info.setWeixiuzhuangtai(維修中); info.setJiedanshijian(new Date()); baoxiuInfoService.updateById(info); return Result.success(接單成功); }這段代碼體現(xiàn)了一個(gè)容易被忽視的業(yè)務(wù)約束接單前的狀態(tài)校驗(yàn)。如果在更新狀態(tài)前不檢查當(dāng)前報(bào)修單是否已經(jīng)是「待接單」?fàn)顟B(tài)就可能出現(xiàn)兩個(gè)維修員同時(shí)搶同一張單子后提交的人把前一個(gè)人的信息覆蓋掉。這種并發(fā)問(wèn)題在這個(gè)資源里用的是「先查再改」的方式雖然不做數(shù)據(jù)庫(kù)層面的樂(lè)觀鎖但對(duì)課設(shè)場(chǎng)景夠用。如果你想做得更穩(wěn)可以在表里加一個(gè) version 字段在更新 SQL 里加WHERE version #{oldVersion}的樂(lè)觀鎖條件。4.2 宿舍分配用戶、宿舍、床位三者關(guān)聯(lián)的精確度問(wèn)題宿舍分配模塊解決的核心問(wèn)題是「哪個(gè)學(xué)生住在哪個(gè)宿舍的哪個(gè)床位」。資源里的實(shí)現(xiàn)邏輯是管理員在后臺(tái)選擇宿舍再選擇用戶接著填入床位號(hào)保存后生成一條分配記錄同時(shí)更新宿舍表的已住人數(shù)。這里有一個(gè)容易踩坑的業(yè)務(wù)點(diǎn)宿舍的容量校驗(yàn)。分配宿舍時(shí)應(yīng)該校驗(yàn)該宿舍當(dāng)前已住人數(shù)是否小于可住人數(shù)否則會(huì)出現(xiàn)超員分配。我在拆解資源時(shí)看了一下這個(gè)校驗(yàn)邏輯在源碼里并不完整需要自己補(bǔ)上。補(bǔ)上這個(gè)校驗(yàn)的代碼邏輯如下// 查詢宿舍當(dāng)前狀態(tài) Sushexinxi sushe sushexinxiService.getById(sushefenpei.getSusheId()); if (sushe null) { return Result.error(宿舍不存在); } int yizhu Integer.parseInt(sushe.getYizhurenshu()); int kezhu Integer.parseInt(sushe.getKezhurenshu()); if (yizhu kezhu) { return Result.error(該宿舍已住滿); } // 執(zhí)行分配 sushefenpeiService.save(sushefenpei); // 更新已住人數(shù) sushe.setYizhurenshu(String.valueOf(yizhu 1)); sushexinxiService.updateById(sushe);這里有個(gè)細(xì)節(jié)值得注意yizhurenshu和kezhurenshu在表設(shè)計(jì)里用的是 varchar 類型而不是 int。這在數(shù)據(jù)庫(kù)設(shè)計(jì)里其實(shí)不算最佳實(shí)踐因?yàn)橐鰯?shù)值比較時(shí)必須先轉(zhuǎn)型。這個(gè)資源這么設(shè)計(jì)可能是因?yàn)閷?shí)體類里為了方便前端展示用了字符串類型。如果你在二次開發(fā)時(shí)要把這塊做嚴(yán)謹(jǐn)建議把這兩個(gè)字段改成 int 類型Mapper 層做一層類型轉(zhuǎn)換即可。4.3 配件申請(qǐng)與庫(kù)存聯(lián)動(dòng)維修員領(lǐng)料的后臺(tái)邏輯配件申請(qǐng)模塊的連接點(diǎn)在維修員和配件設(shè)備之間。維修員接單后如果發(fā)現(xiàn)維修需要更換配件可以在線提交配件申請(qǐng)管理員審核通過(guò)后配件庫(kù)存數(shù)量需要相應(yīng)減少。PostMapping(/shenqing) public Result shenqing(RequestBody Peijianshenqing peijianShenqing) { Peijianshebei peijian peijianshebeiService.getById(peijianShenqing.getPeijianId()); if (peijian null) { return Result.error(配件不存在); } int kucun Integer.parseInt(peijian.getKucun()); int shenqingshu Integer.parseInt(peijianShenqing.getShenqingshu()); if (kucun shenqingshu) { return Result.error(庫(kù)存不足); } // 減少庫(kù)存 peijian.setKucun(String.valueOf(kucun - shenqingshu)); peijianshebeiService.updateById(peijian); // 記錄申請(qǐng)單 peijianShenqing.setZhuangtai(已發(fā)放); peijianshenqingService.save(peijianShenqing); return Result.success(申請(qǐng)成功); }這個(gè)接口是「先扣庫(kù)存再保存申請(qǐng)單」的順序按正常事務(wù)語(yǔ)義來(lái)說(shuō)應(yīng)該在一個(gè)事務(wù)里執(zhí)行否則如果保存申請(qǐng)單失敗庫(kù)存已經(jīng)被扣掉了。資源里沒(méi)有顯式加Transactional注解這是一個(gè)潛在隱患。你在二次開發(fā)時(shí)建議加上這個(gè)注解讓這兩個(gè)數(shù)據(jù)庫(kù)操作處于同一事務(wù)中保證數(shù)據(jù)一致性。配件模塊還有一個(gè)值得提的設(shè)計(jì)配件分類表和配件設(shè)備表分離。分類表存配件類型設(shè)備表存具體配件通過(guò)分類 ID 關(guān)聯(lián)。這種設(shè)計(jì)的好處是新增配件時(shí)不用改表結(jié)構(gòu)選擇已有分類就行。4.4 維修進(jìn)度查詢用戶端的狀態(tài)可視化維修進(jìn)度模塊是用戶體驗(yàn)的加分項(xiàng)。用戶在提交報(bào)修單后可以在「維修進(jìn)度查詢」頁(yè)面看到自己所有報(bào)修單的當(dāng)前狀態(tài)。這里的實(shí)現(xiàn)思路很簡(jiǎn)單就是按照當(dāng)前登錄用戶的賬號(hào)查詢?cè)撚脩羲袌?bào)修單按時(shí)間倒序展示狀態(tài)字段。進(jìn)度展示的字段設(shè)計(jì)覆蓋了報(bào)修全生命周期提交時(shí)間、當(dāng)前狀態(tài)、維修員姓名、維修完成時(shí)間。用戶在列表里能直觀看到自己的單子處于什么環(huán)節(jié)是待接單、維修中還是已完成。這塊功能實(shí)現(xiàn)不復(fù)雜但它是用戶視角里感知系統(tǒng)價(jià)值最直接的地方。需要注意的是維修進(jìn)度查詢?cè)谫Y源里的實(shí)現(xiàn)是按報(bào)修單狀態(tài)字段實(shí)時(shí)讀取的不是寫一條進(jìn)度日志表記錄每次狀態(tài)變更。如果你需要做「完整操作留痕」比如記錄誰(shuí)在什么時(shí)間把狀態(tài)從 A 改成了 B那就需要額外建一張狀態(tài)變更日志表在每次狀態(tài)變化的代碼邏輯里插入一條日志記錄。5. 避坑指南五個(gè)讓系統(tǒng)在演示現(xiàn)場(chǎng)翻車的常見問(wèn)題5.1 端口被占用導(dǎo)致 Tomcat 啟動(dòng)失敗現(xiàn)象在 IDEA 里啟動(dòng) SpringBoot 項(xiàng)目控制臺(tái)直接報(bào)Port 8080 was already in use整個(gè)應(yīng)用起不來(lái)。原因本機(jī)之前有別的程序占用了 8080 端口或者上一次運(yùn)行項(xiàng)目的進(jìn)程沒(méi)被完全殺掉。這在課設(shè)演示前最容易發(fā)生越急越找不到是哪個(gè)程序占的。解決Windows 系統(tǒng)用netstat -ano | findstr 8080查出占用端口的 PID然后到任務(wù)管理器里結(jié)束對(duì)應(yīng)進(jìn)程或者直接改application.properties里的server.port配置把端口換掉。我一般建議直接用后者省事改完重啟就行。5.2 MySQL 時(shí)區(qū)問(wèn)題導(dǎo)致時(shí)間字段相差 8 小時(shí)現(xiàn)象系統(tǒng)里新增報(bào)修記錄的時(shí)間在頁(yè)面上顯示比當(dāng)前時(shí)間晚了 8 小時(shí)。原因MySQL 8.0 版本之后的連接驅(qū)動(dòng)對(duì)時(shí)區(qū)敏感如果 JDBC URL 里沒(méi)有指定 serverTimezone 參數(shù)默認(rèn)使用服務(wù)器本地時(shí)區(qū)而國(guó)內(nèi)環(huán)境經(jīng)常會(huì)匹配到 UTC 標(biāo)準(zhǔn)時(shí)間。解決在數(shù)據(jù)庫(kù)連接 URL 里明確加上?serverTimezoneAsia/ShanghaiuseUnicodetruecharacterEncodingutf8。這個(gè)問(wèn)題在資源配套的數(shù)據(jù)庫(kù)連接配置文件里其實(shí)已經(jīng)處理過(guò)但如果你自己重裝了 MySQL 或者改了連接串很容易重新踩進(jìn)去。配置改完重啟應(yīng)用時(shí)間就正常了。5.3 登錄頁(yè)驗(yàn)證碼或密碼校驗(yàn)不走攔截器現(xiàn)象未登錄狀態(tài)直接訪問(wèn)后端接口地址居然能拿到數(shù)據(jù)登錄功能形同虛設(shè)。原因SpringBoot 項(xiàng)目里的攔截器只對(duì)部分 URL 生效如果配置 WebMvcConfigurer 時(shí)攔截路徑寫得不對(duì)比如只攔截了/admin/**漏掉了/api/**或者登錄接口本身就在攔截名單里就會(huì)導(dǎo)致鑒權(quán)邏輯不生效。解決先看資源里的攔截器配置類把不需要登錄就能訪問(wèn)的接口路徑全部加到放行列表里比如/login、/register其余接口全部攔截。放行列表怎么寫可以看一下攔截器的實(shí)現(xiàn)一般是在excludePathPatterns()方法后面追加。把攔截器的匹配規(guī)則改成/**再排除掉白名單路徑是最穩(wěn)妥的。5.4 Navicat 導(dǎo)入 SQL 腳本時(shí)外鍵約束失敗現(xiàn)象把資源里的數(shù)據(jù)庫(kù)腳本用 Navicat 導(dǎo)入過(guò)程中報(bào)錯(cuò)提示Cannot add foreign key constraint導(dǎo)致部分表創(chuàng)建失敗。原因腳本里建表的順序可能有問(wèn)題先創(chuàng)建了有外鍵的子表后創(chuàng)建了被引用的父表或者是兩張表的字段類型不完全匹配比如一邊是 bigint 另一邊是 int。解決不用 Navicat 直接導(dǎo)整份腳本改成先執(zhí)行建表語(yǔ)句創(chuàng)建所有表再單獨(dú)執(zhí)行外鍵約束的修改語(yǔ)句?;蛘吒纱喟涯_本里所有外鍵約束刪掉表結(jié)構(gòu)建好之后手動(dòng)在 Navicat 里補(bǔ)外鍵關(guān)系。課設(shè)評(píng)審階段外鍵約束是否完整影響不大關(guān)鍵是表能建成功、數(shù)據(jù)能存進(jìn)去。5.5 前端頁(yè)面請(qǐng)求跨域?qū)е陆涌谀貌坏綌?shù)據(jù)現(xiàn)象前端 Vue 項(xiàng)目啟動(dòng)后頁(yè)面能打開但所有數(shù)據(jù)都是空的瀏覽器控制臺(tái)報(bào)Access to XMLHttpRequest has been blocked by CORS policy錯(cuò)誤。原因前端運(yùn)行在 8080 端口后端運(yùn)行在 8081 端口瀏覽器出于同源策略限制禁止跨端口發(fā)起 AJAX 請(qǐng)求。解決后端加一個(gè)跨域配置類實(shí)現(xiàn) WebMvcConfigurer 接口并重寫 addCorsMappings 方法。代碼如下Configuration public class CorsConfig implements WebMvcConfigurer { Override public void addCorsMappings(CorsRegistry registry) { registry.addMapping(/**) .allowedOriginPatterns(*) .allowedMethods(GET, POST, PUT, DELETE, OPTIONS) .allowedHeaders(*) .allowCredentials(true) .maxAge(3600); } }注意allowedOriginPatterns和allowCredentials(true)要配合使用否則有的瀏覽器版本會(huì)報(bào)錯(cuò)。6. 從課程設(shè)計(jì)到能用二次開發(fā)時(shí)我最先動(dòng)刀的五個(gè)位置拿到這套資源如果你是拿來(lái)交作業(yè)跑通就完事了。但如果你想讓這個(gè)系統(tǒng)真正能放進(jìn)一個(gè)宿舍樓里用我建議按下面的優(yōu)先級(jí)做改造。第一處是密碼加密。資源里的密碼是明文存儲(chǔ)的登錄也是直接WHERE username ? AND password ?查詢?,F(xiàn)實(shí)場(chǎng)景里哪怕只是內(nèi)部系統(tǒng)明文密碼也是不可接受的。用 Spring Security 的 BCryptPasswordEncoder也就是一個(gè)加密工具類注冊(cè)時(shí)對(duì)密碼做哈希登錄時(shí)用matches方法做比對(duì)改動(dòng)量在注冊(cè)接口和登錄接口兩個(gè)地方半小時(shí)能改完。第二處是報(bào)修單狀態(tài)的并發(fā)控制。之前提到接單接口的「先查再改」邏輯存在并發(fā)覆蓋風(fēng)險(xiǎn)加樂(lè)觀鎖是最省事的方案。在報(bào)修信息表加 version 字段更新時(shí)用UPDATE baoxiuxinxi SET weixiuzhuangtai 維修中, version version 1 WHERE id ? AND version ?受影響行數(shù)為 0 就說(shuō)明有人搶先改過(guò)了提示用戶刷新后重試。第三處是文件上傳和圖片展示。報(bào)修單里如果能附帶現(xiàn)場(chǎng)照片會(huì)大幅減少溝通成本。資源里沒(méi)有實(shí)現(xiàn)圖片上傳需要自己補(bǔ)一個(gè)上傳接口把文件保存到服務(wù)器本地目錄訪問(wèn)時(shí)通過(guò)一個(gè)映射路徑直接讀取。注意上傳目錄要放在應(yīng)用外部不然重新部署的時(shí)候文件會(huì)丟。第四處是數(shù)據(jù)備份。很多課設(shè)系統(tǒng)跑著跑著數(shù)據(jù)庫(kù)就崩了崩了就沒(méi)有后悔藥。至少配置一個(gè) MySQL 定時(shí)任務(wù)每天凌晨用mysqldump把整個(gè)庫(kù)導(dǎo)出一份 SQL 文件到備份目錄。這個(gè)操作不寫代碼一行 crontab 就能解決。從那以后我每次接手這類系統(tǒng)都強(qiáng)制自己先讀一遍數(shù)據(jù)庫(kù)表設(shè)計(jì)文檔再動(dòng)手改代碼。表結(jié)構(gòu)理順了業(yè)務(wù)邏輯八九不離十就能看懂。這套資源的表設(shè)計(jì)雖然不是完美的但它把報(bào)修業(yè)務(wù)的關(guān)鍵實(shí)體和狀態(tài)都覆蓋到了適合做基礎(chǔ)再去改。希望這篇拆解能幫你在自己的項(xiàng)目里少走幾步彎路。本文還有配套的精品資源點(diǎn)擊獲取