:從MySQL數(shù)據(jù)庫到前后端分離部署)
之前整理過一個SpringBootVue的無人智慧超市項目被好幾個準備做畢設的同學追著問干脆把完整的實現(xiàn)思路和坑點都寫出來。這套系統(tǒng)后端是SpringBootMyBatis-Plus前端是VueElement UI數(shù)據(jù)庫用MySQL邏輯并不復雜但覆蓋了一個小型管理系統(tǒng)的完整閉環(huán)商品管理、會員管理、自助購物車、訂單結算、門禁模擬、數(shù)據(jù)統(tǒng)計拿去應付本科畢設或者課程設計完全夠用更重要的是每個模塊都能講清楚為什么這么做。如果你正處在選型階段最關心的無非是三件事好不好跑起來、代碼量大不大、答辯能不能講明白。先說結論這套項目屬于中等體量后端大概十幾個核心接口前端七八個頁面一個人一周左右能啃完而且因為業(yè)務貼近日常生活功能解釋起來非常直觀。1. 項目整體定位與設計思路1.1 這個系統(tǒng)到底解決了什么問題無人智慧超市核心就是把傳統(tǒng)超市的人工收銀、人工盤貨、人工門禁這些環(huán)節(jié)自動化。放在畢設場景里不需要你真的接入人臉識別硬件或重力感應貨架而是用軟件模擬這套流程用戶掃碼或注冊會員后進店選擇商品加入虛擬購物車結算時系統(tǒng)自動計算金額并扣除賬戶余額或模擬支付出門時校驗訂單狀態(tài)。管理端則負責商品上下架、庫存調整、訂單查詢、會員管理、銷售統(tǒng)計。這個業(yè)務模型的價值在于它天然包含了一個管理信息系統(tǒng)該有的所有要素數(shù)據(jù)建模、前后端交互、權限控制、狀態(tài)流轉、統(tǒng)計報表。老師問你這個系統(tǒng)做了什么你可以順著用戶一條完整的購物鏈路講下來老師問你用了什么技術前后端分離、RESTful接口、JWT鑒權、MyBatis-Plus條件構造器都能排得上用場。1.2 為什么選SpringBootVue這套組合而不是其他方案每年畢設選型都會被問這個問題我的建議很直接如果你是Java方向這就是最穩(wěn)的組合。SpringBoot把Spring的配置簡化到了極致內嵌Tomcat打成一個Jar包就能跑不需要折騰Tomcat部署。MyBatis-Plus又在MyBatis基礎上把單表CRUD寫成了現(xiàn)成的方法省掉大量重復的Mapper XML。前端Vue配合Element UI表格、表單、彈窗、分頁這些后臺管理頁面最常見的組件都有現(xiàn)成的不需要自己手搓UI。對比一下其他方案你就明白了如果用JSPServlet代碼寫得再規(guī)范答辯時也容易被質疑技術陳舊如果后端用Python的Django又和你Java課程的主線不一致面試時不好解釋。SpringBootVue是當前企業(yè)里中小型管理系統(tǒng)的主流搭檔學完寫在簡歷上不虧。還有一個實際原因網(wǎng)上這套組合的參考資料最多遇到報錯基本都能搜到解決方案。我見過太多同學選了冷門框架最后卡在一個莫名其妙的依賴沖突上連問都沒處問。選熱門技術棧本身就是降低風險的手段。1.3 無人超市的完整業(yè)務流程梳理把業(yè)務流程理清楚后面建模就順了。一個用戶從進店到離店大致經(jīng)歷這樣幾步進店用戶掃碼或者輸入會員手機號系統(tǒng)校驗身份生成一個會話記錄標記在店狀態(tài)。選購用戶瀏覽商品列表把商品加入購物車。這里不需要真實掃碼槍前端頁面加個按鈕就行。結算用戶提交購物車后端計算總金額調用模擬支付接口扣減余額生成訂單扣減庫存。離店系統(tǒng)根據(jù)訂單狀態(tài)更新會話記錄為已離店后端返回一個放行標識前端展示模擬門禁放行動畫。管理端的流程就簡單了管理員登錄維護商品分類和商品信息處理庫存預警查看訂單列表和銷售額統(tǒng)計。整個系統(tǒng)就是這兩條主線的交織數(shù)據(jù)庫表也是圍繞這兩條線設計的。2. 技術棧與項目結構2.1 后端分層結構與Maven依賴后端我用標準的四層結構Controller接收前端請求Service寫業(yè)務邏輯Mapper操作數(shù)據(jù)庫Entity映射表結構。有人覺得分層麻煩但畢設答辯時這就是加分項你可以明確告訴老師我遵循了單一職責原則。pom.xml里的核心依賴就這么幾個dependency groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-web/artifactId /dependency dependency groupIdcom.baomidou/groupId artifactIdmybatis-plus-boot-starter/artifactId version3.5.5/version /dependency dependency groupIdmysql/groupId artifactIdmysql-connector-java/artifactId version8.0.33/version /dependency dependency groupIdio.jsonwebtoken/groupId artifactIdjjwt-api/artifactId version0.11.5/version /dependency dependency groupIdcn.hutool/groupId artifactIdhutool-all/artifactId version5.8.25/version /dependencyHutool是個小工具庫生成隨機訂單號、日期處理、加密都用得上。依賴別一股腦全加夠用就好加多了版本沖突反而麻煩。JWT用來做登錄鑒權比傳統(tǒng)的Session更適合前后端分離的場景。2.2 SpringBoot版本與JDK版本怎么選這里需要重點提醒SpringBoot版本不是越高越好。我一開始用了SpringBoot 3.2.x結果它要求JDK 17而且部分老版本的MyBatis-Plus、Druid和它有兼容問題。如果你只是做畢設沒必要追新推薦用SpringBoot 2.7.18配合JDK 8這個組合經(jīng)過大量項目驗證網(wǎng)上報錯解決方案也最全。# application.yml 核心配置 server: port: 8080 spring: datasource: driver-class-name: com.mysql.cj.jdbc.Driver url: jdbc:mysql://localhost:3306/smart_mart?useUnicodetruecharacterEncodingutf8serverTimezoneAsia/Shanghai username: root password: 123456 jackson: date-format: yyyy-MM-dd HH:mm:ss time-zone: Asia/Shanghai mybatis-plus: configuration: log-impl: org.apache.ibatis.logging.stdout.StdOutImpl global-config: db-config: logic-delete-field: deleted logic-delete-value: 1 logic-not-delete-value: 0注意url里的serverTimezoneAsia/Shanghai不加上MySQL 8連接時會報時區(qū)錯誤。log-impl配置成StdOutImpl可以在控制臺打印SQL排查問題時非常有用答辯演示時也方便展示。我見過不少同學卡在這些基礎配置上其實都是經(jīng)驗問題。2.3 前端Vue項目的搭建方式前端我用的是Vue 2不是Vue 3。不是說Vue 3不好而是Element UI對Vue 2的支持最成熟網(wǎng)上的示例代碼90%都是Vue 2寫法照著抄不容易翻車。如果你非要用Vue 3那配合Element Plus也行但很多細節(jié)寫法有差異調試成本會高一些。創(chuàng)建項目用Vue CLInpm install -g vue/cli vue create smart-mart-web # 選擇 Manually select features勾選 Router cd smart-mart-web npm install element-ui axios安裝依賴的過程經(jīng)常出問題尤其是網(wǎng)絡不穩(wěn)定的時候node_modules裝一半失敗了。國內環(huán)境建議先設置npm鏡像源再安裝npm config set registry https://registry.npmmirror.com裝完依賴后在main.js里注冊Element UI和Router然后按頁面拆組件。我的頁面結構是登錄頁、用戶端首頁、商品列表頁、購物車頁、訂單確認頁、管理端布局、商品管理頁、訂單管理頁、會員管理頁、統(tǒng)計頁。每個頁面一個文件夾組件按需拆分。3. 數(shù)據(jù)庫設計與核心功能模塊3.1 數(shù)據(jù)庫表設計五張表串起整個系統(tǒng)數(shù)據(jù)庫是這套項目的地基表設計得好后面的代碼能少寫一半。我設計了五張核心表用戶表、商品表、訂單表、訂單明細表、購物車表。下面給出建表SQL的核心片段。CREATE TABLE user ( id bigint NOT NULL AUTO_INCREMENT, phone varchar(20) NOT NULL, password varchar(100) NOT NULL, nickname varchar(50) DEFAULT NULL, balance decimal(10,2) DEFAULT 0.00, role tinyint DEFAULT 1, status tinyint DEFAULT 1, create_time datetime DEFAULT NULL, PRIMARY KEY (id), UNIQUE KEY uk_phone (phone) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4;role字段區(qū)分管理員和普通用戶balance存賬戶余額這是模擬支付的賬戶來源。手機號加唯一索引這是登錄賬號。CREATE TABLE product ( id bigint NOT NULL AUTO_INCREMENT, name varchar(100) NOT NULL, category_id bigint DEFAULT NULL, price decimal(10,2) NOT NULL, stock int NOT NULL, image varchar(255) DEFAULT NULL, status tinyint DEFAULT 1, PRIMARY KEY (id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4;商品表加了category_id字段預留分類擴展status字段控制上下架下架的商品用戶端不展示。CREATE TABLE orders ( id bigint NOT NULL AUTO_INCREMENT, order_no varchar(32) NOT NULL, user_id bigint NOT NULL, total_amount decimal(10,2) NOT NULL, status tinyint DEFAULT 0, create_time datetime DEFAULT NULL, pay_time datetime DEFAULT NULL, PRIMARY KEY (id), UNIQUE KEY uk_order_no (order_no) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4;訂單表里status是整個系統(tǒng)最關鍵的狀態(tài)字段0代表待支付1代表已支付2代表已離店。字段注釋必須寫清楚答辯時老師一定會問狀態(tài)怎么流轉的。CREATE TABLE order_item ( id bigint NOT NULL AUTO_INCREMENT, order_id bigint NOT NULL, product_id bigint NOT NULL, product_name varchar(100) DEFAULT NULL, price decimal(10,2) NOT NULL, quantity int NOT NULL, PRIMARY KEY (id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4;為什么要有訂單明細表因為一個訂單可能包含多個商品而訂單表只保存總額。到時候你要查某個訂單里買了哪些東西沒有明細表根本查不了。還有一點下單時要把商品名稱和價格冗余存一份這樣即使日后商品改名或者刪除訂單的歷史記錄仍然完整。這是電商系統(tǒng)的常規(guī)做法講出來顯得專業(yè)。3.2 用戶端功能模塊從登錄到購物車用戶端核心接口就四個注冊登錄、商品列表、購物車操作、提交訂單。登錄用手機號密碼密碼存在數(shù)據(jù)庫里用MD5加鹽處理。我知道MD5不算安全但畢設項目里夠用而且你可以主動跟老師說生產(chǎn)環(huán)境應該用BCrypt這反而成了加分項。購物車我采用了后端存儲而不是前端存儲的方案。有人認為購物車存前端localStorage簡單但那樣用戶換設備數(shù)據(jù)就丟了。我用一張購物車表關聯(lián)用戶和商品后端提供添加、修改數(shù)量、刪除、查詢四個接口。數(shù)據(jù)結構很簡單操作邏輯都在Service層。Service public class CartServiceImpl implements CartService { Override public Result addCart(CartDTO dto) { // 先查詢該用戶購物車里是否已有這個商品 LambdaQueryWrapperCart wrapper new LambdaQueryWrapper(); wrapper.eq(Cart::getUserId, dto.getUserId()) .eq(Cart::getProductId, dto.getProductId()); Cart exist cartMapper.selectOne(wrapper); if (exist ! null) { exist.setQuantity(exist.getQuantity() dto.getQuantity()); cartMapper.updateById(exist); } else { Cart cart new Cart(); cart.setUserId(dto.getUserId()); cart.setProductId(dto.getProductId()); cart.setQuantity(dto.getQuantity()); cartMapper.insert(cart); } return Result.success(加入成功); } }這里用MyBatis-Plus的LambdaQueryWrapper構造條件比寫XML簡潔很多而且類型安全字段名寫錯了編譯期就能發(fā)現(xiàn)。3.3 管理端功能模塊商品、訂單、會員、統(tǒng)計管理端的商品管理就是常規(guī)的增刪改查分頁MyBatis-Plus自帶Page對象配合selectPage方法幾行代碼搞定Override public PageProduct pageProducts(int pageNum, int pageSize, String keyword) { PageProduct page new Page(pageNum, pageSize); LambdaQueryWrapperProduct wrapper new LambdaQueryWrapper(); if (StringUtils.hasText(keyword)) { wrapper.like(Product::getName, keyword); } wrapper.orderByDesc(Product::getCreateTime); return productMapper.selectPage(page, wrapper); }訂單管理就是按狀態(tài)篩選訂單列表也可以查看訂單的明細。這里有個小技巧查詢訂單列表時如果每條訂單都要單獨查一次明細會產(chǎn)生N1查詢問題。簡單項目可以不那么較真但如果想展示自己的水平可以在Service層先把訂單ID列表查出來再一次性批量查詢明細然后按訂單ID分組組裝。這個優(yōu)化講出來能讓老師覺得你不是在背代碼。會員管理做得簡單就是一個用戶列表可以看到每個用戶的余額和消費統(tǒng)計。統(tǒng)計模塊我用ECharts畫了兩個圖表近七天銷售額折線圖、商品分類銷量占比餅圖。數(shù)據(jù)來源是SQL的聚合查詢。這里千萬別用循環(huán)查數(shù)據(jù)庫再在Java里計算效率低而且代碼丑直接讓MySQL分組求和查出來什么就展示什么。SELECT DATE_FORMAT(create_time, %Y-%m-%d) AS day, SUM(total_amount) AS amount FROM orders WHERE status 1 AND create_time DATE_SUB(CURDATE(), INTERVAL 7 DAY) GROUP BY day ORDER BY day;4. 關鍵代碼實現(xiàn)與實操細節(jié)4.1 模擬門禁與結算的核心邏輯無人超市系統(tǒng)最需要講故事的地方就是結算后如何放行。真實場景是硬件聯(lián)動這里用軟件模擬。我在后端設計了這樣一套邏輯用戶在購物車頁面點擊結算前端把購物車ID列表和用戶ID傳給后端。后端先校驗所有商品庫存是否充足如果不夠就返回明確提示。庫存充足的情況下在一個方法里完成三件事計算總金額、生成訂單和訂單明細、扣減庫存和用戶余額。這三件事必須放在同一個事務里要么全部成功要么全部回滾。這一步一定要講清楚事務是保證數(shù)據(jù)一致性的關鍵。Transactional(rollbackFor Exception.class) public Result checkout(CheckoutDTO dto) { ListCart carts cartMapper.selectBatchIds(dto.getCartIds()); BigDecimal total BigDecimal.ZERO; for (Cart cart : carts) { Product product productMapper.selectById(cart.getProductId()); if (product.getStock() cart.getQuantity()) { return Result.error(商品【 product.getName() 】庫存不足); } total total.add(product.getPrice().multiply(BigDecimal.valueOf(cart.getQuantity()))); } // 生成訂單號 String orderNo SM DateUtil.format(new Date(), yyyyMMddHHmmss) RandomUtil.randomNumbers(4); Orders order new Orders(); order.setOrderNo(orderNo); order.setUserId(dto.getUserId()); order.setTotalAmount(total); order.setStatus(1); order.setPayTime(new Date()); ordersMapper.insert(order); // 插入訂單明細、扣減庫存、扣減余額、清空購物車 return Result.success(orderNo); }事務注解Transactional放在這里的作用是如果插入訂單成功后扣庫存失敗整個方法回滾不會出現(xiàn)訂單生成了但庫存沒扣的臟數(shù)據(jù)。你可以把這句話原封不動寫進論文里。4.2 JWT登錄鑒權的實現(xiàn)細節(jié)前后端分離項目里登錄狀態(tài)通常用JWT而不是Session。JWT就是一個包含用戶信息的加密字符串服務器不保存狀態(tài)客戶端每次請求帶上服務器驗證簽名后解析出用戶ID。實現(xiàn)分三步第一步登錄成功后簽發(fā)TokenString token Jwts.builder() .setSubject(user.getId().toString()) .claim(phone, user.getPhone()) .claim(role, user.getRole()) .setExpiration(new Date(System.currentTimeMillis() 86400000)) .signWith(secretKey, SignatureAlgorithm.HS256) .compact();第二步寫一個攔截器攔截需要登錄的接口public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) { String token request.getHeader(Authorization); if (token null || !token.startsWith(Bearer )) { // 返回401狀態(tài)碼 response.setStatus(401); return false; } try { Claims claims Jwts.parserBuilder() .setSigningKey(secretKey) .build() .parseClaimsJws(token.substring(7)) .getBody(); request.setAttribute(userId, claims.getSubject()); return true; } catch (JwtException e) { response.setStatus(401); return false; } }第三步在WebMvcConfigurer里注冊攔截器配置哪些路徑需要攔截。登錄接口和商品列表接口不需要攔截購物車、訂單、管理端接口全部攔截。前端拿到401響應就跳回登錄頁。這里要注意一個坑攔截器注冊路徑如果寫錯你會發(fā)現(xiàn)某些接口沒攔截、某些接口明明應該攔截卻放行了。我習慣把接口路徑統(tǒng)一用/api/**開頭然后根據(jù)需要排除/api/user/login、/api/product/list這些公開接口。4.3 Vue端的路由守衛(wèi)與Axios封裝前端要做的事情是在Axios請求攔截器里自動帶上Token在響應攔截器里統(tǒng)一處理401跳轉。這個屬于固定套路直接貼代碼說明。// request.js axios.interceptors.request.use(config { const token localStorage.getItem(token); if (token) { config.headers[Authorization] Bearer token; } return config; }); axios.interceptors.response.use( response response.data, error { if (error.response error.response.status 401) { localStorage.removeItem(token); router.push(/login); } return Promise.reject(error); } );路由守衛(wèi)控制頁面訪問權限。比如管理端頁面只有role0的用戶能訪問普通用戶跳進去要彈提示router.beforeEach((to, from, next) { const token localStorage.getItem(token); if (to.path /login) { next(); return; } if (!token) { next(/login); return; } const role localStorage.getItem(role); if (to.meta.requiresAdmin role ! 0) { next(/); return; } next(); });我見過有些同學的畢設前端頁面能直接通過URL訪問后臺接口這就是沒做路由守衛(wèi)答辯時被問如何防止未授權訪問就答不上來。這一段屬于安全要點值得單獨準備一下。4.4 模擬支付與訂單狀態(tài)流轉支付模塊不用接真實支付接口做一個模擬余額支付就行。用戶可以事先在個人中心給賬戶充值也是模擬的管理員直接改余額。結算時后端判斷余額是否足夠不夠就返回余額不足請充值夠就扣減余額并生成支付記錄。訂單狀態(tài)從0流轉到1再在用戶點擊確認離店時流轉到2。如果你想讓系統(tǒng)看起來更完整還可以加一層虛擬的門外設備檢測用戶訂單狀態(tài)為2之后前端門禁頁顯示門鎖已開啟請通過。這個效果用Vue的過渡動畫就能實現(xiàn)成本低演示效果好。5. 本地部署與運行環(huán)境搭建5.1 開發(fā)環(huán)境準備清單我把整套環(huán)境版本列成一個清單照著裝就不會出兼容性問題組件版本說明JDK1.8別用17兼容問題太多MySQL5.7 或 8.08.0需要配置時區(qū)Node.js16.xVue2項目對Node版本要求寬松Maven3.8.xIDEA自帶也可IDEA2023.x社區(qū)版夠用MySQL安裝時有個細節(jié)安裝到Windows上的時候如果之前裝過其他版本端口3306可能被占用用netstat -ano查一下端口占用或者直接改端口3307并在連接串里同步修改。我遇到過一個同學裝完MySQLNavicat連接不上折騰半天發(fā)現(xiàn)服務根本沒啟動Windows服務列表里把MySQL服務啟動就好了。5.2 數(shù)據(jù)庫初始化的兩種方式第一種是用Navicat手工導入SQL文件適合看不懂命令行的人。新建數(shù)據(jù)庫smart_mart字符集選utf8mb4右鍵運行SQL文件把建表語句和初始數(shù)據(jù)一起導入。第二種是用命令行mysql -u root -p smart_mart.sql初始數(shù)據(jù)里我預置了幾個測試賬號一個管理員賬號admin/123456一個普通用戶13800000000/123456還有十來個商品數(shù)據(jù)。有初始數(shù)據(jù)的好處是系統(tǒng)一跑起來就能看到效果不用一個個手動錄入。答辯前務必檢查演示賬號還能不能用我見過有人把數(shù)據(jù)庫密碼改了現(xiàn)場登錄不進去非常尷尬。5.3 SpringBoot與Vue的啟動順序后端啟動很簡單IDEA里直接運行SmartMartApplication的main方法。啟動后訪問http://localhost:8080如果看到SpringBoot默認的錯誤頁說明端口正常。前端啟動分兩步npm install npm run serveDevServer默認跑在8080端口而后端也在8080必然沖突。解決辦法是在vue.config.js里配置前端端口和代理module.exports { devServer: { port: 3000, proxy: { /api: { target: http://localhost:8080, changeOrigin: true } } } }配置代理之后前端請求/api/product/list會轉發(fā)到后端的http://localhost:8080/api/product/list跨域問題由DevServer解決瀏覽器里看不到跨域報錯。啟動順序沒有嚴格要求但先啟動后端再啟動前端能保證前端代理的調試信息里不至于報連接被拒絕。5.4 生產(chǎn)環(huán)境打包前后端怎么合到一起畢設如果需要部署演示最省事的方式是把前端的build產(chǎn)物放到SpringBoot的static目錄下最后只發(fā)布一個Jar包。操作流程是npm run build生成dist目錄把dist下的所有文件復制到后端項目的src/main/resources/static目錄下再用Maven打包mvn clean package -DskipTests生成的target/smart-mart.jar用java -jar smart-mart.jar運行瀏覽器直接訪問http://localhost:8080SpringBoot會把前端頁面和接口請求放在同一個端口處理。注意此時前端的接口請求路徑必須是相對路徑也就是/api/xxx不能寫死成localhost:3000否則頁面加載了但接口全部403。6. 常見問題與避坑記錄6.1 SpringBoot版本太高引發(fā)的連環(huán)報錯這是這一屆學生里最常見的坑。SpringBoot 3.x強制要求JDK 17而很多人本機裝的是JDK 8啟動直接報UnsupportedClassVersionError。就算你臨時改了JDK版本MyBatis-Plus老版本里的Jsoup等依賴也會出現(xiàn)ClassNotFoundException因為SpringBoot 3用了Jakarta命名空間不再兼容javax.*。我的建議是統(tǒng)一用SpringBoot 2.7.18 JDK 8。如果你的團隊或者老師要求必須用新版本也不要硬扛直接換MyBatis-Plus 3.5.5以上版本并在pom.xml里排查所有用到javax.servlet的依賴。這類問題排查耗時很長而且越查越亂不如一開始就選穩(wěn)定版本。6.2 MySQL連接失敗時區(qū)、驅動、密碼三個坎連接MySQL 8時第一個坎是時區(qū)不配置serverTimezoneAsia/Shanghai會報The server time zone value ?D1ú±ê×?ê±?? is unrecognized解決辦法就是在連接串里加上時區(qū)參數(shù)。第二個坎是驅動類名MySQL 8要寫com.mysql.cj.jdbc.Driver寫舊的com.mysql.jdbc.Driver雖然也能跑但會報警告。第三個坎是密碼驗證方式MySQL 8默認用的caching_sha2_password和舊版JDBC驅動不兼容要么升級驅動版本要么在MySQL里執(zhí)行ALTER USER rootlocalhost IDENTIFIED WITH mysql_native_password BY 密碼。新手最容易在這里喪失信心其實這些報錯信息都很直白把英文翻譯一下就知道缺什么了。排查原則先看MySQL服務有沒有啟動再用命令行工具連接測試最后才去檢查Java代碼和配置。6.3 Vue項目啟動報錯與依賴安裝問題vue create過程可能卡在chromedriver這個依賴上這個是網(wǎng)絡問題居多。裝依賴時看到卡住不要直接CtrlC先等一會兒實在不行就換成npmmirror鏡像源再試。有些同學從前端拿到別人項目壓縮包后直接扔進IDEA這東西不是Java項目要先用命令行進入目錄執(zhí)行npm installIDEA只能幫你打開文件裝依賴還得靠npm。還有一類問題是Vue CLI版本太高要求Node.js版本過低的環(huán)境會報Digital Envelope routines::unsupported錯誤。常見的臨時解決辦法是執(zhí)行export NODE_OPTIONS--openssl-legacy-provider但這是權宜之計最好還是把Node.js升級到16或18的穩(wěn)定版本??傊岸谁h(huán)境問題大多出在版本不匹配上和寫代碼的關系不大但確實很消磨耐心。6.4 前后端聯(lián)調時常見的跨域與接口對接問題跨域問題是必考題。如果前端直接請求后端地址而沒有走代理瀏覽器會報CORS錯誤。解決方式有兩個一個是在SpringBoot里配置全局CORS另一個是像前面那樣在vue.config.js里配代理。我推薦后者因為生產(chǎn)環(huán)境打包后是同源部署配置代理能順帶模擬出這個效果。接口對接時的另一個坑是字段名對不上。前端約定用productName后端實體類寫的是name結果列表里商品名稱一列全是空的。這種問題排查起來很煩建議開發(fā)時打開瀏覽器F12看網(wǎng)絡請求的響應體先確定后端返回了哪些字段再去前端模板里核對。關鍵接口直接用Polyline圖的思路調通一個再看下一個不要攢了一堆問題再一起調試。6.5 答辯演示時的三個穩(wěn)定性建議現(xiàn)場演示翻車是畢設答辯的重災區(qū)我總結三個經(jīng)驗。第一演示前一天必須跑一遍完整流程不要僥幸小改動不影響。第二手機熱點和現(xiàn)場WiFi都不要壓上把學生證、身份證等無關頁面關掉省得切換頁面時卡頓。第三數(shù)據(jù)庫里要準備好演示數(shù)據(jù)商品別太少訂單要有不同狀態(tài)的這樣展示列表篩選功能時才有內容。還有一個容易被忽略的點答辯時老師喜歡反問你覺得這個系統(tǒng)哪里還可以改進。提前準備幾個合理的優(yōu)化方向比如引入Redis做購物車緩存、用RabbitMQ處理訂單異步通知、接入真實人臉識別SDK都會讓回答更從容。7. 后端的擴展與學習建議7.1 這個項目還能怎么擴展成創(chuàng)新點畢設如果只是把基本功能做完很多學校只給中等分。想讓系統(tǒng)更有競爭力可以從三個方向擴展。第一個方向是把靜態(tài)的商品列表升級成分頁條件搜索比如按價格區(qū)間、分類、熱度排序這涉及MyBatis-Plus的多條件組合查詢論文里也好寫。第二個方向是加一個簡單的推薦模塊根據(jù)用戶購買記錄推薦同類商品不需要上機器學習算法用一個簡單規(guī)則統(tǒng)計就行。第三個方向是接入WebSocket做實時庫存展示前端頁面每兩秒刷新一次庫存管理員能看到哪些商品快賣完了。我個人覺得最劃算的擴展是導入導出用EasyExcel實現(xiàn)商品數(shù)據(jù)的Excel批量導入和訂單導出。這個功能企業(yè)里用得極多而且演示效果直觀先把Excel模板下載出來填幾條數(shù)據(jù)再導入前后端都有交互答辯時可以讓老師親眼看到數(shù)據(jù)從Excel流進了數(shù)據(jù)庫比純口頭描述有說服力得多。7.2 學完這套項目你真正會收獲什么把這個項目完整擼一遍你收獲的不只是一堆代碼。你會真正理解一個管理系統(tǒng)的請求怎么從前端頁面發(fā)起經(jīng)過攔截器校驗、Controller參數(shù)接收、Service業(yè)務處理、Mapper數(shù)據(jù)查詢最后再返回到頁面上完整走通。這個鏈路是書本里很難講透的只有親手連起來才有感覺。很多人學新項目喜歡通篇復制跑通了就覺得自己會了。我的建議是跑通之后從一個小功能開始獨立重寫比如單獨把提交訂單邏輯自己寫一遍不參考原代碼。能寫出來說明你真正掌握了寫不出來就對著原代碼一行行比對找出你卡住的地方那正是你需要補的知識點。7.3 最后補充一個調試的小技巧在項目開發(fā)過程中我強烈建議后端保持log-impl: org.apache.ibatis.logging.stdout.StdOutImpl這條配置能讓控制臺打印出每條SQL執(zhí)行語句和參數(shù)值。當你的列表數(shù)據(jù)不對時先看SQL有沒有按預期拼接條件當更新操作沒生效時先看SQL影響行數(shù)是0還是1。大部分數(shù)據(jù)問題都能靠這一步定位比在Java代碼里打斷點還高效。調試工具上前端打開F12的Network面板看接口響應后端看IDEA控制臺的日志兩邊對照著調效率會高很多。不要碰到問題就甩給系統(tǒng)報錯四個字先把報錯信息完整讀三遍你會發(fā)現(xiàn)大部分問題其實描述得已經(jīng)很清楚了。