開發(fā)實(shí)戰(zhàn):從數(shù)據(jù)庫設(shè)計(jì)到前后端聯(lián)調(diào))
又到一年畢設(shè)季后臺(tái)私信問得最多的還是那幾樣SpringBootVue車牌管理系統(tǒng)、圖書館管理系統(tǒng)、學(xué)生選課系統(tǒng)。今天就把我用得最順手、改過好幾版的一個(gè)方案徹底聊透——基于SpringBootVue的前后端分離車輛管理系統(tǒng)配合MySQL存儲(chǔ)拿來交畢設(shè)、課設(shè)或者自己練手都合適。這套東西我前前后后帶過好幾個(gè)學(xué)弟學(xué)妹從零搭到答辯踩過的坑、改過的文檔、調(diào)過的接口都在腦子里一篇講完不廢話。車輛管理系統(tǒng)說白了就是把單位的車、司機(jī)、出車記錄、維修保養(yǎng)、油耗這些東西從Excel里搬到一個(gè)能登錄、能分配權(quán)限的網(wǎng)頁上來。它的功能邊界非常清晰很適合做成一個(gè)完整的前后端分離項(xiàng)目后端管業(yè)務(wù)邏輯和數(shù)據(jù)庫前端管頁面交互中間用JSON接口對(duì)接。這恰好就是企業(yè)里最主流的開發(fā)模式所以院校也喜歡拿它當(dāng)畢設(shè)題目。這篇內(nèi)容我會(huì)從技術(shù)選型思路、數(shù)據(jù)庫設(shè)計(jì)、核心模塊實(shí)現(xiàn)、前后端聯(lián)調(diào)、論文答辯話術(shù)、常見排錯(cuò)這幾個(gè)維度全部過一遍。不管你是剛把SpringBoot跑通的入門選手還是已經(jīng)在寫Mapper層的老手都能從里面拿到能直接用的東西。1. 項(xiàng)目整體拆解為什么這套組合經(jīng)久不衰1.1 車輛管理系統(tǒng)的需求邊界到底劃在哪很多人一拿到的題目就懵不知道“車輛管理系統(tǒng)”該做多大。我見過最夸張的學(xué)弟想在一個(gè)畢設(shè)里做GPS實(shí)時(shí)定位、違章查詢、ETC扣費(fèi)聯(lián)動(dòng)最后把自己寫崩潰了。講實(shí)話畢設(shè)和課設(shè)階段的車輛管理系統(tǒng)核心需求永遠(yuǎn)圍繞“人、車、單、賬”四個(gè)字打轉(zhuǎn)。所謂“人”就是兩類角色管理員和普通用戶。管理員管車輛檔案、司機(jī)檔案、審批派車普通用戶申請(qǐng)用車、查看自己的記錄、提交還車信息?!败嚒敝傅氖擒囕v基礎(chǔ)資料車牌號(hào)、品牌型號(hào)、購置日期、座位數(shù)、當(dāng)前狀態(tài)可用/已派出/維修中/報(bào)廢?!皢巍本褪浅鲕嚿暾?qǐng)單、還車記錄、維修保養(yǎng)記錄、加油記錄?!百~”是月度的里程統(tǒng)計(jì)、油耗匯總、費(fèi)用明細(xì)。這四個(gè)維度撐起來的系統(tǒng)既有增刪改查的常規(guī)操作又有角色權(quán)限的區(qū)分還有統(tǒng)計(jì)報(bào)表這類稍微帶點(diǎn)復(fù)雜度的功能用來應(yīng)付畢設(shè)的“工作量”標(biāo)準(zhǔn)剛剛好。別貪多把基礎(chǔ)閉環(huán)做扎實(shí)比堆一堆跑不通的花哨功能得分高得多。1.2 為什么選SpringBootVue而不是別的技術(shù)棧這個(gè)問題答辯必問我建議你得能講清楚。SpringBoot是基于Spring的快速開發(fā)框架好處是內(nèi)置了Tomcat不用再單獨(dú)配置服務(wù)器打出一個(gè)jar包就能跑。它把SpringMVC、依賴注入、自動(dòng)配置這些底層繁瑣的東西都封好了你寫Controller就專注寫業(yè)務(wù)邏輯這對(duì)學(xué)生來說極其友好。Vue作為前端框架核心優(yōu)勢(shì)是數(shù)據(jù)驅(qū)動(dòng)視圖。你維護(hù)一個(gè)data對(duì)象頁面上的表格、表單、下拉框會(huì)自動(dòng)跟著變不需要像jQuery那樣手動(dòng)操作DOM。配合Element UI組件庫表格、彈窗、表單、分頁這些界面元素直接拿來用寫出來的頁面干凈又規(guī)范。MySQL就更不用說了開源、輕量、文檔全和Java配合最成熟你隨便搜一個(gè)問題都有答案。這套組合在國內(nèi)中小型系統(tǒng)里占有率極高你畢業(yè)去公司大概率也會(huì)碰到類似的項(xiàng)目結(jié)構(gòu)用它做畢設(shè)不是應(yīng)付交差是真能積累實(shí)戰(zhàn)經(jīng)驗(yàn)。1.3 源碼目錄結(jié)構(gòu)一份標(biāo)準(zhǔn)的腳手架長什么樣拿到源碼或者自己新建項(xiàng)目時(shí)先把目錄結(jié)構(gòu)搞清楚后面才不會(huì)迷路。我習(xí)慣這樣組織一個(gè)前后端分離的車輛管理系統(tǒng)vehicle-manage/ ├── backend/ # SpringBoot后端 │ ├── src/main/java │ │ └── com/example/vehicle │ │ ├── controller/ # 接口層接收前端請(qǐng)求 │ │ ├── service/ # 業(yè)務(wù)邏輯層 │ │ ├── mapper/ # 數(shù)據(jù)庫操作層MyBatis接口 │ │ ├── entity/ # 實(shí)體類對(duì)應(yīng)數(shù)據(jù)庫表 │ │ ├── config/ # 配置類比如CORS跨域配置 │ │ └── common/ # 通用返回結(jié)果、異常處理 │ ├── src/main/resources │ │ ├── mapper/ # XML文件寫復(fù)雜SQL │ │ └── application.yml # 端口、數(shù)據(jù)庫連接配置 │ └── pom.xml ├── frontend/ # Vue前端 │ ├── src │ │ ├── views/ # 頁面組件 │ │ ├── router/ # 路由配置 │ │ ├── api/ # axios請(qǐng)求封裝 │ │ ├── store/ # 登錄狀態(tài)管理 │ │ └── main.js │ └── package.json └── sql/ └── vehicle.sql # 初始化數(shù)據(jù)庫腳本這種結(jié)構(gòu)最標(biāo)準(zhǔn)也最容易講清楚。你論文里的系統(tǒng)架構(gòu)圖基本就是照著這個(gè)畫。注意一點(diǎn)controller盡量只做參數(shù)接收和結(jié)果返回業(yè)務(wù)邏輯寫在service里這樣答辯時(shí)導(dǎo)師問你“這個(gè)模塊怎么設(shè)計(jì)的”你能分層講得明明白白。2. 數(shù)據(jù)庫設(shè)計(jì)車輛系統(tǒng)能不能撐住就看表建得怎么樣2.1 核心表結(jié)構(gòu)和字段設(shè)計(jì)我對(duì)每一個(gè)帶過的學(xué)弟都會(huì)強(qiáng)調(diào)數(shù)據(jù)庫設(shè)計(jì)是畢設(shè)的命門之一答辯老師必看表設(shè)計(jì)合不合理。車輛管理系統(tǒng)最簡化的表方案需要六張表用戶表、車輛表、出車申請(qǐng)表、還車記錄表、維修保養(yǎng)表、加油記錄表。有時(shí)加一張字典表用來存車輛類型、使用狀態(tài)這種枚舉值。用戶表字段不用多id、username、password、real_name、phone、role、status、create_time。密碼千萬別存明文用BCrypt加密這個(gè)細(xì)節(jié)寫進(jìn)論文里很加分。車輛表字段多一些id、plate_number、brand、model、vehicle_type、buy_date、seat_count、engine_number、status、remark。status建議用int或者tinyint0可用、1已派出、2維修中、3報(bào)廢用數(shù)字比字符串效率高也好擴(kuò)展。出車申請(qǐng)表是業(yè)務(wù)核心字段有id、user_id、vehicle_id、driver_name、start_place、end_place、start_time、end_time、reason、mileage、fuel_cost、status、create_time。status字段用來表示申請(qǐng)狀態(tài)0待審核、1已通過、2已駁回、3已完成。很多系統(tǒng)把送審和歸還合在一張表里我建議拆開或者用status區(qū)分因?yàn)椤吧暾?qǐng)”和“記錄”本質(zhì)上不是一個(gè)生命周期狀態(tài)。關(guān)聯(lián)關(guān)系要注意業(yè)務(wù)表都用關(guān)聯(lián)id不要直接存字符串。比如申請(qǐng)表里存user_id而不是user_name這樣后期做統(tǒng)計(jì)的時(shí)候用JOIN就能拿到名字改用戶姓名也不會(huì)影響歷史記錄。違反這個(gè)原則的表后期寫統(tǒng)計(jì)SQL時(shí)你只會(huì)想罵人。2.2 建庫腳本和初始化數(shù)據(jù)的坑啟動(dòng)項(xiàng)目前一定要有一條能用的SQL腳本。我見過很多同學(xué)拿到的源碼里SQL文件是殘缺的或者字段對(duì)不上實(shí)體類一啟動(dòng)就報(bào)Unknown column。拿到任何源碼第一件事是先看SQL文件里有哪些表再去對(duì)entity目錄下的類一個(gè)字段一個(gè)字段確認(rèn)。建庫時(shí)注意字符集統(tǒng)一用utf8mb4不然存車牌號(hào)里的生僻字或者特殊符號(hào)會(huì)報(bào)錯(cuò)。數(shù)據(jù)庫名我習(xí)慣用vehicle_db導(dǎo)入命令就拿Navicat或者命令行執(zhí)行SQL文件。初始化數(shù)據(jù)至少要有管理員賬號(hào)和一條可用車輛不然你前端登錄進(jìn)去看到空頁面還以為代碼寫錯(cuò)了。管理員密碼建議在SQL里就存成BCrypt加密后的值比如admin123加密后的字符串別直接用明文也算是一種規(guī)范。2.3 表之間的外鍵到底要不要加這是課設(shè)里很常見的問題。我建議邏輯上保留關(guān)聯(lián)關(guān)系但物理上少加真正的外鍵約束。什么意思就是你建表時(shí)不用FOREIGN KEY關(guān)鍵字去強(qiáng)制約束而是在業(yè)務(wù)代碼里保證數(shù)據(jù)一致。原因很簡單加了物理外鍵刪除車輛時(shí)如果有出車記錄引用它你會(huì)被數(shù)據(jù)庫擋住報(bào)錯(cuò)信息對(duì)新手特別不友好。可能你只是想測(cè)試一下刪除功能結(jié)果怎么都刪不掉。用邏輯外鍵刪除車輛時(shí)先檢查有沒有未完成的申請(qǐng)單有就在service層攔截沒就在車輛表里把status改成“報(bào)廢”而不是物理刪除。這種設(shè)計(jì)在生產(chǎn)環(huán)境也是主流寫論文的時(shí)候還能順帶提一句“為了性能和靈活性采用邏輯外鍵設(shè)計(jì)”顯得你有思考。3. 后端SpringBoot核心模塊實(shí)現(xiàn)3.1 統(tǒng)一返回結(jié)果與異常處理前端和后端聯(lián)調(diào)最煩的一件事就是接口返回格式不統(tǒng)一。有的接口返回對(duì)象有的返回List報(bào)錯(cuò)時(shí)一會(huì)兒是String一會(huì)兒是Map前端根本沒法寫統(tǒng)一處理。這個(gè)問題從一開始就要避免。我習(xí)慣定義一個(gè)Result類長這樣Data public class ResultT { private Integer code; // 200成功 500失敗 private String msg; // 提示信息 private T data; // 數(shù)據(jù) public static T ResultT success(T data) { ResultT result new Result(); result.setCode(200); result.setMsg(操作成功); result.setData(data); return result; } public static T ResultT error(String msg) { ResultT result new Result(); result.setCode(500); result.setMsg(msg); return result; } }所有的Controller接口都返回Result類型前端axios封裝里統(tǒng)一判斷code。這樣登錄失效、參數(shù)校驗(yàn)失敗、服務(wù)器內(nèi)部錯(cuò)誤都能用同一套邏輯處理。配合RestControllerAdvice做全局異常捕獲controller里就不用每個(gè)方法都寫try-catch代碼清爽很多。3.2 登錄認(rèn)證與JWT攔截器車輛管理系統(tǒng)肯定要分角色普通用戶不能去審批派車管理員要能看到所有記錄。最常見的方案是JWT 攔截器。登錄時(shí)驗(yàn)證用戶名密碼通過后生成一個(gè)token返回前端前端每次請(qǐng)求都在header里帶上token后端攔截器驗(yàn)證token并解析出用戶id和角色。核心代碼其實(shí)不多我貼一下攔截器的邏輯Component public class JwtInterceptor implements HandlerInterceptor { Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { String token request.getHeader(Authorization); if (StringUtils.isBlank(token)) { throw new BusinessException(未登錄); } // 解析token校驗(yàn)簽名和過期時(shí)間 Claims claims JwtUtil.parseToken(token.replace(Bearer , )); if (claims null) { throw new BusinessException(登錄已過期請(qǐng)重新登錄); } // 把用戶id放到request里后續(xù)controller可以直接取 request.setAttribute(userId, claims.get(userId)); return true; } }然后寫一個(gè)WebMvcConfigurer把攔截器注冊(cè)進(jìn)去排除登錄接口本身和靜態(tài)資源。這里最容易犯的錯(cuò)是攔截器沒有排除登錄接口導(dǎo)致前端登錄都調(diào)不通。我調(diào)試的順序永遠(yuǎn)是先直接訪問后端接口再從前端頁面調(diào)分段定位問題。3.3 分頁查詢與條件搜索的實(shí)現(xiàn)車輛列表、出車記錄、維修記錄這些頁面都要用到分頁和搜索。MyBatis結(jié)合PageHelper是最簡單的分頁方案頁面?zhèn)鱟urrent和size兩個(gè)參數(shù)后端直接開查。我寫一個(gè)典型的分頁查詢service看一下public PageResultVehicle pageVehicles(int current, int size, String plateNumber, Integer status) { PageHelper.startPage(current, size); ListVehicle list vehicleMapper.selectByCondition(plateNumber, status); PageInfoVehicle pageInfo new PageInfo(list); PageResultVehicle result new PageResult(); result.setTotal(pageInfo.getTotal()); result.setRecords(pageInfo.getList()); return result; }這里有個(gè)細(xì)節(jié)PageHelper.startPage后面必須緊跟一條Mapper查詢中間不能插入其他操作否則分頁會(huì)失效。這個(gè)坑我踩過好多次現(xiàn)在每次寫完都要確認(rèn)startPage和select之間沒有多余的代碼。模糊查詢用LIKE CONCAT(%, #{plateNumber}, %)注意別用${}直接拼字符串既防SQL注入也避免特殊字符把SQL搞壞。3.4 業(yè)務(wù)狀態(tài)流轉(zhuǎn)控制出車申請(qǐng)不只是簡單的增刪改查它有狀態(tài)流轉(zhuǎn)提交申請(qǐng) → 管理員審核通過 → 出車 → 歸還。每個(gè)狀態(tài)之間的轉(zhuǎn)換要有校驗(yàn)。比如已經(jīng)通過審核的單子就不能再隨便刪除否則統(tǒng)計(jì)數(shù)據(jù)全亂了。我的做法是在service層寫狀態(tài)變更方法比如auditApply(id, status, auditOpinion)方法內(nèi)部先查當(dāng)前狀態(tài)只允許“待審核”狀態(tài)變?yōu)椤巴ㄟ^”或“駁回”。如果狀態(tài)已經(jīng)是“通過”再調(diào)這個(gè)接口就直接拋異常。這種“狀態(tài)機(jī)”思維雖然代碼量不大但會(huì)讓你的系統(tǒng)看起來專業(yè)很多答辯時(shí)也容易講。4. 前端Vue頁面實(shí)現(xiàn)與聯(lián)調(diào)細(xì)節(jié)4.1 登錄頁與路由守衛(wèi)前端項(xiàng)目的入口是登錄頁。表單提交到后端/login接口拿到token之后存到localStorage同時(shí)把用戶信息放進(jìn)Vuex。這里注意token的存儲(chǔ)位置直接決定后面好不好調(diào)試我習(xí)慣只存token在localStorage用戶信息存Vuex里刷新頁面再調(diào)一次getInfo接口拉取。路由守衛(wèi)是必須寫的不然未登錄的人可以直接敲URL進(jìn)后臺(tái)頁面router.beforeEach((to, from, next) { const token localStorage.getItem(token); if (to.meta.requireAuth !token) { next(/login); } else { next(); } });還有個(gè)容易被忽視的點(diǎn)axios請(qǐng)求攔截器里把token掛到header上service.interceptors.request.use(config { const token localStorage.getItem(token); if (token) { config.headers[Authorization] Bearer token; } return config; });這兩段代碼配上之后整個(gè)系統(tǒng)的登錄驗(yàn)證就算閉環(huán)了。4.2 Element UI表格、表單與彈窗Vue頁面里占用開發(fā)時(shí)間最多的就是表格和表單的組合。車輛列表頁的結(jié)構(gòu)大概是頂部搜索欄車牌號(hào)輸入框、狀態(tài)下拉框、查詢按鈕中間表格展示車輛信息底部彈窗新增/編輯車輛。Element UI的el-table、el-pagination、el-dialog、el-form這四個(gè)組件足夠完成95%的頁面。表格列要注意格式化比如狀態(tài)字段在數(shù)據(jù)庫里是0、1、2頁面上不能直接顯示數(shù)字要用tag標(biāo)簽映射成中文。我常用的方式是在表格列里寫templateel-table-column label狀態(tài) template slot-scopescope el-tag :typestatusType(scope.row.status) {{ statusText(scope.row.status) }} /el-tag /template /el-table-columnstatusText和statusType寫成一個(gè)工具函數(shù)所有需要顯示狀態(tài)的地方共用避免每個(gè)頁面寫一份。提交表單時(shí)默認(rèn)的el-form會(huì)把數(shù)據(jù)綁到一個(gè)form對(duì)象里如果你直接把整個(gè)對(duì)象傳給后端可能會(huì)帶上莫名其妙的多余字段。比較好的做法是在提交前手動(dòng)構(gòu)造一個(gè)干凈的DTOconst submitData { plateNumber: this.form.plateNumber, brand: this.form.brand, model: this.form.model, status: this.form.status };這樣既不會(huì)傳臟數(shù)據(jù)接口參數(shù)也好維護(hù)。4.3 前端接口封裝規(guī)范我見過最亂的項(xiàng)目是每個(gè)頁面里直接寫axios.get(/api/vehicle/list)URL散落一地后端一改路徑前端要翻遍所有vue文件。正確做法是用api目錄統(tǒng)一管理接口按模塊拆分文件。// src/api/vehicle.js import request from /utils/request; export function getVehicleList(params) { return request({ url: /vehicle/page, method: get, params }); } export function addVehicle(data) { return request({ url: /vehicle/add, method: post, data }); } export function updateVehicle(data) { return request({ url: /vehicle/update, method: post, data }); }頁面里調(diào)用的時(shí)候import進(jìn)來用就行。這樣做的好處是接口路徑統(tǒng)一管理改一處全項(xiàng)目生效。而且每個(gè)模塊一個(gè)文件后端的controller結(jié)構(gòu)是什么樣前端的api文件就是什么樣對(duì)應(yīng)關(guān)系一目了然。4.4 跨域問題的解決方案前后端分離開發(fā)最煩的就是CORS跨域。前端8080端口后端8081端口默認(rèn)情況下瀏覽器會(huì)攔截跨域請(qǐng)求。解決方式有兩種一種是在后端寫配置類開啟CORS一種是用前端代理。我推薦開發(fā)階段用前端代理發(fā)布于生產(chǎn)時(shí)用后端CORS。Vue的vue.config.js里加這么一段module.exports { devServer: { port: 8080, proxy: { /api: { target: http://localhost:8081, changeOrigin: true } } } };這樣前端請(qǐng)求/api/xxx會(huì)轉(zhuǎn)發(fā)到后端的8081端口。注意你項(xiàng)目里所有接口前綴要統(tǒng)一比如都帶/api。后端Controller的RequestMapping第一個(gè)層級(jí)就是/api/xxx這樣代理規(guī)則才有效。這個(gè)細(xì)節(jié)很多人不寫前端聯(lián)調(diào)時(shí)接口404找半天。5. 統(tǒng)計(jì)報(bào)表與Excel導(dǎo)出的加分功能5.1 月度油耗、里程、費(fèi)用統(tǒng)計(jì)的SQL寫法車輛管理系統(tǒng)里統(tǒng)計(jì)功能是答辯時(shí)的加分項(xiàng)但也是很多人的薄弱點(diǎn)。其實(shí)核心就是幾個(gè)SQL聚合查詢。比如統(tǒng)計(jì)每輛車每月的總里程和油耗可以先建立加油記錄表每次加油記錄升數(shù)、金額、當(dāng)前里程。那某輛車一段時(shí)間內(nèi)的油耗率就可以算出來。我常用的統(tǒng)計(jì)接口是查出一張匯總表SELECT vehicle_id, DATE_FORMAT(record_date, %Y-%m) AS month, SUM(fuel_volume) AS total_fuel, SUM(fuel_cost) AS total_cost, SUM(mileage) AS total_mileage FROM fuel_record WHERE record_date BETWEEN #{startDate} AND #{endDate} GROUP BY vehicle_id, DATE_FORMAT(record_date, %Y-%m) ORDER BY month DESC然后在service里算平均油耗total_fuel除以total_mileage再乘100就是百公里油耗。這個(gè)數(shù)寫進(jìn)論文正文非常有說服力說明你真的理解了業(yè)務(wù)。前端展示用ECharts畫一個(gè)柱狀圖或者折線圖視覺沖擊力很強(qiáng)答辯演示的時(shí)候頁面上刷出一張油耗趨勢(shì)圖基本就穩(wěn)了。5.2 導(dǎo)出Excel工具類的封裝“導(dǎo)出Excel”這個(gè)功能看起來簡單但很多同學(xué)不會(huì)寫只會(huì)嘴上說“可以實(shí)現(xiàn)”。這里提供一條最簡單的路用Apache POI或者EasyExcel。EasyExcel是阿里開源的API簡單得多我建議直接用。思路是查到數(shù)據(jù)List然后用EasyExcel的write方法寫到HttpServletResponse的輸出流GetMapping(/export) public void exportExcel(HttpServletResponse response) throws IOException { ListVehicle list vehicleService.listAll(); response.setContentType(application/vnd.openxmlformats-officedocument.spreadsheetml.sheet); response.setCharacterEncoding(utf-8); String fileName URLEncoder.encode(車輛信息表, UTF-8); response.setHeader(Content-disposition, attachment;filename fileName .xlsx); EasyExcel.write(response.getOutputStream(), Vehicle.class).sheet(車輛信息).doWrite(list); }注意導(dǎo)出的實(shí)體類需要加ExcelProperty注解把實(shí)體字段對(duì)應(yīng)成中文列名。這個(gè)功能做完記得在論文里寫一句“本系統(tǒng)實(shí)現(xiàn)了數(shù)據(jù)導(dǎo)出功能便于管理人員線下存檔和進(jìn)一步分析”功能完整度立刻上一個(gè)臺(tái)階。5.3 ECharts圖表展示的實(shí)現(xiàn)思路前端引入ECharts做圖表其實(shí)不難。在Vue項(xiàng)目里安裝echarts然后在需要展示統(tǒng)計(jì)的頁面初始化一個(gè)DOM容器用setOption配置數(shù)據(jù)源和圖表類型。柱狀圖用來展示各部門用車頻次折線圖展示月度油費(fèi)趨勢(shì)餅圖展示車輛狀態(tài)分布。這三張圖放在首頁Dashboard上能替代一大段文字描述答辯時(shí)導(dǎo)師一眼就能看出你系統(tǒng)的數(shù)據(jù)可視化能力。6. 畢設(shè)答辯如何講清楚這套系統(tǒng)6.1 系統(tǒng)架構(gòu)與技術(shù)選型怎么描述答辯PPT里必定有一頁是系統(tǒng)架構(gòu)圖。我建議畫三層的結(jié)構(gòu)展示層用Vue和Element UI業(yè)務(wù)層用SpringBoot提供的RESTful接口數(shù)據(jù)層用MySQL配合MyBatis。上面再標(biāo)注一個(gè)JWT用于身份認(rèn)證。這張圖一放整個(gè)系統(tǒng)的全貌就出來了。技術(shù)選型部分不要只是羅列名詞要給出理由。比如問“為什么用SpringBoot”你不要只說“因?yàn)楹唵巍币v“SpringBoot內(nèi)置Tomcat簡化了配置過程同時(shí)繼承了Spring的依賴注入和事務(wù)管理能力適合快速開發(fā)中小型管理系統(tǒng)”。一問一答有來有回導(dǎo)師會(huì)覺得你是真懂。6.2 答辯必問的幾個(gè)高頻問題第一個(gè)必問系統(tǒng)的角色權(quán)限是怎么實(shí)現(xiàn)的標(biāo)準(zhǔn)回答思路用戶表里有role字段區(qū)分角色登錄后發(fā)放JWTJWT里包含角色信息后端攔截器解析請(qǐng)求后判斷角色是否有權(quán)限訪問當(dāng)前接口。第二個(gè)必問如果并發(fā)量上來了這個(gè)系統(tǒng)怎么優(yōu)化這個(gè)問題要小心不要一上來就說“加Redis加Nginx”。就畢設(shè)體量來講先答數(shù)據(jù)庫層面給常用查詢字段加索引、避免SELECT *、分頁優(yōu)化。再答代碼層面sql防注入、連接池配置。最后說如果真到生產(chǎn)環(huán)境可以考慮引入Redis緩存熱點(diǎn)數(shù)據(jù)和消息隊(duì)列削峰。層層遞進(jìn)比空談高深技術(shù)得分高。第三個(gè)必問你遇到的最大難點(diǎn)是什么怎么解決的這問題考察的是解決問題的能力不是代碼本身。你可以挑一個(gè)真實(shí)的坑比如跨域問題排查了一整天最后發(fā)現(xiàn)是前端口令中帶了token沒生效或者分頁失效是因?yàn)镻ageHelper和startPage之間插了其他操作。關(guān)鍵是要講出“排查思路”和“最終解決手段”。6.3 論文結(jié)構(gòu)建議和字?jǐn)?shù)分配我給你一個(gè)可以照著用的論文結(jié)構(gòu)建議。第一章緒論寫背景意義和國內(nèi)外現(xiàn)狀注意國內(nèi)外現(xiàn)狀寫的時(shí)候別空洞可以結(jié)合一些行業(yè)數(shù)據(jù)。第二章相關(guān)技術(shù)介紹寫SpringBoot、Vue、MySQL、MyBatis每節(jié)寫清楚“是什么為什么選它”。第三章系統(tǒng)分析寫可行性分析和需求分析畫用例圖和數(shù)據(jù)流圖。第四章系統(tǒng)設(shè)計(jì)寫總體架構(gòu)設(shè)計(jì)和數(shù)據(jù)庫表結(jié)構(gòu)設(shè)計(jì)。第五章系統(tǒng)實(shí)現(xiàn)按功能模塊逐個(gè)介紹配截圖和核心代碼。第六章系統(tǒng)測(cè)試寫功能性測(cè)試和性能測(cè)試附測(cè)試用例表格。關(guān)鍵點(diǎn)是論文截圖一定要真實(shí)所有頁面截圖和數(shù)據(jù)庫截圖都要與你系統(tǒng)的實(shí)際界面完全一致。很多同學(xué)論文里放的效果圖和實(shí)際系統(tǒng)對(duì)不上答辯老師一眼就看穿這種情況印象分直接就掉完了。7. 實(shí)際部署與常見排錯(cuò)實(shí)錄7.1 從源碼到本地跑通的完整路徑拿到任何一份車輛管理系統(tǒng)的源碼我最推薦的運(yùn)行順序是先看SQL腳本導(dǎo)入數(shù)據(jù)庫再啟動(dòng)后端服務(wù)最后啟動(dòng)前端。不要反著來。后端啟動(dòng)之前確認(rèn)application.yml里的數(shù)據(jù)庫地址、端口、用戶名密碼都改了。前端啟動(dòng)之前執(zhí)行npm install如果嫌慢可以配置一下鏡像源這是常規(guī)操作。后端啟動(dòng)成功后訪問Swagger或者直接訪問一個(gè)GET接口確認(rèn)返回JSON。前端啟動(dòng)后訪問首頁如果頁面能打開但接口報(bào)錯(cuò)優(yōu)先看瀏覽器F12的Network面板確認(rèn)請(qǐng)求是不是被代理轉(zhuǎn)發(fā)到了正確的后端地址。90%的聯(lián)調(diào)問題都能在這個(gè)面板里定位到。7.2 數(shù)據(jù)庫連接失敗的排查步驟數(shù)據(jù)庫連接失敗這個(gè)問題高發(fā)報(bào)錯(cuò)大概長這樣Access denied for user rootlocalhost。先別急按順序排查三層第一MySQL服務(wù)有沒有啟動(dòng)Windows下按CtrlShiftEsc到服務(wù)里看第二用戶名密碼是不是對(duì)的拿Navicat直接連一下試試第三賬號(hào)允許訪問的主機(jī)范圍是不是localhost。第三步是重災(zāi)區(qū)很多人的MySQL的root賬號(hào)不允許遠(yuǎn)程訪問要用就得在mysql庫的user表里改host字段和權(quán)限或者創(chuàng)建一個(gè)專用賬號(hào)。7.3 前后端接口返回值對(duì)不上的處理方式聯(lián)調(diào)時(shí)會(huì)遇到這種情況前端表格全是空的但瀏覽器的Network里明明看到后端返回了數(shù)據(jù)。排查思路先看Response是不是被瀏覽器攔截了CORS錯(cuò)誤會(huì)在Console里報(bào)再看數(shù)據(jù)格式是不是嵌套了一層比如后端返回的是{ code: 200, data: {...} }你前端卻直接拿res.data當(dāng)成列表用拿到的其實(shí)是整個(gè)Result對(duì)象自然渲染不出來。還有一種情況是后端的日期字段返回格式是時(shí)間戳前端直接顯示一串?dāng)?shù)字。解決辦法是在后端實(shí)體字段上加JsonFormat(pattern yyyy-MM-dd HH:mm:ss)注解或者在Jackson配置里統(tǒng)一指定時(shí)間格式。這種細(xì)節(jié)不處理答辯演示時(shí)表格里出現(xiàn)一串?dāng)?shù)字觀感非常差。7.4 前端Node環(huán)境相關(guān)的坑npm install能不能順利跑完取決于Node版本和依賴版本匹不匹配。有些老項(xiàng)目依賴的是webpack 4配Node 18以上會(huì)報(bào)錯(cuò)。如果你拿到的源碼用的是Vue CLI 4建議裝Node 14或者16的LTS版本。這里也提醒一句Vue2和Vue3的項(xiàng)目結(jié)構(gòu)有一定差別拿到源碼先看package.json里的vue版本再?zèng)Q定自己的操作。依賴裝好后npm run serve就可以啟動(dòng)了。如果報(bào)端口被占用改vue.config.js里的port即可。如果報(bào)了ESLint格式錯(cuò)誤導(dǎo)致編譯失敗可以在vue.config.js里把lintOnSave設(shè)為false先把項(xiàng)目跑起來再說。8. 寫在最后的一點(diǎn)建議我做這套系統(tǒng)的最大感受是別把畢設(shè)當(dāng)成最后一個(gè)月要趕的作業(yè)當(dāng)成一個(gè)真正要上線的小產(chǎn)品來做。當(dāng)你把“能用”的代碼改成“好用”的代碼——表格加了搜索、數(shù)據(jù)加了校驗(yàn)、異常給了提示、頁面加了圖表——那個(gè)過程學(xué)到的知識(shí)密度遠(yuǎn)大于你把視頻課刷一遍。如果你現(xiàn)在手里正準(zhǔn)備啟動(dòng)一個(gè)車輛管理系統(tǒng)我建議第一周什么都不寫先把表和接口設(shè)計(jì)想明白。我是吃過這個(gè)虧的第一次做的時(shí)候數(shù)據(jù)庫表建得隨意到了第三周要加統(tǒng)計(jì)功能發(fā)現(xiàn)表結(jié)構(gòu)根本查不出數(shù)據(jù)硬著頭皮重建了四張表那周基本沒睡好。最后分享一個(gè)小技巧寫完一個(gè)模塊就自己模擬用戶走一遍完整流程注冊(cè)、登錄、申請(qǐng)出車、審批、還車、查看報(bào)表。你親手走過一遍完整的業(yè)務(wù)閉環(huán)答辯時(shí)講出來的通暢程度和只背代碼完全不一樣。你會(huì)發(fā)現(xiàn)導(dǎo)師問的很多問題都在這個(gè)閉環(huán)里有了答案。