開發(fā)全攻略)
簡介一份基于微信小程序與Java后端SSMMySQL的鮮花銷售畢業(yè)設(shè)計(jì)完整工程包面向計(jì)算機(jī)相關(guān)專業(yè)畢業(yè)生、課程設(shè)計(jì)及小程序?qū)崙?zhàn)學(xué)習(xí)者覆蓋管理員、用戶、商家三個(gè)角色實(shí)現(xiàn)了首頁、鮮花信息與分類管理、訂單及系統(tǒng)管理等核心業(yè)務(wù)模塊。整個(gè)壓縮包共1341個(gè)文件約12.51MB以png圖片、Java源碼、Vue頁面、JavaScript邏輯、wxml/wxss小程序頁面及SQL腳本為主同時(shí)包含bat啟動(dòng)腳本與說明文檔便于直接部署和二次開發(fā)。資源提供完整源碼和數(shù)據(jù)庫其中附帶數(shù)據(jù)庫腳本、構(gòu)建運(yùn)行批處理及詳細(xì)說明可幫助讀者理解SSM框架在小程序后臺(tái)中的應(yīng)用也能快速跑通前后端聯(lián)調(diào)。目前已有235人學(xué)習(xí)下載適合需要完整畢業(yè)設(shè)計(jì)案例和項(xiàng)目實(shí)戰(zhàn)經(jīng)驗(yàn)的人群參考。1. 畢業(yè)設(shè)計(jì)選它不只是為了“能答辯”這一句又到一年畢設(shè)季很多人在選題頁面反復(fù)橫跳。如果你手頭拿到的題目是“基于微信小程序java后端的鮮花銷售”先別急著把它歸為“又一套商城系統(tǒng)”。這個(gè)項(xiàng)目真正跑通之后你會(huì)發(fā)現(xiàn)它把前后端分離、微信登錄、訂單流程、數(shù)據(jù)庫表設(shè)計(jì)這些面試常問的東西全串起來了。對(duì)Java基礎(chǔ)還不算扎實(shí)、又想快速擁有一套能演示、能講清楚的項(xiàng)目的人來說它是性價(jià)比很高的一個(gè)。下面按我實(shí)際跑通的路徑來講項(xiàng)目拆解、本地啟動(dòng)、前后端對(duì)接、常見坑以及怎么把它改成你自己的畢設(shè)。2. 先把項(xiàng)目拆開看后端、小程序與數(shù)據(jù)庫各管什么拿到壓縮包解壓后不要急著雙擊 README先按“數(shù)據(jù)庫腳本、后端工程、小程序前端”三個(gè)目錄去認(rèn)識(shí)它。這類畢設(shè)工程的典型結(jié)構(gòu)是一個(gè) Spring Boot 后端提供 RESTful 接口一個(gè)原生微信小程序作為前端一個(gè) MySQL 腳本負(fù)責(zé)初始化數(shù)據(jù)。三者通過 HTTP 接口關(guān)聯(lián)數(shù)據(jù)表通過外鍵和業(yè)務(wù)邏輯維系。下面把每一塊說清楚免得你后面改的時(shí)候不知道動(dòng)哪里。2.1 管理員端功能清單商品、分類、訂單、輪播是標(biāo)配后端管理接口對(duì)應(yīng)的是一個(gè)管理后臺(tái)。雖然這個(gè)壓縮包不一定自帶 Vue 后臺(tái)頁面但接口層是完整的。常見做法是后端提供一組/admin/**接口管理員登錄后可以對(duì)商品做增刪改查、上下架管理商品分類處理訂單狀態(tài)待發(fā)貨、發(fā)貨、完成還能配置首頁輪播圖。我拿到的這份工程里管理員登錄走的是admin表密碼用 MD5 加密存儲(chǔ)初始賬號(hào)在 SQL 腳本里就能看到。商品這塊一般包含名稱、主圖、輪播圖列表、詳情富文本或圖片、分類ID、價(jià)格、庫存、銷量。這些字段在商品表flower或product里都能對(duì)上。訂單表則記錄用戶 ID、收貨地址、總金額、狀態(tài)、下單時(shí)間子表order_item存每個(gè)商品的數(shù)量和單價(jià)。主表負(fù)責(zé)匯總子表負(fù)責(zé)明細(xì)是標(biāo)準(zhǔn)的一對(duì)多設(shè)計(jì)。分類表通常很簡單id、名稱、圖片、排序號(hào)。輪播圖表也是 id、圖片、跳轉(zhuǎn)鏈接。所以如果你調(diào)研需求會(huì)發(fā)現(xiàn)“鮮花銷售”本質(zhì)上就是一個(gè)通用商城把商品名詞換掉就能套用到其他垂直品類。這也是為什么這類項(xiàng)目容易被改造成蛋糕店、零食店、二手交易。答辯時(shí)你把這一層講清楚老師就知道你不是只會(huì)照抄。2.2 用戶端小程序登錄、瀏覽、加購、下單一條線用戶端是原生微信小程序頁面大概包括首頁輪播分類入口商品列表、分類頁、商品詳情頁、購物車頁、確認(rèn)訂單頁、訂單列表頁、個(gè)人中心頁。邏輯上是一條完整鏈路用戶打開首頁看商品點(diǎn)進(jìn)詳情加入購物車結(jié)算下單模擬支付然后在訂單列表看到狀態(tài)變化。這一步覆蓋了小程序開發(fā)里最核心的頁面?zhèn)鲄ⅰ⒈镜鼐彺妗⒄?qǐng)求封裝和組件復(fù)用。登錄部分小程序端用wx.login拿code傳給后端后端替小程序向微信服務(wù)器換openid然后生成自定義 token 返回給前端。有些版本還會(huì)調(diào)getPhoneNumber組件獲取手機(jī)號(hào)但注意這個(gè)接口需要小程序認(rèn)證為企業(yè)主體個(gè)人主體沒法用這點(diǎn)在避坑章節(jié)再強(qiáng)調(diào)。購物車數(shù)據(jù)通常存在后端表里cart表也有簡化版存在本地wx.setStorageSync里。這份工程既然帶了數(shù)據(jù)庫多半走的是后端購物車。這樣訂單表直接引用購物車數(shù)據(jù)生成邏輯閉環(huán)更完整。如果你拿到的版本是本地購物車也無妨但要注意換設(shè)備后購物車會(huì)丟答辯時(shí)可以提一句這是簡化方案。2.3 數(shù)據(jù)庫設(shè)計(jì)概覽五張核心表怎么關(guān)聯(lián)打開 SQL 腳本重點(diǎn)看這幾張表表名核心字段作用adminid, username, password管理員登錄userid, openid, nickname, avatar, phone用戶信息categoryid, name, sort商品分類flowerid, category_id, name, price, stock, image, detail商品主體cartid, user_id, flower_id, quantity, checked購物車orderid, order_no, user_id, total_amount, status, address_id訂單主表order_itemid, order_id, flower_id, quantity, price訂單明細(xì)bannerid, image, link首頁輪播關(guān)系很好懂user與order是一對(duì)多order與order_item是一對(duì)多flower與category是多對(duì)一。購物車?yán)镆粭l記錄對(duì)應(yīng)一個(gè)用戶和一個(gè)商品。這種設(shè)計(jì)是教科書級(jí)別的范式不花哨但適合答辯——老師問外鍵、索引、三范式你都能指著表回答。這里有一個(gè)細(xì)節(jié)訂單金額在order表里冗余了一份而不是每次都去order_item里 sum。這在商城系統(tǒng)里是常見做法因?yàn)橛唵紊珊笊唐穬r(jià)格可能變動(dòng)必須把下單那一刻的金額快照保存下來。面試如果被問到“商品改價(jià)了老訂單怎么算”答案就在這里。另外商品表flower我一般建議多一個(gè)status字段0 下架 1 上架和sales字段銷量這兩列在畢設(shè)演示中非常有用。下架商品在前端列表不展示銷量可以在商品詳情頁展示。SQL 腳本里如果缺少自己ALTER TABLE補(bǔ)上即可不影響原邏輯。數(shù)據(jù)庫字符集一般設(shè)置成utf8mb4否則表情符號(hào)存不進(jìn)去。商品名稱、用戶昵稱里可能出現(xiàn) emoji用utf8會(huì)報(bào)錯(cuò)或變問號(hào)。稍后避坑章節(jié)再展開。3. 從本地跑起來JDK、MySQL與微信開發(fā)者工具的三步順序很多人拿到源碼第一步就卡在環(huán)境上。這個(gè)項(xiàng)目我建議按“后端→數(shù)據(jù)庫→小程序”的順序依次啟動(dòng)每一步驗(yàn)證通過再進(jìn)下一步不要在沒跑通后端的情況下就去點(diǎn)小程序不然接口報(bào)錯(cuò)你會(huì)分不清是前端問題還是后端問題。3.1 Spring Boot 后端啟動(dòng)前的環(huán)境準(zhǔn)備這類項(xiàng)目的后端技術(shù)棧基本是 Spring Boot 2.x MyBatis / MyBatis-Plus Maven。打開pom.xml確認(rèn)依賴版本。建議用 JDK 1.8Spring Boot 2.x 搭配很穩(wěn)如果裝了 JDK 17有些舊依賴會(huì)因javax命名空間問題加載失敗。我一般會(huì)在 IDEA 里把 Project Structure 里的 SDK 切到 1.8Maven Runner 里的 JRE 也選 1.8避免編譯期報(bào)錯(cuò)。啟動(dòng)前要在 IDEA 里做兩件事一是確認(rèn) Maven 已經(jīng)配了阿里云鏡像否則下載依賴能磨掉你半小時(shí)耐心二是打開application.yml看一眼端口和數(shù)據(jù)源配置。server: port: 8080 spring: datasource: url: jdbc:mysql://localhost:3306/flower_shop?useUnicodetruecharacterEncodingutf8serverTimezoneAsia/Shanghai username: root password: 你的數(shù)據(jù)庫密碼 driver-class-name: com.mysql.cj.jdbc.Driver這里的serverTimezoneAsia/Shanghai很關(guān)鍵MySQL 8 以上不寫它連接時(shí)大概率報(bào)時(shí)區(qū)錯(cuò)誤。characterEncodingutf8則保證中文正常寫入。如果你的 MySQL 是 5.7驅(qū)動(dòng)用com.mysql.jdbc.Driver也行但新版工程一般都用cj驅(qū)動(dòng)。參數(shù)說明useUnicodetrue讓驅(qū)動(dòng)按 Unicode 傳輸字符配合characterEncodingutf8才能保證中文不亂碼。MySQL 8 建議用utf8mb4但驅(qū)動(dòng)對(duì)utf8兼容。啟動(dòng)方式有兩種IDEA 里直接運(yùn)行Application.java的 main 方法或命令行mvn spring-boot:run。控制臺(tái)出現(xiàn)Started Application in x.xxx seconds且沒有紅色異常就說明后端起來了。此時(shí)瀏覽器訪問http://localhost:8080/api/xxx具體路徑看 Controller 注解能看到 JSON說明接口正常。3.2 導(dǎo)入數(shù)據(jù)庫腳本并核對(duì)賬號(hào)配置數(shù)據(jù)庫腳本一般在工程根的sql目錄下文件名類似flower_shop.sql。用 Navicat 或命令行導(dǎo)入。命令行導(dǎo)入命令mysql -uroot -p flower_shop.sql導(dǎo)入后執(zhí)行show tables;你應(yīng)該能看到 8 張左右的表。如果一張表都沒看到檢查 SQL 腳本里是否帶了CREATE DATABASE flower_shop而當(dāng)前登錄用戶沒有建庫權(quán)限。常見做法是腳本里有建庫語句導(dǎo)入后要切換庫use flower_shop;。這里有一個(gè)容易忽略的點(diǎn)腳本里的管理員初始密碼是加密后的 MD5 值除非你知道原始密碼否則不要直接改admin表的 password 字段。登錄不了后臺(tái)時(shí)最穩(wěn)妥的辦法是找代碼里的MD5Util或DigestUtils手動(dòng)把新密碼加密后 update 進(jìn)表。我見過太多人直接把這個(gè)字段改成明文然后卡在“密碼錯(cuò)誤”上花半天找原因。另外檢查application.yml里的數(shù)據(jù)庫名、用戶名、密碼是否與本地一致。數(shù)據(jù)庫名一般是flower_shop如果腳本里建庫名不一樣要么改 yml要么改腳本二選一不要兩處都留著不同。你可以用DESC user;看下表結(jié)構(gòu)驗(yàn)證腳本是否完整。如果表里已經(jīng)帶了測試數(shù)據(jù)啟動(dòng)后首頁就會(huì)有商品省得自己造數(shù)。3.3 小程序端導(dǎo)入微信開發(fā)者工具后端和數(shù)據(jù)庫都就緒后打開微信開發(fā)者工具選擇“導(dǎo)入項(xiàng)目”目錄指向小程序端文件夾通常叫miniprogram或frontend。這里需要兩個(gè)關(guān)鍵配置一是你自己的測試 AppID如果沒有用測試號(hào)也可以但登錄相關(guān)功能受限二是本地開發(fā)時(shí)在“詳情→本地設(shè)置”里勾選“不校驗(yàn)合法域名、web-view業(yè)務(wù)域名、TLS 版本以及 HTTPS 證書”。小程序端的請(qǐng)求地址一般在utils/config.js或app.js里常見寫法const BASE_URL http://127.0.0.1:8080/api; module.exports { BASE_URL }注意微信開發(fā)者工具里請(qǐng)求localhost有時(shí)會(huì)因 IPv6 解析慢建議直接用127.0.0.1。如果你后端起在別的機(jī)器或云上這里就要改成服務(wù)器 IP 或域名并配好微信后臺(tái)的 request 合法域名。本地開發(fā)勾選不校驗(yàn)域名就行但真機(jī)預(yù)覽時(shí)手機(jī)無法訪問你電腦的localhost需要把后端部署到可訪問的服務(wù)器或者用內(nèi)網(wǎng)穿透工具這里不展開。導(dǎo)入后編譯如果看到首頁輪播圖和商品列表都出來了說明前后端已經(jīng)打通。如果商品列表空白打開調(diào)試器看 Console 報(bào)錯(cuò)最常見的是請(qǐng)求 404 或請(qǐng)求被攔截。還有一個(gè)適配問題如果頁面頂部出現(xiàn)重影或按鈕和膠囊重疊是因?yàn)樽远x導(dǎo)航欄沒有適配狀態(tài)欄高度。通常用wx.getSystemInfoSync()獲取statusBarHeight再用wx.getMenuButtonBoundingClientRect()拿到膠囊的位置動(dòng)態(tài)計(jì)算導(dǎo)航欄高度才能保證在劉海屏和普通屏上都不錯(cuò)位。4. 小程序與Java后端聯(lián)調(diào)登錄換取openid與Token鑒權(quán)這一章是畢設(shè)答辯的重頭戲。老師問“怎么知道你登錄了”“接口安全怎么做的”你只要能說清楚登錄時(shí)序和 token 校驗(yàn)基本就能過關(guān)。下面按實(shí)際代碼路徑講。4.1 wx.login 拿到 code后端換 openid 的流程小程序端點(diǎn)擊微信登錄時(shí)調(diào)用wx.login獲取一個(gè)臨時(shí)憑證code這個(gè) code 只能使用一次有效期五分鐘。把 code 通過接口 POST 給后端后端拿著 code 去微信接口jscode2session換取openid和session_key。openid 是用戶在微信體系下的唯一 ID用它去user表里查查不到就注冊(cè)新用戶。后端代碼如下偽代碼邏輯具體類名以源碼為準(zhǔn)PostMapping(/auth/login) public Result login(RequestBody LoginRequest req) { // 1. 用小程序的 appid 和 secret以及前端傳的 code請(qǐng)求微信接口 String url https://api.weixin.qq.com/sns/jscode2session?appid APPID secret SECRET js_code req.getCode() grant_typeauthorization_code; String response HttpUtil.get(url); // 2. 解析返回的 openid JSONObject obj JSON.parseObject(response); String openid obj.getString(openid); // 3. 查表沒有則注冊(cè) User user userMapper.selectByOpenid(openid); if (user null) { user new User(); user.setOpenid(openid); userMapper.insert(user); } // 4. 生成自己的登錄態(tài) token String token UUID.randomUUID().toString().replace(-, ); redis.set(token_ token, String.valueOf(user.getId()), 7 * 24 * 3600); return Result.success(token); }邏輯說明后端不信任前端傳來的任何身份信息只用code向微信服務(wù)器兌換身份這是安全邊界的第一條。grant_typeauthorization_code是固定值表示 OAuth2 授權(quán)碼模式。如果你沒有 Redis也可以用session或直接返回token并存在內(nèi)存 Map 里但項(xiàng)目重啟就失效畢設(shè)夠用答辯時(shí)記得說明缺點(diǎn)。參數(shù)說明APPID和SECRET在小程序后臺(tái)的“開發(fā)→開發(fā)管理→開發(fā)設(shè)置”里獲取。注意 SECRET 不要硬編碼在 Web 端也不要在小程序代碼里出現(xiàn)否則任何人都能拿去調(diào)用接口。我見過源碼里把這兩個(gè)值寫在util.js里的這種做法答辯時(shí)會(huì)被扣分至少把它挪到后端配置里。小程序端對(duì)應(yīng)代碼wx.login({ success: async (res) { const code res.code; const loginRes await request({ url: /auth/login, method: POST, data: { code: code } }); wx.setStorageSync(token, loginRes.data.token); } })這里把token存到本地緩存后續(xù)每個(gè)請(qǐng)求在 header 里帶上Authorization: token后端攔截器統(tǒng)一校驗(yàn)。4.2 前后端分離項(xiàng)目的接口路徑與參數(shù)約定后端 Controller 一般會(huì)分成兩類用戶端/api/**和管理端/admin/**。用戶端接口例如GET /api/banner、GET /api/flower/list?categoryId1、POST /api/cart/add、POST /api/order/submit。管理端需要管理員身份通常會(huì)有一個(gè)/admin/login登錄后返回管理員 token后續(xù)管理操作攜帶該 token。統(tǒng)一返回格式非常重要推薦約定成{ code: 0, message: success, data: { } }code0表示成功非 0 為業(yè)務(wù)異常。前端在request封裝里統(tǒng)一攔截function request(options) { return new Promise((resolve, reject) { wx.request({ url: BASE_URL options.url, method: options.method || GET, data: options.data || {}, header: { Content-Type: application/json, Authorization: wx.getStorageSync(token) || }, success: (res) { if (res.data.code 0) { resolve(res.data); } else { wx.showToast({ title: res.data.message, icon: none }); reject(res.data); } }, fail: (err) reject(err) }) }) }這段封裝的價(jià)值在于所有頁面不用關(guān)心 token 怎么傳統(tǒng)一在 header 里帶所有后端返回的錯(cuò)誤在success回調(diào)里統(tǒng)一彈 toast。頁面代碼只管調(diào)用request不需要散落一堆try/catch。參數(shù)說明BASE_URL不要寫在每個(gè)頁面里url以/開頭拼接。如果你遇到“接口通了但登錄不了”優(yōu)先檢查 header 里的 token 名是否和后端攔截器取的 header 名一致。4.3 常見做法用 Token 還是 Session 做認(rèn)證微信小程序沒有 Cookie 概念所以傳統(tǒng) Session 方式要么手動(dòng)把JSESSIONID存起來再傳要么干脆用 Token。Token 方案在前后端分離項(xiàng)目里是主流畢設(shè)中用起來也簡單登錄成功后返回一個(gè)隨機(jī)字符串后端用一個(gè) Map 或 Redis 存這個(gè) token 對(duì)應(yīng)的用戶 ID下一次請(qǐng)求帶著 token后端查 Map 就知道是誰。Component public class AuthInterceptor implements HandlerInterceptor { Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { String token request.getHeader(Authorization); if (token null || token.isEmpty()) { response.setStatus(401); return false; } String userId tokenStore.get(token); if (userId null) { response.setStatus(401); return false; } request.setAttribute(userId, userId); return true; } }這個(gè)攔截器要注冊(cè)到 WebMvcConfigurer 里并排除登錄接口和商品瀏覽接口否則游客沒有 token連商品列表都看不了。參數(shù)說明tokenStore是一個(gè)MapString, String生產(chǎn)環(huán)境應(yīng)該換成 Redis 并設(shè)置過期時(shí)間。答辯時(shí)老師如果問“你的 token 永遠(yuǎn)不會(huì)過期嗎”你回答“目前用內(nèi)存存儲(chǔ)重啟失效生產(chǎn)環(huán)境會(huì)替換為 Redis 并設(shè)置 7 天過期”就是一次不錯(cuò)的追問應(yīng)對(duì)。管理端接口建議單獨(dú)寫一個(gè)AdminAuthInterceptor不能和用戶端共用同一個(gè) token 存儲(chǔ)。管理員的 token 要有獨(dú)立的過期機(jī)制否則普通用戶偽造 token 就能調(diào)用后臺(tái)接口。5. 避坑與常見問題五個(gè)最讓人翻車的點(diǎn)這一章是我跑這類畢設(shè)項(xiàng)目時(shí)的血淚經(jīng)驗(yàn)匯總每一條都是真實(shí)發(fā)生過的。按“現(xiàn)象→原因→解決”的順序?qū)懩阏罩挪槟苁“胩鞎r(shí)間。5.1 現(xiàn)象微信開發(fā)者工具請(qǐng)求后端接口報(bào)“不在以下合法域名列表中”原因微信小程序出于安全考慮要求所有wx.request的域名必須是 HTTPS 且在后臺(tái)配置過。本地開發(fā)時(shí)后端是http://127.0.0.1:8080肯定不滿足。解決開發(fā)階段在微信開發(fā)者工具右上角“詳情→本地設(shè)置”勾選“不校驗(yàn)合法域名”。真機(jī)預(yù)覽時(shí)沒有這個(gè)選項(xiàng)要么把后端部署到帶 HTTPS 的服務(wù)器并配置域名要么用內(nèi)網(wǎng)穿透。注意勾選后要重新編譯。如果依然報(bào)錯(cuò)檢查BASE_URL里是否帶了/api后又拼接了/api形成/api/api雙前綴。這種玄學(xué)問題最費(fèi)時(shí)間建議先打印實(shí)際請(qǐng)求的完整 URL。5.2 現(xiàn)象數(shù)據(jù)庫導(dǎo)入后中文亂碼商品名稱變成“???”原因?qū)?SQL 腳本時(shí)客戶端連接字符集不是 UTF-8或者腳本本身的編碼是 GBK。最典型的是用命令行導(dǎo)入時(shí)沒加--default-character-setutf8mb4。解決重新導(dǎo)入并顯式指定字符集mysql -uroot -p --default-character-setutf8mb4 flower_shop.sql如果已經(jīng)導(dǎo)入了可以改表字符集ALTER TABLE flower CONVERT TO CHARACTER SET utf8mb4;同時(shí)檢查application.yml里的連接串是否有characterEncodingutf8并確認(rèn) MySQL 的my.cnf里[client]和[mysqld]都設(shè)置了default-character-setutf8mb4。改完重啟后端重新查一遍數(shù)據(jù)才有意義。這里多說一句你看到源碼里中文正常不代表你本地復(fù)制過來也正常編碼問題要最先查。5.3 現(xiàn)象商品圖片上傳后小程序端顯示不出來原因圖片路徑存的是絕對(duì)路徑比如D:\upload\1.jpg或者存的是沒有帶域名的相對(duì)路徑但后端配置了虛擬路徑映射。前端拿到\upload\1.jpg拼上BASE_URL后變成http://127.0.0.1:8080/api/upload/1.jpg而后端靜態(tài)資源映射路徑是/upload/**映射到磁盤目錄接口前綴/api把路徑吃掉了。解決第一后端存圖片時(shí)只存相對(duì)路徑/upload/xxx.jpg不要存絕對(duì)路徑。第二靜態(tài)資源映射配置要確認(rèn)攔截路徑。Spring Boot 中常見做法Configuration public class WebConfig implements WebMvcConfigurer { Override public void addResourceHandlers(ResourceHandlerRegistry registry) { registry.addResourceHandler(/upload/**) .addResourceLocations(file:D:/upload/); } }這里的file:D:/upload/是圖片在服務(wù)器本地的物理路徑注意結(jié)尾要有/。如果你把圖片放在項(xiàng)目目錄下推薦用file:./upload/這種相對(duì)寫法避免部署到別的機(jī)器還要改路徑。前端拼圖片地址時(shí)我一般會(huì)讓后端直接返回完整 URL也就是在序列化商品對(duì)象時(shí)把image字段加工成http://ip:8080/upload/...。源碼里如果沒這么做前端拼的時(shí)候要小心雙斜杠問題。5.4 現(xiàn)象并發(fā)下單時(shí)庫存變成負(fù)數(shù)訂單和庫存對(duì)不上原因下單邏輯是“查詢庫存→判斷充足→UPDATE庫存→插入訂單”中間沒有事務(wù)和鎖。兩個(gè)人同時(shí)買同一款鮮花都讀到庫存 5各自減 1UPDATE 最終是 4但實(shí)際上應(yīng)該賣出 2 單。更糟的是如果訂單插入失敗庫存已經(jīng)減了但訂單沒生成數(shù)據(jù)不一致。解決在 Service 方法上加Transactional并且用樂觀鎖或悲觀鎖控制庫存更新。最簡單的做法是 UPDATE 語句里加上庫存條件UPDATE flower SET stock stock - 1 WHERE id ? AND stock 0如果影響行數(shù)為 0說明庫存不足直接拋業(yè)務(wù)異?;貪L訂單。同時(shí)Transactional保證扣庫存和插訂單在同一個(gè)事務(wù)里任何一步失敗都回滾。代碼示意Transactional(rollbackFor Exception.class) public Order submitOrder(SubmitRequest req) { // 1. 扣庫存受影響行數(shù)為0則拋異常 int rows flowerMapper.deductStock(req.getFlowerId(), req.getQuantity()); if (rows 0) { throw new BusinessException(庫存不足); } // 2. 創(chuàng)建訂單主表和明細(xì)表 // 3. 返回訂單對(duì)象 }參數(shù)說明rollbackFor Exception.class很關(guān)鍵只寫Transactional默認(rèn)只回滾 RuntimeExceptionchecked exception比如IOException會(huì)導(dǎo)致庫存扣了但訂單沒建這就是經(jīng)典的血淚坑。5.5 現(xiàn)象個(gè)人版小程序獲取手機(jī)號(hào)按鈕功能點(diǎn)擊無效原因button open-typegetPhoneNumber這個(gè)能力需要小程序主體是企業(yè)或個(gè)體工商戶個(gè)人主體的小程序無法調(diào)用點(diǎn)擊按鈕會(huì)收到getPhoneNumber:fail no permission或類似錯(cuò)誤。很多簡歷上寫了“獲取用戶手機(jī)號(hào)”但用個(gè)人 AppID 演示時(shí)直接翻車。解決如果只是做畢設(shè)演示建議把手機(jī)號(hào)獲取改成可編輯的輸入框讓用戶手動(dòng)填接口里校驗(yàn) 11 位格式?;蛘呱暾?qǐng)一個(gè)企業(yè)主體小程序?qū)W生可借用實(shí)驗(yàn)室、導(dǎo)師公司的資質(zhì)才能完整走通。答辯時(shí)把這一點(diǎn)提前說明個(gè)人開發(fā)者無法使用該接口所以才退化為手動(dòng)輸入這是合規(guī)做法不是偷懶。如果源碼里已經(jīng)使用了getPhoneNumber你拿到后不要硬調(diào)先把該功能隱藏避免現(xiàn)場爆紅。6. 改造成你自己的畢設(shè)換主題、加狀態(tài)機(jī)與交付習(xí)慣前面幾章解決了“跑起來”和“避坑”最后一章講怎么把它變成“你的項(xiàng)目”。畢竟答辯時(shí)老師會(huì)問你做了什么如果你回答“把鮮花換成了蛋糕”那至少說明你理解了項(xiàng)目的可維護(hù)性。6.1 替換商品分類、圖片與文案的最快路徑打開數(shù)據(jù)庫category表里把“鮮花”“綠植”改成你的主題分類比如“蛋糕”“甜品”“烘焙工具”。flower表里的商品名稱、圖片 URL、價(jià)格、庫存都改成你自己的數(shù)據(jù)。圖片資源直接替換upload目錄下的文件保持原文件名映射最省事。小程序端的頁面文案在pages目錄下的.wxml文件里搜索“鮮花”關(guān)鍵字全局替換。改完這幾處項(xiàng)目主題就變了前后端邏輯一行不用動(dòng)。建議做一張“替換清單”表格把需要改的位置列出來相當(dāng)于一份自定義文檔。答辯時(shí)拿出這個(gè)比只會(huì)說“我改了圖片”要有說服力得多。6.2 給畢設(shè)答辯加分的驗(yàn)證點(diǎn)訂單狀態(tài)機(jī)原項(xiàng)目訂單狀態(tài)可能只是簡單的下拉框選擇。你可以把它升級(jí)成狀態(tài)機(jī)待付款→待發(fā)貨→已發(fā)貨→已完成以及取消、退款兩個(gè)分支。后端用一張狀態(tài)枚舉表維護(hù)前端根據(jù)狀態(tài)顯示按鈕。實(shí)現(xiàn)并不難但在代碼里體現(xiàn)“狀態(tài)流轉(zhuǎn)”的概念能讓評(píng)委覺得你有工程意識(shí)。public enum OrderStatusEnum { WAIT_PAY(0, 待付款), WAIT_SHIP(1, 待發(fā)貨), SHIPPED(2, 已發(fā)貨), FINISHED(3, 已完成), CANCELLED(4, 已取消); // 構(gòu)造方法、getter... }在order表增加一個(gè)字段或者直接復(fù)用原 status 字段把 update 操作封裝成changeStatus(orderId, fromStatus, toStatus)如果當(dāng)前狀態(tài)不是fromStatus就拋異常。用這套邏輯把之前簡單 update 的代碼替換掉項(xiàng)目深度至少提升一個(gè)檔次。6.3 動(dòng)手前先備份改完每個(gè)點(diǎn)都要回歸最后說一個(gè)我自己的習(xí)慣。每次拿到新的畢設(shè)源碼第一件事不是雙擊運(yùn)行而是先看數(shù)據(jù)庫腳本和配置文件再啟動(dòng)項(xiàng)目。任何一次改動(dòng)之前我都會(huì)先備份一份原始代碼哪怕只是改一個(gè)字段名。從那以后我每次跑這類項(xiàng)目都會(huì)把“后端啟動(dòng)→小程序編譯→下單流程”強(qiáng)制走一遍確認(rèn)沒有回歸問題再繼續(xù)下一步。希望幫到你。本文還有配套的精品資源點(diǎn)擊獲取