境初始化到訂單壓測的完整實踐)
簡介一套面向生鮮電商場景的 Spring/Maven 項目源碼定位為本地版可直接導入 IntelliJ IDEA 構建運行適合開發(fā)者用于教學、二次開發(fā)或項目實戰(zhàn)練習。壓縮包共 434 個文件約 14.11MB其中 Java 源碼、XML 配置、class 編譯文件、JPG/PNG 圖片資源等一應俱全后端業(yè)務與控制層、前端頁面素材、Maven 構建配置均有覆蓋。壓縮包內置依賴管理與構建腳本能幫助快速搭建環(huán)境、屏蔽版本差異.gitignore 與 target、src 等目錄劃分也便于保持倉庫整潔、理解項目結構與編譯產(chǎn)物。已有 1021 人學習下載。整個項目內包含訂單、商品、購物車、用戶及統(tǒng)一異常處理等業(yè)務模塊通過閱讀源碼可掌握這些核心模塊的實現(xiàn)思路以及 Spring Boot/Spring MVC 項目從零配置到啟動運行的整體流程適合正在學習電商系統(tǒng)開發(fā)、希望快速上手的中初級開發(fā)者。1. 慕慕生鮮本地版一套能離線跑起來的生鮮電商工程比預想中值錢把「慕慕生鮮項目源碼本地版」這串字扔進搜索框的人多半是剛拿到某個壓縮包、解壓后面對一堆模塊目錄發(fā)懵的開發(fā)者和想復現(xiàn)項目的畢設選手。這個標題說的不是某個網(wǎng)頁模板也不是幾段教學用的 CRUD 碎片而是一整套可在本機完整運行的生鮮電商工程通常包含 C 端用戶端H5/小程序殼、商家端、管理后臺和配送端后端提供商品、訂單、庫存、營銷、支付回調等接口。本地版的價值在于沒有云服務依賴、沒有真實短信和第三方支付通道用模擬支付和內置賬號就能把一條真實下單鏈路從商品瀏覽走到訂單出庫。這篇筆記我按自己實際跑生鮮類電商源碼的習慣來寫先講本地版的技術選型和初始化順序再拆商品與訂單主鏈路接著解決前后端聯(lián)調最后給避坑清單和一個壓測驗證技巧。新手跟著步驟能把這套東西跑起來熟手可以直接跳過前兩章去看第 4、5 章的端口沖突、Redis 序列化和事務失效問題。2. 本地版技術選型與初始化先把 JDK、MySQL、Redis 三件套理順2.1 為什么本地版普遍是「單體內核 模塊化目錄」而不是微服務集群我見過不少剛接觸源碼的人一看到order-service、user-service這種目錄名就緊張以為必須在本地起 Nacos 集群、搞一堆注冊中心才能運行。實際上去掉「分布式演示」需求后生鮮項目源碼的本地版通常是一個 Spring Boot 單體應用或者拆成 35 個可獨立啟動的 Maven 模塊共用同一個 MySQL 庫和同一個 Redis。原因很樸素一套微服務全家桶在單機上光啟動順序就能勸退一半人而本地版的定位是「開發(fā)者自己能跑通、能改、能交作業(yè)」單體聚合結構最容易達成這個目標。我一般會先看根目錄pom.xml或者build.gradle里的模塊劃分判斷后端是純單體還是多模塊。生鮮電商里「商品 訂單 庫存」強相關的域本地版剝掉消息隊列和分布式事務后用Transactional在單庫上把下單邏輯串起來反而是最穩(wěn)的方案。前端方面管理后臺用 Vue 2/3 或 React用戶端如果是 H5 就用同一套構建產(chǎn)物如果是小程序則另有一套。2.2 初始化數(shù)據(jù)庫建庫、導入、初始化賬號的最短路徑拿到源碼第一件事不是mvn spring-boot:run而是先確認 MySQL 的版本和初始化 SQL 在哪。生鮮項目里數(shù)據(jù)庫腳本常見命名是sql/目錄下的init.sql、data.sql或h2文件夾。我建議按下面這組命令把數(shù)據(jù)庫初始化干凈避免后面因為舊數(shù)據(jù)殘留導致賬號、庫存對不上。# 1. 啟動本地 MySQL 并登錄以 root 為例 mysql -uroot -p # 2. 建庫字符集用 utf8mb4生鮮項目里商品名和用戶備注都有 emoji CREATE DATABASE IF NOT EXISTS mumu_fresh DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci; # 3. 在 mysql 命令行內執(zhí)行導入注意先切庫 USE mumu_fresh; SOURCE /path/to/your/sql/init.sql; SOURCE /path/to/your/sql/data.sql; # 4. 驗證核心表數(shù)量生鮮項目至少該看到十張以上業(yè)務表 SHOW TABLES;這段命令的作用是「把demo.sql 與初始化腳本喂給新庫」。utf8mb4不是玄學生鮮商品描述里經(jīng)常有「新鮮·甜」這類帶特殊符號的文本utf8mb3 在部分版本上會報Incorrect string value錯誤。SOURCE路徑用絕對路徑最省事不要在 MySQL 里用相對路徑猜半天。導入完成后順手查一下user或member表有沒有內置賬號常見做法是預置一個13800000000 / 123456之類的測試手機號密碼在data.sql里是 BCrypt 密文別指望能用明文查出來。2.3 后端配置文件里必須改的 6 個參數(shù)數(shù)據(jù)庫初始化完下一步是打開application.yml或application-local.yml搜datasource、redis、server.port三個關鍵詞。生鮮項目的本地版默認配置經(jīng)常指向localhost:3306/mumu_fresh但密碼、端口、Redis 庫 index 這些往往需要人工對齊。下面這張表是我每次必查的參數(shù)清單按修改優(yōu)先級排列配置項常見默認值改完之后的作用spring.datasource.urljdbc:mysql://localhost:3306/mumu_fresh?useUnicodetruecharacterEncodingutf8數(shù)據(jù)庫地址和庫名不對啟動即報Communications link failurespring.datasource.username/passwordroot / root與本地 MySQL 不一致時啟動報Access deniedspring.redis.host/port/databaselocalhost / 6379 / 0Redis 連不上不影響啟動但登錄驗證碼、商品緩存會運行時異常server.port8080容易被本地其他服務占用后面避坑章節(jié)會細說spring.servlet.multipart.max-file-size10MB后臺傳商品圖超過限制直接 500生鮮商品大圖常見 38MB建議調到 50MBcustom.pay.mock-enabledtrue本地版模擬支付開關改成 false 會跳真實支付網(wǎng)關本地必翻車改完配置文件后后端啟動命令不用花哨直接在項目根目錄執(zhí)行# Maven 方式多模塊時先 -pl 指定模塊或直接到含 Application 類的模塊目錄 mvn spring-boot:run -Dspring-boot.run.profileslocal # 或者打完包再跑常用于確認依賴完整 mvn clean package -DskipTests java -jar target/mumu-fresh.jar --spring.profiles.activelocal這段里--spring.profiles.activelocal是很多新手忽略的參數(shù)本地版源碼通常自帶application-dev.yml、application-prod.yml如果你直接裸跑可能讀到 prod 配置去連遠程數(shù)據(jù)庫。啟動日志里看到Tomcat started on port(s): 8080只是第一步還不代表業(yè)務跑通了最好再執(zhí)行curl http://localhost:8080/api/health或直接登錄后臺確認配置真正生效。注意如果你的源碼是前后端分離的后端起不來時前端頁面空白屬于正?,F(xiàn)象先排查啟動日志里最后三行異常不要急著改前端。3. 拆一條生鮮主鏈路商品緩存、購物車、下單事務是怎么串起來的3.1 商品模塊的緩存策略本地版也值得保留 Redis 緩存層生鮮電商和普通商城最大的差別在商品模型上同一個 SKU 可能有「斤」「份」「盒」多種單位價格隨促銷活動浮動庫存字段還要區(qū)分「可售庫存」和「鎖定庫存」。因此商品接口普遍是兩層結構熱點分類和商品詳情放 Redis庫存和價格實時查 MySQL。本地版雖然流量不大但保留這個結構能讓你后續(xù)壓測時看到緩存命中率對接口響應時間的影響。以商品列表接口為例常見實現(xiàn)是「先讀緩存、未命中再查庫回填」public ListProductVO listCategoryProducts(Long categoryId) { String cacheKey fresh:category: categoryId; // 1. 先查緩存 String cached redisTemplate.opsForValue().get(cacheKey); if (StringUtils.hasText(cached)) { return JSON.parseArray(cached, ProductVO.class); } // 2. 緩存未命中查數(shù)據(jù)庫并回填 ListProductVO list productMapper.selectByCategoryId(categoryId); // 回填時加一個 5 分鐘過期生鮮商品價格變動頻率不高但促銷時會即時改價 redisTemplate.opsForValue().set(cacheKey, JSON.toJSONString(list), 5, TimeUnit.MINUTES); return list; }這段邏輯不復雜但有兩個參數(shù)值得解釋過期時間用 5 分鐘而不是 30 分鐘是因為生鮮品類早晚市會調價緩存太久會讓用戶看到「昨日價格」導致下單時價格校驗不一致另外fresh:category:這個 key 前綴是管理后臺改價時主動失效緩存用的如果項目里沒有對應的delete操作那你改完后臺價格后要等緩存過期才能在前端看到變化。管理后臺的改價接口里我一般習慣主動刪緩存而不是等它過期# 常見做法后臺更新商品后調用緩存刪除接口或工具清理 redis-cli KEYS fresh:category:* | xargs redis-cli DEL這只是個兜底操作實際源碼里會封裝成RedisService.deleteByPrefix()。生鮮項目里「商品改價 → 緩存清理 → C 端生效」這條鏈路是面試和答辯時最常被追問的點建議你本地跑通后親手試一次改價流程。3.2 下單接口的庫存扣減本地版怎么在不引入 MQ 的情況下防超賣下單是生鮮系統(tǒng)的核心也是最容易「翻車」的地方。本地版雖然沒有高并發(fā)流量但如果把庫存扣減寫成交互式「先查庫存、再 UPDATE」并發(fā)壓測時照樣能超賣。更穩(wěn)妥的方案是把庫存扣減放在一條 SQL 或一個 Redis Lua 腳本里完成保證原子性。我在本地驗證過一套精簡實現(xiàn)下單接口先校驗商品上下架和用戶狀態(tài)隨后調用庫存服務扣減最后生成訂單流水并發(fā)送模擬支付回調。核心庫存語句長這樣Transactional(rollbackFor Exception.class) public Long createOrder(CreateOrderRequest req) { // 1. 原子扣減庫存只有剩余庫存足夠時才扣成功 int updated stockMapper.deductStock(req.getSkuId(), req.getQuantity()); if (updated 0) { throw new BizException(ErrorCode.STOCK_NOT_ENOUGH); } // 2. 創(chuàng)建訂單主表和明細表 Order order buildOrderFromRequest(req); orderMapper.insert(order); orderItemMapper.insertBatch(order.getId(), req.getItems()); // 3. 返回訂單號等待模擬支付回調 return order.getId(); }deductStock對應的 SQL 是防超賣的關鍵常見寫法是帶條件的 UPDATEUPDATE sku_stock SET sold sold #{quantity}, version version 1 WHERE sku_id #{skuId} AND (total - sold) #{quantity}這段 SQL 的邏輯說明total - sold quantity是數(shù)據(jù)庫層的硬校驗比「先 SELECT 再 UPDATE」少一個競態(tài)窗口。updated 0時說明庫存不足直接拋異?;貪L整個事務訂單不會產(chǎn)生。這里有一個重要參數(shù)version字段是悲觀開發(fā)習慣保留的樂觀鎖位用于后臺人工調整庫存時避免互相覆蓋不是下單鏈路必須的但如果源碼表里有這個字段你寫別的更新 SQL 時最好帶上它。模擬支付是本地版的典型設計。真實生鮮項目里用戶支付后會收到微信/支付寶異步回調本地版沒有外網(wǎng)回調地址所以普遍做法是提供一個「模擬支付接口」訂單創(chuàng)建后前端帶著訂單號調一次后端直接把訂單狀態(tài)置為已支付并進入配送流程。# 模擬支付接口GET 或 POST 都可本地自測用 POST curl -X POST http://localhost:8080/api/pay/mock \ -H Content-Type: application/json \ -d {orderNo:202501011200001,payType:alipay,amount:36.50}生鮮項目里訂單狀態(tài)一般有待支付、已支付、備貨中、配送中、已完成、已取消。本地版把「備貨中」到「配送中」做成定時任務掃表模擬真實履約節(jié)奏。你調試時可手動改訂單狀態(tài)字段不建議頻繁跑定時任務等狀態(tài)推進。3.3 用戶端與后臺的數(shù)據(jù)約定JSON 字段命名不是隨便定的生鮮電商的 C 端和后臺共用一套后端接口但不同端對字段的訴求不同。C 端商品詳情需要sales銷量和freshtime上架時間后臺列表需要costPrice成本價和stockWarning預警值。同一個ProductVO里兩種角色字段都返回是很常見的這在本地版源碼里幾乎成慣例。理解這個約定比看懂代碼更重要你在本地改后端時如果只給 C 端加一個「同品類推薦」字段要保證后臺接口不報錯最佳做法是新增 DTO 而不是在現(xiàn)有 VO 上硬加。生鮮項目源碼里經(jīng)常出現(xiàn)AppProductDetailVO和AdminProductVO兩份類就是為這個原因拆的。注意如果你發(fā)現(xiàn)某個接口返回的 JSON 里缺少前端要的字段別急著給前端說「后端沒有」先去后端模塊搜同名字段常常是JsonIgnore注解把它藏在了管理端實體上本地聯(lián)調時改動實體要謹慎確認兩邊都不受影響再刪注解。4. 前端、管理后臺與本地接口聯(lián)調端口、跨域、Token 三個硬骨頭4.1 本地版前端代理的正確配法別把 API 地址寫死在代碼里生鮮項目源碼的前端部分常見有兩種形態(tài)一種是 Vue 工程里統(tǒng)一走.env.development的環(huán)境變量另一種是直接打成靜態(tài)文件放在后端resources/static下。后者的聯(lián)調最簡單——啟動后端后直接訪問http://localhost:8080即可前端請求走同源不存在跨域問題。前者則需要配置開發(fā)代理以 Vite 工程為例// vite.config.js export default defineConfig({ server: { port: 5173, proxy: { /api: { target: http://localhost:8080, changeOrigin: true, // 后端接口統(tǒng)一前綴是 /api前端不需要額外透傳 rewrite: (path) path.replace(/^\/api/, ) } } } })這個配置解決的是「前端 5173 端口頁面調后端 8080 接口」的跨域問題。changeOrigin: true是關鍵它讓后端收到的 Host 頭來自localhost:8080否則有些后端框架基于 Host 做校驗時會拒絕請求。rewrite那段要看后端實際的RequestMapping有沒有/api前綴如果后端 Controller 自帶/api/fresh/goods那 rewrite 就要去掉否則會變成二次拼接導致 404。管理后臺的端口一般和用戶端不同常見做法是 5173 跑 C 端、5174 跑后臺或者讓后臺和后端直接同源。如果你本地起后臺時發(fā)現(xiàn)登錄頁請求全部 404先按這個順序排查瀏覽器 F12 看 Network 里請求 URL 的端口 → 看是 5174 還是 8080 → 回vite.config.js檢查 target 是否指對了端口。4.2 Token 認證本地怎么處理登錄態(tài)和權限控制的最小閉環(huán)生鮮項目里 C 端用戶和管理員是兩套認證體系。C 端走手機號 短信驗證碼本地版用萬能驗證碼123456繞過短信通道后臺走賬號密碼。Token 用 JWT 是主流做法登錄成功后前端把 token 存到localStorage請求攔截器統(tǒng)一附帶Authorization: Bearer token。本地調試時最常遇到的認證問題是「后臺頁面一直跳登錄頁」。原因幾乎都是 token 過期時間太短生鮮項目源碼默認設置常見是 720 分鐘但有的版本只給了 30 分鐘。調試階段我一般直接改配置# application-local.yml custom: jwt: # 調成 7 天避免聯(lián)調一上午就要重新登錄三次 expire-minutes: 10080 admin-expire-minutes: 10080這個參數(shù)屬于本地聯(lián)調優(yōu)化配置不是源碼里默認放出來的甚至custom.jwt前綴本身都可能是jwt.secret加jwt.ttl的組合。你搜expire或Token關鍵字定位到配置類后把過期時間調大即可。別忘了同時檢查 Redis 里是否存在token:blacklist之類的登出邏輯如果存在改配置后要重啟一次后端和 Redis否則舊 token 還在黑名單里。4.3 本地版靜態(tài)文件與圖片上傳商品圖裂了十有八九是路徑問題生鮮項目的圖片處理是本地上手第二常見的問題。后臺添加商品時上傳圖片默認存本地磁盤的/upload/文件夾但前端訪問的是http://localhost:8080/files/xxx.jpg。如果源碼里沒有把/upload/**映射成靜態(tài)資源或者路徑里帶file://前端圖片就會裂。常見做法是加一個 WebMvc 配置Configuration public class WebMvcConfig implements WebMvcConfigurer { Override public void addResourceHandlers(ResourceHandlerRegistry registry) { // 把本地磁盤物理路徑映射到 /files/** 訪問 registry.addResourceHandler(/files/**) .addResourceMapping(/files/**) .addResourceLocations(file: uploadPath /); } }這段配置里file:前綴必須保留前面我有一次寫成classpath:把圖片丟到 jar 包內部結果項目一重啟圖片就全部丟失。商品圖還好生鮮項目的營業(yè)執(zhí)照、身份證上傳件丟失就麻煩多了走本地測試時建議單獨建一個data/uploads目錄不要和源碼目錄混在一起方便打包時排除。注意本地版經(jīng)常把上傳路徑寫死在配置文件的絕對路徑如/Users/yourname/uploads這個路徑一旦別人拷走源碼就會失效。你拿到本地版若是圖片上傳后報FileNotFoundException第一反應應該是改上傳根路徑而不是去看業(yè)務代碼。5. 慕慕生鮮本地版高頻避坑5 條血淚經(jīng)驗與排查套路5.1 啟動報「端口被占用」但 netstat 找不到是哪個進程現(xiàn)象mvn spring-boot:run啟動到一半報Port 8080 was already in use執(zhí)行netstat -ano | grep 8080卻看不到占用進程或者看到的是宿主機某個 PID 但用任務管理器還殺不掉。原因多數(shù)情況是上一次啟動的后端進程沒有真正退出特別是 IDE 里點紅色停止按鈕時Spring Boot 的子進程比如內嵌 Tomcat可能還掛在后臺。另一種隱蔽情況是 Docker 里裝了 Nacos 或 MySQL 映射了 8080 端口netstat未必直接顯示。解決先用lsof -i :8080看完整進程如果是 java 進程就kill -9如果發(fā)現(xiàn)是 Docker 端口映射直接改本地server.port換成 8081 更快。我的習慣是本地調試端口統(tǒng)一走 8081避開系統(tǒng)上各種代理工具默認的 8080省得每次翻車都去排查占用來源。5.2 數(shù)據(jù)庫連接正常但所有查詢都報「Table doesnt exist」現(xiàn)象啟動正常登錄接口返回 500日志里報Table mumu_fresh.sys_user doesnt exist但你明明導入了 SQL。原因初始化腳本里可能帶了DROP TABLE IF EXISTS而data.sql里的表名和你導入的庫名大小寫不一致。Linux 上 MySQL 表名區(qū)分大小寫Windows 不區(qū)分本機是 macOS 也區(qū)分。更常見的原因是把init.sql導入到了另一個庫比如項目默認連mumu_fresh你卻在mumu_fresh_test里導了數(shù)據(jù)。解決檢查application-local.yml里jdbc:mysql://localhost:3306/后面的庫名再進 MySQL 執(zhí)行SHOW TABLES FROM 對應庫名確認sys_user或user表存在。如果表名確實對不上重新導入一次初始化腳本導入時先USE數(shù)據(jù)庫名。生鮮項目存在多套 SQL 的情況不少init.sql和data.sql都要導只導一個就會出現(xiàn)「管理端能登錄但首頁數(shù)據(jù)全部空白」的怪問題。5.3 Redis 緩存里的值是亂碼登錄驗證碼接口能用但商品詳情反序列化報錯現(xiàn)象前后端都起來了驗證碼能正常顯示但商品列表接口報JSON parse error: Cannot deserialize instance of ... out of START_ARRAY token。原因本地版源碼里如果 RedisTemplate 沒顯式指定Jackson2JsonRedisSerializer或GenericJackson2JsonRedisSerializer默認 JDK 序列化會把對象存成帶類名的一長串二進制。如果改密碼時后端寫緩存用的序列化器和讀緩存時用的不一致讀出來的字符串前面會多一串\xAC\xED之類的字節(jié)JSON 直接解析失敗。解決找到 RedisTemplate 的配置類統(tǒng)一設置序列化器Bean public RedisTemplateString, Object redisTemplate(RedisConnectionFactory factory) { RedisTemplateString, Object template new RedisTemplate(); template.setConnectionFactory(factory); // key 用字符串序列化可讀性好方便 redis-cli 排查 template.setKeySerializer(new StringRedisSerializer()); // value 用 JSON 序列化跨語言友好避免 JDK 序列化亂碼 template.setValueSerializer(new GenericJackson2JsonRedisSerializer()); // hash 結構同樣設置生鮮項目購物車常用 hash template.setHashKeySerializer(new StringRedisSerializer()); template.setHashValueSerializer(new GenericJackson2JsonRedisSerializer()); template.afterPropertiesSet(); return template; }設置完成后把 Redis 里舊 key 清掉否則老數(shù)據(jù)會被新反序列化器再坑一次。執(zhí)行redis-cli FLUSHDB只清緩存庫不影響 MySQL本地調試放心用。這個坑在「驗證碼能用但詳情頁崩」的場景里極具迷惑性我把它列為本地版最值得提前預防的序列化問題。5.4 下單后訂單狀態(tài)停在「待支付」模擬支付接口卻查不到訂單現(xiàn)象用戶端能下單訂單列表能看到待支付訂單但調用模擬支付接口后提示「訂單不存在」或「訂單狀態(tài)不可支付」。原因這套生鮮源碼里 C 端用戶的下單訂單和后臺看到的訂單可能不在同一張表常見設計是order表與order_manage視圖分離另一種可能是訂單號字段在模擬支付接口里用的是orderNo但真正落庫的字段叫outTradeNo或orderSn兩邊字段名對不上導致按訂單號查不到。解決先拿前端下單接口的返回 JSON看訂單號在里面對應的字段名是什么再去后端搜模擬支付接口的入?yún)ο蠖x。生鮮項目源碼本地版經(jīng)常把mockPay接口做成獨立的 Controller方法簽名里不一定是orderNo搜MockPayController或mockPay關鍵字直接定位。字段名不匹配時在后端做個兼容處理接收orderNo和orderSn兩個入?yún)⑷∪我环强罩等ゲ橛唵?。這個改動不影響線上邏輯純本地調試友好。5.5 凌晨定時任務把訂單全部變成「已取消」一覺醒來數(shù)據(jù)沒了現(xiàn)象頭天晚上下單測試第二天打開后臺發(fā)現(xiàn)所有「已支付」訂單變成了「已取消」而庫存也回補了。原因生鮮項目源碼里定時任務常寫「每分鐘掃描一次未支付訂單超時 30 分鐘自動取消」。本地版的模擬支付延遲到第二天操作時訂單超時被你本地電腦的睡眠時間覆蓋掉了。常見定時任務參數(shù)在Scheduled(cron 0 */1 * * * ?)配合order.expire.minutes30里改成 0 可以臨時關閉超時取消。解決本地測試期間把超時時間調成 1440 分鐘或者直接注釋掉OrderTimeoutCancelTask的Scheduled注解。但注意生鮮項目的定時任務不止這一個還有自動確認收貨、庫存預警等任務改之前先看類名是否清晰別誤關核心任務。我的習慣是本地開發(fā)一律把任務類掃描路徑排除等真正要測定時任務效果時再手動調用一次任務里的execute()方法用手動觸發(fā)代替真實調度排查問題更快。6. 用 JMeter 壓測一個本地下單接口驗證并發(fā)扣庫存到底穩(wěn)不穩(wěn)本地版跑通之后最值得做的驗證不是「頁面能不能打開」而是「這個訂單主鏈路在并發(fā)下會不會出問題」。生鮮電商的高頻場景很集中首頁流量打到商品查詢、營銷活動流量打到下單。前者有緩存扛著后者的庫存扣減 SQL 是真實考驗。我習慣用 JMeter 跑一個 100 并發(fā) × 50 次的「下單 模擬支付」腳本看庫存是否對得上。JMeter 里建一個線程組線程數(shù)設 100Ramp-Up Period 設 5 秒循環(huán)次數(shù) 50。添加 HTTP 請求到http://localhost:8080/api/order/create請求體是 JSON記得加一個 HTTP Header Manager 放Authorization: Bearer token。下單接口的響應里拿到訂單號后再用正則表達式提取器把訂單號喂給下一個模擬支付請求形成一條完整鏈路。跑完之后判斷結果的指標有三個Error %是否為 0p95響應時間是否在 500ms 內最后一個關鍵步驟是查數(shù)據(jù)庫-- 下單前置庫存為 5000壓測總請求 5000 次成功 5000 單 -- 檢查庫存表的 sold 字段是否剛好等于 5000 SELECT sku_id, total, sold, (total - sold) AS remaining FROM sku_stock WHERE sku_id 10086; -- 順便核對訂單表的已支付數(shù)量和 sold 保持同步 SELECT COUNT(*) AS paid_count FROM order WHERE sku_id 10086 AND status PAID;如果sold remaining ! total說明庫存扣減存在并發(fā)問題回去檢查你在deductStock的 UPDATE 語句里是不是少了(total - sold) #{quantity}這個條件。如果壓測時出現(xiàn)「庫存足夠但下單失敗」的報錯那是updated 0判斷太嚴格檢查事務隔離級別下是否有行鎖競爭超時。生鮮項目源碼的本地版如果默認帶sold字段進緩存壓測時先清 Redis 里的商品庫存緩存避免緩存里的庫存數(shù)和數(shù)據(jù)庫對不上。一個更細的技巧是持續(xù)觀察壓測中的數(shù)據(jù)庫連接數(shù)。生鮮項目源碼里HikariCP連接池默認配置常見是maximum-pool-size: 10100 并發(fā)進來時連接池會排隊這時的 p95 會飆升但不至于報錯。如果你想模擬更真實的線上表現(xiàn)把maximum-pool-size調到 20、connection-timeout調到 3000ms然后再壓一次對比數(shù)據(jù)你能直觀看到連接池參數(shù)對吞吐量的影響這也是面試時能拿得出手的本地驗證數(shù)據(jù)。最后補一句我的習慣性動作每次壓測前把 MySQL 和 Redis 都做一次快照或者FLUSHDB 重新導入data.sql保證壓測結果不被上次的臟數(shù)據(jù)污染。生鮮項目的庫存金額直接影響訂單金額臟數(shù)據(jù)會讓報表數(shù)字對不上排查成本遠高于重新初始化 30 秒的成本。這些坑我基本都踩過一遍按這套順序跑下來你拿到手的任何一套生鮮本地版源碼都能在半天內從黑匣子變成可控工程。希望幫到你。本文還有配套的精品資源點擊獲取