:從部署到核心模塊與避坑指南)
前陣子幫人調(diào)試一套基于Springboot的仿淘寶購物管理系統(tǒng)說句實話這類項目在很多平臺上一搜一大把但真正拿到源碼能順利跑起來、看完文檔能搞清楚業(yè)務(wù)邏輯的還真不多見。今天借著這套系統(tǒng)把我從導入項目到二次開發(fā)過程中遇到的坑、梳理出來的設(shè)計思路、以及代碼里值得反復(fù)揣摩的核心部分一次性整理清楚。如果你正在做課程設(shè)計、畢業(yè)設(shè)計或者單純想找一套電商項目來實操Springboot這篇文章應(yīng)該能讓你少走不少彎路。這套系統(tǒng)的定位很明確模仿主流電商平臺的購物流程把用戶、商品、購物車、訂單、支付、收貨、評價這一條完整鏈路串起來。它不是那種只有一個CRUD的空殼項目而是把權(quán)限、事務(wù)、并發(fā)、文件存儲這些實際開發(fā)中繞不開的點都塞了進去所以拿來練手或者二次開發(fā)都很合適。1. 項目概覽為什么這類購物系統(tǒng)這么適合練手1.1 電商項目覆蓋的技術(shù)面足夠廣我在帶新人或者幫人看項目時一直建議優(yōu)先選擇電商類項目作為Springboot練手目標。原因很簡單一個完整的購物系統(tǒng)天然就能拆出多個模塊每個模塊都能對應(yīng)到不同的技術(shù)難點。用戶模塊要處理注冊、登錄、權(quán)限驗證這涉及密碼加密和JWT鑒權(quán)商品模塊要處理分類、搜索、圖片上傳這涉及文件存儲和條件查詢購物車模塊要維護用戶和商品的關(guān)系需要考慮合并、數(shù)量修改訂單模塊最麻煩既要保證事務(wù)一致性又要處理庫存扣減的并發(fā)問題支付模塊雖然通常對接模擬支付但回調(diào)通知、訂單狀態(tài)流轉(zhuǎn)這些邏輯一點都不能少。這套基于Springboot的淘寶購物管理系統(tǒng)表面上看是一個麻雀雖小五臟俱全的商城實際上它把Springboot、MyBatis-Plus、Redis、Minio、JWT這些常用組件的整合方式都演示了一遍。對于初學者來說跟著源碼把這些模塊逐個過一遍比看十遍理論都管用。1.2 角色與核心功能矩陣系統(tǒng)里分了三種角色買家、賣家和管理員。有些項目會把賣家和管理員合并但這套系統(tǒng)是分開的權(quán)限粒度更清晰。角色核心功能涉及模塊游客瀏覽商品、搜索商品、查看商品詳情商品模塊買家登錄注冊、管理購物車、下單、支付、收貨、評價、查看訂單用戶、購物車、訂單、支付、評價賣家管理自家商品、處理訂單發(fā)貨、查看銷售情況商品、訂單管理員管理用戶、審核商品、管理分類、數(shù)據(jù)統(tǒng)計管理端三個角色對應(yīng)的是三套不同的接口權(quán)限這也是很多同學拿到源碼后最容易疑惑的地方為什么同一個接口不同角色調(diào)用返回的結(jié)果不一樣其實就是在攔截器里做了角色判斷后面我會把這塊的代碼邏輯拆開講。1.3 從瀏覽到收貨的完整業(yè)務(wù)閉環(huán)拿一次完整的購物流程舉例游客在前臺頁面看到商品列表點擊進詳情頁如果想下單需要先注冊并登錄登錄后把商品加入購物車然后在購物車里勾選要結(jié)算的商品生成訂單。訂單生成時系統(tǒng)會扣減庫存同時開啟支付倒計時用戶支付成功后才算真正下單完成。賣家在后臺看到新訂單執(zhí)行發(fā)貨操作買家收到貨后確認收貨再對商品進行評價。整個閉環(huán)里涉及的所有狀態(tài)變化這套系統(tǒng)都用數(shù)據(jù)庫字段和狀態(tài)更新記錄下來了。理解這個閉環(huán)很重要因為后面所有模塊的代碼都是圍繞這條鏈路展開的。我在給文檔寫說明時也是按照這個流程來分章節(jié)而不是單純按代碼目錄結(jié)構(gòu)來講。2. 技術(shù)選型與工程結(jié)構(gòu)每一步都要有理由2.1 技術(shù)棧清單及選型理由這套系統(tǒng)的技術(shù)棧是典型的Springboot全家桶組合我列個表把每個組件解決什么問題寫清楚。技術(shù)組件版本建議解決的問題Spring Boot2.7.x提供自動配置和快速啟動降低整合成本MyBatis-Plus3.5.x簡化單表CRUD提供分頁插件和條件構(gòu)造器MySQL8.0存儲業(yè)務(wù)數(shù)據(jù)支持事務(wù)和復(fù)雜查詢Redis6.x / 7.x緩存驗證碼、商品詳情、購物車數(shù)據(jù)也能做分布式鎖Minio8.x商品圖片、頭像等文件的對象存儲服務(wù)JWT Spring Interceptor-無狀態(tài)登錄鑒權(quán)區(qū)分角色權(quán)限有人會問為什么不用Spring Data JPA我的觀點是電商項目的查詢條件往往是動態(tài)拼接的比如商品列表要按照價格區(qū)間、分類、關(guān)鍵字過濾MyBatis-Plus的條件構(gòu)造器寫起來比JPA的Specification直觀得多而且很多老項目的Mapper XML可以直接遷移復(fù)用。另外MyBatis-Plus對分頁、邏輯刪除、自動填充都有現(xiàn)成支持能省不少代碼量。Redis在這個項目里承擔的是性能加速器角色。商品詳情頁的點擊量最高如果每次請求都打數(shù)據(jù)庫壓力會很大所以項目里把熱門商品的詳情緩存到了Redis并設(shè)置了過期時間。購物車數(shù)據(jù)也存在Redis里用Hash結(jié)構(gòu)存儲key是用戶IDfield是商品IDvalue是商品數(shù)量這樣查詢購物車就很輕量。2.2 后端工程目錄是怎么拆的我拿到源碼后第一件事就是看目錄結(jié)構(gòu)。這套項目的結(jié)構(gòu)很標準但有一點值得拿出來說它在controller層和service層之間加入了一個dto包和一個vo包。com.example.mall ├── controller // 接口層只做參數(shù)接收和結(jié)果封裝 ├── service // 業(yè)務(wù)層事務(wù)控制在這里 │ └── impl ├── mapper // MyBatis-Plus的Mapper接口對應(yīng)數(shù)據(jù)庫操作 ├── entity // 數(shù)據(jù)庫實體類跟表結(jié)構(gòu)一一對應(yīng) ├── dto // 接收前端參數(shù)的傳輸對象 ├── vo // 返回給前端的視圖對象 ├── config // 配置類比如Redis、Minio、攔截器配置 ├── interceptor // 登錄鑒權(quán)攔截器 ├── common // 統(tǒng)一結(jié)果封裝、異常處理、工具類 └── ...很多課程設(shè)計項目喜歡把所有類都堆在controller和service兩層短期內(nèi)看著簡單一旦加功能就會亂。這套系統(tǒng)的dto和vo是分開的這一點很關(guān)鍵。前端傳過來的參數(shù)用dto接收返回給前端的數(shù)據(jù)用vo封裝避免把數(shù)據(jù)庫實體直接暴露出去。比如用戶對象里有密碼字段如果直接把entity返回給前端密碼就泄露了。用vo只返回id、nickname、avatar這些字段安全性會好很多。2.3 數(shù)據(jù)庫設(shè)計核心表關(guān)系一覽數(shù)據(jù)庫一共十幾張表核心的幾張是tb_user、tb_category、tb_product、tb_cart、tb_order、tb_order_item、tb_address、tb_evaluation。表之間關(guān)系并不復(fù)雜但有兩張表的設(shè)計值得特別說明tb_order和tb_order_item是一對多拆開的。訂單主表存收貨地址、總金額、狀態(tài)等概要信息訂單商品表存每個商品的快照信息。訂單商品表必須冗余一份商品名稱、商品主圖、單價作為快照不能直接關(guān)聯(lián)商品表。為什么因為用戶下單之后賣家可能修改商品價格或者下架商品如果訂單詳情還要實時去查商品表用戶看到的價格和下單時就會對不上。這里冗余字段屬于用空間換正確性。另外這個項目統(tǒng)一使用了邏輯刪除所有表都有deleted字段用MyBatis-Plus的TableLogic注解處理。對于一個購物系統(tǒng)用戶誤刪地址、賣家誤刪商品都是常見操作邏輯刪除給數(shù)據(jù)恢復(fù)留了后路這也是很多公司生產(chǎn)環(huán)境的通用做法。3. 核心模塊的實現(xiàn)要點不只是增刪改查3.1 用戶登錄與JWT鑒權(quán)的落地姿勢登錄這塊項目用的是JWT作為令牌整個流程可以簡化成三步用戶輸入賬號密碼后端校驗通過后生成Token返回前端前端把Token存在本地之后每次請求都在請求頭里帶上Authorization后端寫一個攔截器攔截所有需要登錄的接口從Token中解析用戶ID和角色放行或拒絕。代碼層面的關(guān)鍵點是攔截器的注冊方式。項目里有一個WebMvcConfig實現(xiàn)了WebMvcConfigurer重寫addInterceptors方法把自定義的LoginInterceptor注冊進去并且設(shè)置excludePathPatterns把登錄、注冊、商品瀏覽這些接口放行。這里有個新手經(jīng)常踩的坑放行路徑寫錯導致登錄接口也被攔截結(jié)果前端登錄請求拿著用戶名密碼卻進不來排查半天不知道問題在哪。如果你自己調(diào)試記得先確認excludePathPatterns里的路徑跟Controller里的RequestMapping前綴完全一致。Token解析時項目用JWT的SecretKey來簽名和驗簽。需要注意的是JWT本身是不加密的里面不要放敏感信息我見過有人在Token里直接塞用戶手機號雖然能用但一旦Token被人拿到信息就泄露了。這個項目只在Token里放了用戶ID和角色編碼這是比較穩(wěn)妥的做法。3.2 商品模塊與Minio文件存儲的整合商品模塊涉及分類、品牌、上下架狀態(tài)、多圖展示。這里我想重點聊聊圖片上傳。項目用Minio做對象存儲而不是直接把圖片存到本地磁盤。原因很直接本地存儲的圖片不好管理、不好遷移而且生產(chǎn)環(huán)境部署時服務(wù)器磁盤空間有限。Minio本身是開源的對象存儲服務(wù)跟阿里云OSS這類云服務(wù)在API使用上很接近你學會接Minio后面換云存儲時改動成本很低。文件上傳的流程是這樣的前端請求上傳接口后端接收MultipartFile生成新的對象名稱通常是UUID文件擴展名調(diào)用Minio客戶端把文件流上傳到指定桶最后返回文件的訪問URL。這個URL會被存到商品表的image字段和商品圖片表里。我特別提一下文件名生成很多同學習慣直接用原始文件名如果兩個人上傳同一個名字的文件后一個會覆蓋前一個。所以用UUID或者日期隨機數(shù)重命名是必須的。Minio的配置也很簡單核心就是四個參數(shù)endpoint、accessKey、secretKey、bucketName。我在實際跑這個項目時本地直接下載了Minio客戶端開啟一個9000端口的服務(wù)然后配置好賬號密碼基本就通了。如果在云服務(wù)器上部署記得把Minio的安全組和防火墻端口打開不然圖片URL會一直加載不出來。3.3 購物車與生成訂單時的事務(wù)控制購物車在Redis里的實現(xiàn)前面提過但我在這里要強調(diào)一下購物車數(shù)據(jù)從Redis轉(zhuǎn)成訂單時的處理。用戶勾選購物車商品點擊去結(jié)算時后端得一次性接收多個商品ID到Redis里取出對應(yīng)的商品信息然后查數(shù)據(jù)庫拿到最新價格計算總金額生成訂單主記錄和訂單明細記錄同時扣減庫存。這個操作牽扯到多張表的寫入必須加Transactional事務(wù)注解。不加或者加錯位置就會出大問題。比如訂單生成成功了但庫存沒扣減或者扣了庫存但訂單沒生成這兩種情況在并發(fā)用戶同時下單時會立刻暴露。事務(wù)注解要加到service層實現(xiàn)類的方法上不要加到controller方法上也不要在service方法內(nèi)部自調(diào)用。這兩條后面我會在踩坑章節(jié)單獨展開因為真的太多人在這兩個地方栽過跟頭。4. 訂單狀態(tài)機與并發(fā)兜底項目里最值得研究的部分4.1 訂單狀態(tài)機的設(shè)計訂單模塊是整個購物系統(tǒng)的心臟因為訂單狀態(tài)不是隨便改的每個狀態(tài)之間的流轉(zhuǎn)都有明確條件。這套系統(tǒng)里訂單狀態(tài)用一個status字段表示狀態(tài)編碼含義下一步操作0待支付用戶支付或超時自動取消1待發(fā)貨賣家發(fā)貨2待收貨買家確認收貨3已完成買家可評價4已取消無后續(xù)操作5退款中賣家處理退款狀態(tài)流轉(zhuǎn)有一條鐵律除非特殊業(yè)務(wù)否則不允許跨狀態(tài)跳轉(zhuǎn)。比如一筆待支付訂單不能直接變成已完成。在代碼里項目通過在updateOrderStatus方法里傳oldStatus和newStatus用更新語句的WHERE條件帶上前置狀態(tài)來實現(xiàn)狀態(tài)的受控流轉(zhuǎn)。這個做法叫樂觀鎖在狀態(tài)更新上的應(yīng)用核心SQL是這樣的UPDATE tb_order SET status #{newStatus}, update_time NOW() WHERE id #{orderId} AND status #{oldStatus}當兩個請求同時試圖更新同一筆訂單時只有一個請求能被成功執(zhí)行另一個影響行數(shù)為0業(yè)務(wù)代碼根據(jù)影響行數(shù)判斷是否流轉(zhuǎn)失敗。相比先SELECT再UPDATE的方式這種方式避免了并發(fā)狀態(tài)下讀取到的舊數(shù)據(jù)被覆蓋的問題。4.2 庫存扣減樂觀鎖加唯一索引雙重兜底秒殺場景下庫存超賣是經(jīng)典的并發(fā)問題。這個項目雖然沒做秒殺但庫存扣減的邏輯已經(jīng)考慮了并發(fā)情況。正??蹘齑娴拇a不能是先查庫存數(shù)量夠再更新這種兩步走因為在高并發(fā)下兩個請求同時查到庫存還剩1都判定可以購買最后執(zhí)行更新時庫存就會變成負數(shù)。項目里用的是帶條件的更新boolean success inventoryService.deductStock(productId, quantity); // 實際SQL: UPDATE tb_product SET stock stock - #{quantity} // WHERE id #{productId} AND stock #{quantity}stock #{quantity}這個條件非常關(guān)鍵。它讓數(shù)據(jù)庫在更新時自行判斷庫存是否足夠如果不夠更新操作影響的行數(shù)就是0業(yè)務(wù)層捕獲到這個結(jié)果直接提示用戶庫存不足。配合tb_order_item表上的order_id product_id唯一索引還能防止同一用戶在同一訂單里重復(fù)添加同一商品導致的數(shù)據(jù)混亂。我之前幫人從源碼里找bug發(fā)現(xiàn)他把庫存扣減寫成了UPDATE tb_product SET stock stock - #{quantity} WHERE id #{productId}少了庫存判斷條件并發(fā)測試一打就超賣。這個案例我印象很深因為代碼就差一個條件線上出事故就可能是從這種細節(jié)開始的。4.3 超時未支付自動取消的兩種實現(xiàn)訂單生成后通常要設(shè)置一個支付時限比如30分鐘。這個項目里給了兩種實現(xiàn)思路文檔里也寫了對比第一種是定時掃描。寫一個Scheduled注解的定時任務(wù)每隔一分鐘掃描一次訂單表把狀態(tài)為待支付且order_time超過30分鐘的訂單批量更新為已取消。這種方式實現(xiàn)簡單但會有延遲最壞情況下訂單被取消的時間會晚一分鐘而且掃表全量數(shù)據(jù)時如果訂單量大對數(shù)據(jù)庫壓力不小。第二種是延遲消息。用Redis的過期鍵監(jiān)聽或者消息隊列的延遲隊列來實現(xiàn)到期通知精度更高但實現(xiàn)復(fù)雜度明顯上升。對于課程設(shè)計和中小型項目定時掃描完全夠用。這個項目源碼里默認用的是定時掃描你在部署的時候稍微注意一下定時任務(wù)的開關(guān)配置就行別把整個任務(wù)類給注釋掉不然超時訂單永遠取消不了。5. 源碼和文檔拿到手怎么才能用起來5.1 項目導入三步走很多人拿到源碼后第一步就卡住了因為導入項目的細節(jié)沒搞明白。這套系統(tǒng)我建議按下面三個步驟來操作第一步準備環(huán)境。裝好JDK 1.8或11、Maven 3.6、MySQL 8.0、Redis另外要有一個可用的Minio服務(wù)。MySQL執(zhí)行項目里提供的mall.sql腳本把數(shù)據(jù)庫和初始數(shù)據(jù)建好。Redis和Minio在本地起默認服務(wù)就行。第二步改配置。打開application.yml按自己的實際環(huán)境修改數(shù)據(jù)源地址、Redis地址、Minio的endpoint和密鑰。這里最容易踩的坑是數(shù)據(jù)庫時區(qū)問題建議在數(shù)據(jù)庫連接參數(shù)里加上serverTimezoneAsia/Shanghai不然日期字段會差8個小時表現(xiàn)為訂單創(chuàng)建時間和實際時間對不上。第三步啟動項目。先啟動Minio再啟動Redis然后是Spring Boot應(yīng)用。后端起來后用接口文檔或前端頁面的登錄接口試一下能拿到Token基本就說明環(huán)境和代碼都通了。如果項目里有前端頁面通常是Vue寫的一個簡單管理頁npm install之后跑npm run dev或者打包放進Springboot的static目錄看項目自帶文檔里的說明別自己瞎猜路徑。5.2 源碼結(jié)構(gòu)哪些能直接復(fù)用哪些要改這套系統(tǒng)的源碼目錄我前面已經(jīng)大致介紹過這里再補充一下哪些部分能直接當工具代碼用。common包里的Result統(tǒng)一結(jié)果集、GlobalExceptionHandler全局異常處理以及JwtUtils工具類基本是可以直接搬到其他Springboot項目里的這三個文件寫得很規(guī)整沒什么項目耦合。config包里的MyBatisPlusConfig配置了分頁插件RedisConfig配置了RedisTemplate的序列化方式。我特別提醒一下RedisTemplate的序列化很多項目默認用JDK序列化導致在Redis可視化工具里看到一堆轉(zhuǎn)義字符不方便排查。這個項目把Key設(shè)成了String序列化Value設(shè)成了Jackson序列化整個體驗會清爽很多這個配置建議你也沿用。要改的地方主要在業(yè)務(wù)包。entity里的表字段如果和你的需求對不上記得先改數(shù)據(jù)庫表再改實體類保證字段名能映射上。另外dto層的參數(shù)校驗注解比如NotBlank、Email也會因為前端傳參格式不同而需要調(diào)整。5.3 文檔里真正值錢的幾頁很多人不看文檔其實這套項目自帶的文檔里有幾個部分比源碼還值錢。首先是數(shù)據(jù)庫設(shè)計文檔它會畫一張ER圖并寫明每張表每個字段的含義這能幫你快速理解為什么訂單表要冗余商品快照、為什么地址表要保留省市區(qū)多級字段。其次是接口文檔里面把每個接口的請求參數(shù)、響應(yīng)示例、狀態(tài)碼都列出來了前后端聯(lián)調(diào)時直接照著文檔對就行。還有一個容易被忽略的部分是運行部署說明里面包含了一些奇怪的坑。比如有個細節(jié)Minio的bucket在創(chuàng)建時如果沒做公開訪問策略上傳成功后的圖片URL即便存在數(shù)據(jù)庫里也是訪問不了的因為請求沒有簽名。文檔里專門寫了一句話讓你執(zhí)行一條mc policy set public命令或者通過控制臺設(shè)置桶策略。我當時就是沒看這頁卡了半小時最后翻文檔才發(fā)現(xiàn)。6. 真實踩坑記錄這些坑你大概率也會遇到6.1 JSON序列化循環(huán)引用導致的請求超時在開發(fā)商品評價功能時我遇到過一個現(xiàn)象前端請求某個接口等了好一會兒才返回有時候直接超時。剛開始我以為是數(shù)據(jù)庫慢后來查日志發(fā)現(xiàn)是JSON序列化耗時太長。原因出在實體類雙向關(guān)聯(lián)上商品的Vo里關(guān)聯(lián)了評價列表評價的Vo里又關(guān)聯(lián)了商品信息序列化時兩個對象互相引用Jackson來回解析就卡住了。解決辦法有兩個方向。一是在字段上加JsonIgnore避免雙向暴露二是用JsonIgnoreProperties在引用端忽略對方字段。這套項目里很多Vo對象已經(jīng)做了隔離但如果你在二次開發(fā)時新增了關(guān)聯(lián)字段一定要留意這個坑。我建議所有返回給前端的對象關(guān)聯(lián)關(guān)系最多只展開一層不要圖省事把整個對象圖都序列化出去否則接口性能會越來越差。6.2Transactional自調(diào)用導致事務(wù)不回滾這是Spring事務(wù)里最經(jīng)典的一個坑。在一個Service里方法A調(diào)用了同類里的方法B方法B加了Transactional但方法A沒有加你猜B的事務(wù)生效嗎答案是不生效。因為Spring事務(wù)是通過AOP代理實現(xiàn)的同類內(nèi)部直接調(diào)用拿到的是原始對象不是代理對象事務(wù)注解就被繞過了。我在這套系統(tǒng)的訂單模塊里就發(fā)現(xiàn)過這樣的寫法createOrder方法內(nèi)部調(diào)用了deductStock方法deductStock上標了事務(wù)但createOrder沒有標。結(jié)果訂單插入成功后庫存扣減失敗了整筆數(shù)據(jù)就是錯的。正確的做法是把事務(wù)注解加在createOrder上讓整個方法體變成一個事務(wù)或者把deductStock挪到另一個Service類里通過注入的Bean來調(diào)用。檢查你手上的源碼時多留意這類自調(diào)用。6.3 性能優(yōu)化緩存預(yù)熱與頁面靜態(tài)化項目跑通之后如果想做性能優(yōu)化有兩個性價比很高的方向。一是商品熱門數(shù)據(jù)的緩存預(yù)熱可以在項目啟動時把數(shù)據(jù)庫里點擊量最高的前幾十個商品加載到Redis避免第一個訪問用戶直接打到數(shù)據(jù)庫。二是用定時統(tǒng)計替代實時統(tǒng)計比如商品銷量這類數(shù)值沒必要每次下單都更新商品表的sales字段可以定時把訂單表聚合出來的數(shù)據(jù)回填到商品表減少對商品主表的頻繁更新。還有一個可以優(yōu)化的點是圖片懶加載和壓縮。Minio里存的原圖可能很大前端展示列表頁時會造成流量壓力??梢杂肕inio的圖片縮放功能生成縮略圖列表頁加載縮略圖詳情頁加載原圖。這個方案不需要改太多代碼只需要在返回URL時拼上壓縮參數(shù)實測效果很明顯。6.4 前后端聯(lián)調(diào)時的跨域處理如果前端是獨立端口運行的Vue項目跨域問題跑不掉。這套系統(tǒng)的后端已經(jīng)寫了一個CorsConfig里面定義了允許的源IP、請求頭和方法。你需要檢查allowedOriginPatterns是否包含你前端的地址比如http://localhost:5173。如果前端請求是攜帶Token的還要允許Authorization請求頭否則預(yù)檢請求直接不過。我在第一次運行這套系統(tǒng)時前端登錄頁點了半天沒反應(yīng)打開控制臺看到Access-Control-Allow-Origin錯誤去配置里改了一下源地址馬上就好了。這種問題非常常見但很多人會在前端代理上繞來繞去其實后端把跨域配置允許好才是正路。最后再說兩句這套基于Springboot的淘寶購物管理系統(tǒng)我前后也幫人部署和改過幾次整體感受是代碼結(jié)構(gòu)和注釋質(zhì)量在同類項目里屬于中上水平尤其是訂單狀態(tài)機、庫存扣減、JWT鑒權(quán)這幾塊對剛接觸Springboot的人來說是很好的學習樣本。源碼里附帶的文檔雖然篇幅不大但確實把數(shù)據(jù)庫設(shè)計和接口約定寫清楚了配合著學能少掉很多頭發(fā)。如果你準備拿它做畢業(yè)設(shè)計或課程設(shè)計我建議不要只滿足于跑起來交差。試著去改一個功能比如把商品搜索改成支持多字段排序或者把支付回調(diào)改成模擬微信支付的通知格式這個過程會讓你真正理解這套系統(tǒng)是怎么工作的。遇到問題時也別急著換項目靜下心來看看日志、看看源碼里已經(jīng)寫好的處理方式收獲會超出你的預(yù)期。