:前后端分離畢業(yè)設(shè)計完整解析)
簡介基于SpringBoot與Vue的超市管理系統(tǒng)是一套面向畢業(yè)設(shè)計場景的全棧實戰(zhàn)資源適合需要完成前后端分離項目、理解商品庫存與銷售業(yè)務(wù)數(shù)據(jù)化管理的開發(fā)者。RAR壓縮包共2000個文件以1614個JS腳本、233個CSS樣式為主體另含61個Java后端類、43個Vue組件、1個SQL初始化腳本以及XML/JSON等配置整體23.17MB服務(wù)端、前端頁面與數(shù)據(jù)庫初始化內(nèi)容一目了然。系統(tǒng)覆蓋用戶權(quán)限、庫存監(jiān)控、銷售事務(wù)、報表統(tǒng)計、系統(tǒng)設(shè)置等模塊SQL文件可快速創(chuàng)建表結(jié)構(gòu)并寫入初始數(shù)據(jù)SpringBoot應(yīng)用與Vue前端打包后便于本地部署和二次開發(fā)。整個項目目錄結(jié)構(gòu)清晰前后端聯(lián)動邏輯完整適合用于課程設(shè)計、畢業(yè)設(shè)計參考也可作為學(xué)習(xí)前后端分離架構(gòu)和超市業(yè)務(wù)實現(xiàn)的綜合案例目前已有87人瀏覽學(xué)習(xí)。1. 這是什么樣的超市管理系統(tǒng)覆蓋前端到后端的完整畢業(yè)設(shè)計模板如果你正在找一套能直接跑起來、又能在論文里寫出完整業(yè)務(wù)鏈路的畢業(yè)設(shè)計這套基于SpringBoot Vue的超市管理系統(tǒng)值得花一個下午拆開看看。它不是那種只差個前端頁面的半成品而是把超市日常運營里的商品管理、分類管理、供應(yīng)商管理、進(jìn)貨入庫、銷售收銀、庫存預(yù)警、銷售統(tǒng)計這些環(huán)節(jié)都做成了可操作的功能模塊后端由SpringBoot提供RESTful接口前端由Vue框架配合Element UI組件庫渲染頁面數(shù)據(jù)存儲在MySQL里。換句話說從收銀臺點一下「結(jié)算」到庫存表里扣減對應(yīng)商品數(shù)量再到老板首頁看到今日銷售額這條完整鏈路在這份代碼里是閉環(huán)的。我從拿到源碼到把它跑通、看懂核心邏輯前后花了大約一個晚上。過程中確實踩了幾個坑比如Node版本和依賴版本的匹配問題、數(shù)據(jù)庫編碼導(dǎo)致的中文亂碼這些問題在項目文檔里不一定寫全但都在可以修復(fù)的范圍內(nèi)。對準(zhǔn)備做畢業(yè)設(shè)計的同學(xué)來說這套系統(tǒng)的價值在于它足夠真實——業(yè)務(wù)表設(shè)計覆蓋了超市管理的核心實體代碼分層也規(guī)整Controller、Service、Mapper各司其職前端頁面不是簡單的組件堆砌有路由、有狀態(tài)管理、有接口封裝。下面我就從目錄結(jié)構(gòu)開始把每塊的作用、怎么跑通、改哪里容易翻車逐個講清楚。2. 技術(shù)棧解析與整體架構(gòu)為什么這套組合適合當(dāng)畢設(shè)底子2.1 SpringBoot Vue前后端分離的結(jié)構(gòu)邏輯超市管理系統(tǒng)這個題目本身不算稀缺但做到前后端分離、接口語義清晰、表結(jié)構(gòu)合理的版本并不常見。這套項目采用的標(biāo)準(zhǔn)前后端分離架構(gòu)前端運行在Node環(huán)境中開發(fā)模式下通過Vite或Webpack提供熱更新服務(wù)后端由SpringBoot內(nèi)置的Tomcat啟動可獨立打包運行。兩端之間通過JSON格式進(jìn)行數(shù)據(jù)交互前端的每一個頁面操作最終都會映射成一次RESTful API調(diào)用。我比較欣賞的一點是后端沒有把所有代碼塞進(jìn)Controller里。項目里能看到清晰的Controller層接收請求、返回結(jié)果、Service層業(yè)務(wù)邏輯處理、Mapper層數(shù)據(jù)庫操作劃分每一層職責(zé)單一。這樣的結(jié)構(gòu)對論文的架構(gòu)設(shè)計章節(jié)非常友好畫分層架構(gòu)圖、寫接口設(shè)計說明都容易展開。前端部分的目錄組織也遵循了常規(guī)工程化規(guī)范。src/api下按業(yè)務(wù)模塊封裝了axios請求方法src/router里定義了頁面路由src/views下按頁面維度組織Vue組件src/store里放Vuex狀態(tài)管理。對于一個管理后臺來說這樣的劃分足夠支撐后續(xù)往里面加新模塊比如加一個會員管理頁面只需要在對應(yīng)目錄下新增文件再掛上路由即可。2.2 數(shù)據(jù)庫設(shè)計思路與核心表結(jié)構(gòu)超市管理系統(tǒng)的基礎(chǔ)是一張合理的關(guān)系型數(shù)據(jù)庫表結(jié)構(gòu)。這套項目的表設(shè)計屬于典型的進(jìn)銷存體系核心表之間通過外鍵關(guān)聯(lián)基本覆蓋了一個小型超市的信息化需求。主要的數(shù)據(jù)表包括管理員表、商品分類表、商品信息表、供應(yīng)商表、進(jìn)貨單表、進(jìn)貨單明細(xì)表、銷售單表、銷售單明細(xì)表。其中商品信息表與分類表通過分類ID關(guān)聯(lián)進(jìn)貨單和銷售單分別拆成主表和明細(xì)表這樣設(shè)計能保證一張單據(jù)上記錄多條商品數(shù)據(jù)時表結(jié)構(gòu)不會出現(xiàn)冗余也方便后續(xù)做銷售統(tǒng)計時直接用明細(xì)表的數(shù)據(jù)匯總。以商品信息表為例它的字段設(shè)計除了常規(guī)的商品編碼、名稱、條碼、售價、成本價、庫存數(shù)量之外有一個字段容易被忽略——預(yù)警庫存值。當(dāng)商品當(dāng)前庫存低于這個閾值時系統(tǒng)會在庫存頁面觸發(fā)預(yù)警提示。這個設(shè)計雖然簡單卻是超市管理系統(tǒng)區(qū)別于普通CRUD練習(xí)的關(guān)鍵點也是論文里可以重點描述的亮點功能。3. 項目目錄結(jié)構(gòu)與代碼分層從啟動入口到接口響應(yīng)的完整鏈路3.1 后端工程目錄逐層拆解拿到源碼包后先看后端目錄的結(jié)構(gòu)。整體是一個標(biāo)準(zhǔn)的Maven工程包的根路徑下按照職責(zé)劃分了不同的子包。controller存放所有RESTful接口的入口類負(fù)責(zé)接收前端請求參數(shù)并返回統(tǒng)一格式的響應(yīng)結(jié)果。service存放業(yè)務(wù)邏輯接口定義以及對應(yīng)的實現(xiàn)類。mapper存放MyBatis Plus的Mapper接口繼承BaseMapper后即可獲得單表的增刪改查能力。entity存放數(shù)據(jù)庫表對應(yīng)的實體類字段與表結(jié)構(gòu)一一映射。config存放配置類包括跨域配置、MybatisPlus分頁插件配置等。common存放通用工具類、統(tǒng)一響應(yīng)結(jié)果封裝類、異常處理類。common/R類是后端返回給前端的數(shù)據(jù)統(tǒng)一格式封裝。無論接口執(zhí)行成功還是失敗前端拿到的數(shù)據(jù)結(jié)構(gòu)都是固定的code表示狀態(tài)碼msg是提示信息data是具體業(yè)務(wù)數(shù)據(jù)。這樣前端在axios攔截器里只需要判斷一次code就能統(tǒng)一處理錯誤提示和權(quán)限跳轉(zhuǎn)。3.2 前端工程目錄與頁面模塊對應(yīng)關(guān)系前端部分打開根目錄后src下的組織方式如下src/api目錄把每個業(yè)務(wù)模塊的HTTP請求封裝成獨立文件例如product.js里包含商品列表查詢、商品新增、商品修改、商品刪除等接口方法src/views下的目錄結(jié)構(gòu)與后端菜單一一對應(yīng)包含登錄頁面、系統(tǒng)首頁面、商品管理頁、分類管理頁、進(jìn)貨管理頁、銷售管理頁、統(tǒng)計報表頁等src/router/index.js定義了所有路由信息路由守衛(wèi)里做了登錄狀態(tài)校驗未登錄用戶訪問頁面時會自動跳轉(zhuǎn)到登錄頁。頁面與后端接口之間通過API文件做了一層隔離。Vue組件里不直接寫axios調(diào)用而是import對應(yīng)的API方法這樣做的好處是接口路徑如果發(fā)生變化只需要改動API文件頁面層不用跟著動。4. 核心功能模塊拆解從商品上架到庫存扣減的完整業(yè)務(wù)實現(xiàn)4.1 登錄鑒權(quán)模塊系統(tǒng)登錄走的是JWT鑒權(quán)方案。用戶輸入用戶名密碼后后端驗證通過返回一個簽發(fā)的Token前端把Token存進(jìn)localStorage后續(xù)每次請求都在請求頭攜帶這個Token。后端通過攔截器對需要鑒權(quán)的接口進(jìn)行校驗Token過期或非法時統(tǒng)一返回401狀態(tài)碼。// 登錄Controller的關(guān)鍵邏輯 PostMapping(/login) public R login(RequestBody LoginDTO loginDTO) { // 1. 根據(jù)用戶名查詢用戶信息 User user userService.lambdaQuery() .eq(User::getUsername, loginDTO.getUsername()) .one(); if (user null) { return R.error(用戶名或密碼錯誤); } // 2. 密碼需要MD5加密后比對數(shù)據(jù)庫中存儲的是加密值 String encryptedPwd SecureUtil.md5(loginDTO.getPassword()); if (!user.getPassword().equals(encryptedPwd)) { return R.error(用戶名或密碼錯誤); } // 3. 簽發(fā)JWT設(shè)置過期時間為24小時 String token JWT.create() .setClaim(userId, user.getId()) .setClaim(username, user.getUsername()) .setExpiresAt(new Date(System.currentTimeMillis() 86400000)) .sign(secretKey); return R.ok().data(token, token); }這段代碼里有一個需要實際項目注意的地方庫存量扣減不能只做數(shù)值減法還要帶上庫存充足性校驗。當(dāng)并發(fā)請求同時扣減同一個商品庫存時如果只做UPDATE不做條件約束容易出現(xiàn)超賣。但在這個畢業(yè)設(shè)計級別的代碼里通常做法是先查詢庫存判斷是否充足再執(zhí)行扣減更新。對于超市單機(jī)收銀的場景夠用但如果要在論文里談并發(fā)優(yōu)化可以提一下樂觀鎖方案給商品表增加version字段在更新語句里加上where version 舊版本號。// 銷售單提交的事務(wù)處理核心邏輯 Transactional(rollbackFor Exception.class) public R submitSaleOrder(SaleOrderDTO saleOrderDTO) { // 1. 創(chuàng)建銷售單主表記錄 SaleOrder saleOrder new SaleOrder(); saleOrder.setOrderNo(generateOrderNo()); // 生成唯一單號如日期隨機(jī)數(shù) saleOrder.setTotalAmount(saleOrderDTO.getTotalAmount()); saleOrder.setUserId(saleOrderDTO.getUserId()); saleOrder.setCreateTime(new Date()); saleOrderMapper.insert(saleOrder); // 2. 遍歷商品明細(xì)逐條寫入明細(xì)表并扣減庫存 ListSaleOrderItemDTO items saleOrderDTO.getItems(); for (SaleOrderItemDTO item : items) { // 扣庫存條件帶stock 0防止扣成負(fù)數(shù) int updateRows productMapper.deductStock( item.getProductId(), item.getQuantity()); if (updateRows 0) { throw new RuntimeException(商品【 item.getProductName() 】庫存不足); } // 寫入銷售明細(xì) SaleOrderItem saleOrderItem new SaleOrderItem(); saleOrderItem.setOrderId(saleOrder.getId()); saleOrderItem.setProductId(item.getProductId()); saleOrderItem.setPrice(item.getPrice()); saleOrderItem.setQuantity(item.getQuantity()); saleOrderItemMapper.insert(saleOrderItem); } // 3. 清除購物車中已結(jié)算的商品 cartService.removeItems(saleOrderDTO.getCartIds()); return R.ok().data(orderId, saleOrder.getId()); }這段代碼里使用了Spring事務(wù)注解意思是只要循環(huán)中任意一個商品的庫存扣減失敗整個銷售單提交會回滾不會出現(xiàn)訂單保存了但庫存沒扣的臟數(shù)據(jù)。事務(wù)在寫多表操作時非常關(guān)鍵假如沒有加事務(wù)訂單主表可能已經(jīng)插入成功但明細(xì)寫入失敗就會造成金額對不上賬的情況。實際排查數(shù)據(jù)不一致問題時第一件事就是檢查涉及多表寫入的業(yè)務(wù)方法上有沒有事務(wù)控制。這里的方法使用了MyBatis Plus提供的lambdaQuery寫法是一種不用手寫SQL的鏈?zhǔn)讲樵兎绞讲閱伪頃r比較方便。4.2 商品管理模塊含分類、供應(yīng)商、庫存預(yù)警商品管理是這套系統(tǒng)里表關(guān)系最復(fù)雜的模塊因為商品本身除了基本信息還關(guān)聯(lián)了分類和供應(yīng)商兩個維度。分類表是一棵樹形結(jié)構(gòu)支持一級分類和二級分類前端頁面對應(yīng)分類的級聯(lián)選擇器。供應(yīng)商表獨立維護(hù)商品新增時通過下拉選擇供應(yīng)商從而知道這批貨的來源。-- 商品信息表核心字段設(shè)計簡化版 CREATE TABLE product ( id BIGINT PRIMARY KEY AUTO_INCREMENT COMMENT 主鍵ID, product_code VARCHAR(50) NOT NULL COMMENT 商品編碼, product_name VARCHAR(100) NOT NULL COMMENT 商品名稱, barcode VARCHAR(50) DEFAULT NULL COMMENT 條形碼, category_id BIGINT NOT NULL COMMENT 分類ID, supplier_id BIGINT NOT NULL COMMENT 供應(yīng)商ID, purchase_price DECIMAL(10,2) NOT NULL COMMENT 進(jìn)貨成本價, sale_price DECIMAL(10,2) NOT NULL COMMENT 零售價, stock_quantity INT NOT NULL DEFAULT 0 COMMENT 當(dāng)前庫存量, warning_stock INT NOT NULL DEFAULT 10 COMMENT 庫存預(yù)警閾值, status TINYINT NOT NULL DEFAULT 1 COMMENT 狀態(tài) 1上架 0下架, create_time DATETIME NOT NULL COMMENT 創(chuàng)建時間, update_time DATETIME NOT NULL COMMENT 更新時間 ) COMMENT 商品信息表;字段warning_stock就是庫存預(yù)警依賴的字段。商品列表頁查詢時后端只要執(zhí)行一個條件查詢把stock_quantity小于等于warning_stock的記錄挑出來前端就能在頁面上標(biāo)紅顯示預(yù)警狀態(tài)。入庫操作中條件更新庫存是一個實踐細(xì)節(jié)語句里where條件帶上stock #{quantity}這樣即使并發(fā)情況下也不可能把庫存扣成負(fù)數(shù)。4.3 前端頁面與后端聯(lián)調(diào)的關(guān)鍵代碼前端頁面的核心交互邏輯集中在收銀臺頁面和商品管理頁面。收銀臺頁面里用戶輸入商品條碼前端請求后端商品查詢接口拿到商品信息后加入購物車列表最后一次性提交生成銷售單。整個流程對應(yīng)后端的就是上面那個submitSaleOrder事務(wù)方法。// Vue收銀臺頁面核心方法 export default { data() { return { cartList: [], // 當(dāng)前購物車商品 barcode: , // 掃碼槍輸入的商品條碼 totalAmount: 0 // 總金額 }; }, methods: { // 根據(jù)條碼查找商品 async searchProduct() { const res await productApi.getByBarcode(this.barcode); if (res.code 200) { // 加入購物車若已存在則數(shù)量1 const found this.cartList.find(item item.id res.data.id); if (found) { found.quantity 1; } else { this.cartList.push({ ...res.data, quantity: 1 }); } this.recalcTotal(); } else { this.$message.error(未找到該商品); } this.barcode ; }, // 提交結(jié)算 async submitOrder() { if (this.cartList.length 0) return; const orderData { userId: this.$store.state.userId, items: this.cartList.map(item ({ productId: item.id, productName: item.productName, price: item.salePrice, quantity: item.quantity })), totalAmount: this.totalAmount }; const res await orderApi.submit(orderData); if (res.code 200) { this.$message.success(結(jié)算成功); this.cartList []; this.totalAmount 0; } else { this.$message.error(res.msg); } } } };這段前端邏輯里購物車是用數(shù)組加find方法實現(xiàn)的適合小批量商品條目。實際超市系統(tǒng)購物車商品種類可能較多如果追求性能可以用Map結(jié)構(gòu)按商品ID組織條目數(shù)量不過對于日常幾十種商品的結(jié)算場景數(shù)組的線性遍歷成本可以忽略。條碼部分監(jiān)聽了一個onkeydown事件掃碼槍其實是模擬鍵盤快速輸入數(shù)字后回車所以這里的searchProduct方法會在回車鍵按下時觸發(fā)。5. 部署運行避坑指南端口沖突、跨域、Maven依賴下載失敗等5.1 后端啟動失敗報端口被占用現(xiàn)象啟動SpringBoot時提示Port 8080 was already in use或者點擊啟動后控制臺沒有輸出就直接退出。原因本機(jī)其他程序占用了8080端口。常見的是之前開過的后端服務(wù)沒有關(guān)閉或者IDEA里殘留了其他進(jìn)程。我遇到過一種情況是電腦上運行的數(shù)據(jù)庫管理工具自帶Web服務(wù)占了端口排查時只看了Java進(jìn)程沒看其他進(jìn)程。解決先執(zhí)行netstat -ano | findstr 8080查看占用端口的進(jìn)程PID然后在任務(wù)管理器里找到對應(yīng)PID的進(jìn)程結(jié)束掉。如果這個端口經(jīng)常被占直接在application.yml里把server.port改成8081或8888前端API配置文件里的baseURL同步修改即可。5.2 前端登錄接口報跨域錯誤現(xiàn)象瀏覽器控制臺出現(xiàn)Access-Control-Allow-Origin相關(guān)報錯請求發(fā)出去了但拿不到響應(yīng)。原因前端開發(fā)服務(wù)器跑在5173端口Vite默認(rèn)或8080端口Vue CLI默認(rèn)后端接口在8888端口不滿足同源策略瀏覽器攔截了跨域請求。雖然前端項目里配置了代理但如果代理配置沒有生效或者后端沒有開啟跨域允許就會觸發(fā)這個問題。解決推薦用后端方式解決在后端添加一個CorsConfig配置類允許指定來源的請求跨域訪問。前端本地開發(fā)時也可以在Vite配置文件里設(shè)置proxy代理把/api開頭的請求轉(zhuǎn)發(fā)到后端端口這樣瀏覽器看到的請求是同源的就不會報跨域錯誤。需要注意代理修改后必須重啟前端開發(fā)服務(wù)器熱更新不一定能自動加載代理配置。5.3 Maven依賴下載超時或失敗現(xiàn)象項目導(dǎo)入IDE后pom.xml里大量依賴標(biāo)紅或者下載依賴時報Could not resolve dependencies錯誤。原因國內(nèi)網(wǎng)絡(luò)訪問Maven中央倉庫速度不穩(wěn)定尤其是下載SpringBoot相關(guān)的大體積依賴時容易超時中斷。解決在Maven的settings.xml里配置阿里云鏡像源中央倉庫的下載URI改為maven.aliyun.com/repository/central。配置好之后回到IDE刷新項目依賴會重新解析并下載。如果個別依賴反復(fù)下載失敗可以查看本地倉庫目錄把對應(yīng)依賴的.lastUpdated后綴文件刪掉再重新刷新避免Maven因為存在殘留失敗記錄而不重新下載。5.4 MySQL插入中文亂碼現(xiàn)象后端啟動正常但在頁面上新增商品時輸入中文名稱頁面回顯數(shù)據(jù)時中文變成了問號或亂碼字符串。原因數(shù)據(jù)庫表默認(rèn)字符集不是utf8mb4或數(shù)據(jù)庫連接參數(shù)里沒有指定字符編碼。查看具體原因最直接方式是命令行登錄MySQL執(zhí)行show create table product;看表的charset字段是否是utf8mb4。解決創(chuàng)建數(shù)據(jù)庫時指定字符集執(zhí)行CREATE DATABASE supermarket DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci;。同時在SpringBoot的數(shù)據(jù)庫連接配置中追加參數(shù)useUnicodetruecharacterEncodingutf8serverTimezoneAsia/Shanghai。注意如果原表已經(jīng)創(chuàng)建且存在亂碼數(shù)據(jù)修改字符集不會自動修復(fù)既有數(shù)據(jù)需要刪除重插測試數(shù)據(jù)或清空表再重新錄入。5.5 修改數(shù)據(jù)庫但前端頁面不生效現(xiàn)象手動在Navicat里改了商品名稱刷新前端頁面后數(shù)據(jù)沒有變化。原因前端頁面默認(rèn)加載的路由可能走了緩存頁面或者接口查詢帶了緩存。解決先打開瀏覽器開發(fā)者工具切到Network標(biāo)簽頁點一下刷新看商品列表請求是否正常返回了新數(shù)據(jù)。如果返回了但頁面沒更新則問題在前端響應(yīng)數(shù)據(jù)回顯檢查Vue的data屬性名和后端返回字段是否大小寫不一致。很多時候前端頁面不更新是后端接口查詢條件帶了隱藏參數(shù)如當(dāng)前頁碼、搜索關(guān)鍵字手動改的數(shù)據(jù)不在當(dāng)前查詢結(jié)果頁里。6. 二次開發(fā)方向如何把這套系統(tǒng)改造成更適合自己的畢設(shè)項目拿到這套超市管理系統(tǒng)只是第一步真正讓它在答辯時有差異化的是你能在自己的場景下做改造。我建議優(yōu)先做三個方向的擴(kuò)展性價比比較高也容易在論文里講出深度。建一個促銷模塊。業(yè)務(wù)場景是超市會做「滿100減20」「第2件半價」這類活動這套系統(tǒng)目前沒有完整的促銷規(guī)則引擎。做法是在數(shù)據(jù)庫里新建promotion表字段含促銷類型、滿減閾值、折扣率、開始結(jié)束時間再在結(jié)算時校驗當(dāng)前購物車是否滿足促銷條件。結(jié)算訂單時后端增加一個活動匹配方法根據(jù)購物車總金額找出當(dāng)前生效的促銷規(guī)則計算優(yōu)惠金額后才生成最終銷售單。數(shù)據(jù)統(tǒng)計方面可以增加一個按日銷售額趨勢的后端接口。使用GROUP BY對創(chuàng)建時間按天分組統(tǒng)計每天的總銷售額和訂單數(shù)前端用一個折線圖組件渲染做成老板首頁的核心展示區(qū)塊。我的習(xí)慣是先把SQL在Navicat里跑通確認(rèn)數(shù)據(jù)正確后再往后端代碼里搬這樣排錯更快。頁面交互方面收銀臺可以支持快捷按鍵。給商品表格加上鍵盤上下鍵選擇、回車鍵直接加入購物車。這個體量不需要改動后端只動前端組件事件綁定。我個人強烈建議無論怎么改都先把項目原始版本完整跑通一遍提交一份原始代碼進(jìn)Git倉庫再開分支做二次開發(fā)。因為改壞了隨時能回退這個習(xí)慣我在后來好幾個項目里都靠它救了命——有一次改促銷規(guī)則導(dǎo)致所有訂單提交報錯直接切回原分支跑了一遍確認(rèn)是促銷模塊的數(shù)據(jù)庫字段寫錯了很快就定位了問題。從那以后我每次二次開發(fā)都強制走一遍「原始跑通再開分支」的流程。這套項目管理系統(tǒng)的結(jié)構(gòu)并不復(fù)雜但它完整呈現(xiàn)了畢業(yè)設(shè)計需要的要素需求邊界清晰、技術(shù)棧主流、業(yè)務(wù)覆蓋有深度。如果你能跑通之后再動動手把上面的建議方向?qū)崿F(xiàn)一兩個答辯素材就齊了希望幫到你。本文還有配套的精品資源點擊獲取