解析)
運動戶外類商品和普通標品電商最大的區(qū)別在于規(guī)格、庫存和配送維度要復雜得多。沖鋒衣有顏色和尺碼露營帳篷有不同的容量、重量登山杖還有長度和節(jié)數(shù)每一條 SKU 都對應獨立價格和庫存。要把這些商品真正放進小程序里形成交易閉環(huán)不能只做一個靜態(tài)商品展示頁還需要打通用戶登錄、購物車、訂單狀態(tài)、后臺管理這一整條鏈路?;?SpringBoot Vue3 微信小程序?qū)崿F(xiàn)的運動戶外交易電商系統(tǒng)正是圍繞這條鏈路展開的多端協(xié)作項目后端統(tǒng)一提供商品、訂單、用戶服務Vue3 端承擔運營后臺和平臺管理小程序端面向 C 端用戶完成瀏覽、加購、結(jié)算和訂單查看。下面直接從可運行源碼的角度拆解這類系統(tǒng)從數(shù)據(jù)庫到接口、再到前后端聯(lián)調(diào)的全過程。1. 先理清運動戶外商城系統(tǒng)的整體形態(tài)1.1 三端職責與源碼目錄的對應關系很多初學者以為“商城系統(tǒng)”就是一個小程序目錄加一個后端接口目錄實際項目里通常要拆成三部分職責差異很大。端技術方向面向?qū)ο蠛诵穆氊烻pring Boot 后端Spring Boot MyBatis-Plus MySQL Redis所有前端端用戶登錄、商品查詢、購物車、訂單、后臺管理接口Vue3 管理后臺Vue3 Vite Element Plus運營人員、管理員商品編輯、上下架、訂單發(fā)貨、數(shù)據(jù)查看微信小程序端原生微信小程序或 uni-app普通消費者瀏覽商品、加購物車、下單、支付、訂單查看源碼目錄可以這樣組織outdoor-mall/ ├── mall-server/ # Spring Boot 后端 ├── mall-admin/ # Vue3 管理后臺 └── mall-weapp/ # 微信小程序端還有一個容易混淆的問題標題中同時出現(xiàn)“SpringBoot4”“Vue3”和“小程序”前端部分究竟怎么分工需要提前確認技術路線。路線 A管理后臺使用 Vue3小程序使用原生微信小程序。這種方案后端完全不用關心小程序端是否使用 VueVue3 只負責 Web 管理界面。路線 B管理后臺使用 Vue3小程序也通過 uni-app 使用 Vue3 語法開發(fā)。這樣兩個前端工程在代碼風格上更接近組件和 API 也能部分復用代價是多引入一層編譯框架。1.2 一條完整交易主流程要經(jīng)歷哪些環(huán)節(jié)從用戶視角看運動戶外電商的典型使用路徑是打開小程序進入首頁瀏覽商品 - 按分類篩選露營、徒步、騎行裝備 - 進入商品詳情選擇顏色尺碼 - 加入購物車 - 在購物車中勾選商品提交訂單 - 模擬支付或?qū)游⑿胖Ц?- 后臺看到新訂單并發(fā)貨 - 用戶查看物流狀態(tài)并確認收貨。后臺管理員的路徑則是登錄 Vue3 管理后臺 - 維護商品分類 - 新增或編輯商品 - 上傳商品主圖和詳情圖 - 配置 SKU 價格與庫存 - 上架商品 - 在訂單列表中對新訂單進行發(fā)貨處理 - 處理用戶售后或退款。運動戶外交易系統(tǒng)的技術難點不在某個頁面有多炫而在于商品規(guī)格、庫存扣減和訂單狀態(tài)流轉(zhuǎn)必須保持準確。一次并發(fā)下單導致超賣就可能在售后服務階段造成大量成本。1.3 功能模塊先從六張核心表開始拆從后端設計角度看商品、用戶、購物車、訂單四個域是必需的后臺管理員又是一套獨立身份。按這個思路項目功能模塊可以拆成用戶模塊微信授權登錄、收貨地址、用戶狀態(tài)。分類與商品模塊戶外商品分類、商品 SPU、SKU 規(guī)格、庫存。購物車模塊加入購物車、勾選結(jié)算、修改數(shù)量。訂單模塊訂單主表、訂單明細、狀態(tài)流轉(zhuǎn)。后臺管理員模塊管理員登錄、商品管理、訂單發(fā)貨。公共服務模塊文件上傳、統(tǒng)一異常、接口鑒權。模塊邊界越清晰后期擴展評價系統(tǒng)、優(yōu)惠券、分銷等功能時就越不容易污染已有表結(jié)構(gòu)。2. 環(huán)境準備與運動戶外商城數(shù)據(jù)表設計2.1 推薦開發(fā)環(huán)境與版本組合這類項目在不同版本的 JDK、Spring Boot、Node 和小程序基礎庫上表現(xiàn)差異很大建議先按一張表格把環(huán)境固定下來。工具推薦版本或替代方案說明JDK17 或更高Spring Boot 3 起要求 JDK 17Maven3.8管理后端依賴MySQL8.0字符集使用 utf8mb4Redis6.x / 7.x緩存熱點商品、可選的登錄態(tài)輔助Node.js18運行 Vite 管理后臺微信開發(fā)者工具最新穩(wěn)定版編譯預覽小程序小程序 AppID正式 AppID 或測試號登錄 openid 需要真實 AppID關于 SpringBoot4 需要單獨說明一點標題中的框架命名可以直接寫成“SpringBoot4”系列但落地時要以 Maven 中央倉庫能夠拉取到的穩(wěn)定版本為準。如果當時沒有 4.x 正式版退回 Spring Boot 3.x 穩(wěn)定線并不會影響這套項目設計因為表結(jié)構(gòu)、REST 接口、MyBatis-Plus 用法在 3 到 4 之間不會出現(xiàn)顛覆性變化。真正容易踩坑的是 Redis 配置前綴從spring.redis改成spring.data.redis以及命名空間從javax調(diào)整為jakarta。2.2 運動戶外商城最核心的八張表把普通商城簡化后至少需要下面這些表。這里的重點不是列一大堆字段而是解釋每個表在企業(yè)項目中為什么存在。表名存儲內(nèi)容設計要點t_admin_user后臺管理員與 C 端用戶分開避免混在一個用戶體系t_user小程序用戶使用微信 openid 作為唯一登錄標識t_category商品分類運動戶外類目層級較淺先保留一級分類t_product商品 SPU商品公共信息例如名稱、主圖、詳情t_product_sku商品 SKU顏色尺碼規(guī)格價格和庫存都掛在 SKU 上t_cart購物車以用戶和 SKU 為唯一組合t_order訂單主表保存收貨人和訂單總額狀態(tài)放在主表t_order_item訂單明細一個訂單對應多個 SKU下單后不允許被商品修改影響以運動戶外場景為例一件沖鋒衣在 t_product 中只保存“三合一沖鋒衣”這個 SPU但它在 t_product_sku 中會拆成“黑色-M”“黑色-L”“紅色-M”等多條記錄。這樣用戶加購時選擇的是具體 SKU結(jié)算價格也能精確到規(guī)格級別。2.3 建表 SQL 可以直接用于初始化CREATE TABLE t_admin_user ( id BIGINT NOT NULL AUTO_INCREMENT COMMENT 主鍵, username VARCHAR(50) NOT NULL COMMENT 登錄名, password VARCHAR(100) NOT NULL COMMENT BCrypt密文, real_name VARCHAR(50) DEFAULT NULL COMMENT 真實姓名, status TINYINT DEFAULT 1 COMMENT 1啟用 0禁用, create_time DATETIME DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (id), UNIQUE KEY uk_username (username) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT后臺管理員表; CREATE TABLE t_user ( id BIGINT NOT NULL AUTO_INCREMENT COMMENT 主鍵, openid VARCHAR(64) DEFAULT NULL COMMENT 微信openid, unionid VARCHAR(64) DEFAULT NULL COMMENT 微信unionid需要開放平臺才有, nickname VARCHAR(64) DEFAULT NULL COMMENT 昵稱, avatar VARCHAR(255) DEFAULT NULL COMMENT 頭像地址, phone VARCHAR(20) DEFAULT NULL COMMENT 手機號, status TINYINT DEFAULT 1 COMMENT 1正常 0禁用, create_time DATETIME DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (id), UNIQUE KEY uk_openid (openid) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT小程序用戶表; CREATE TABLE t_category ( id BIGINT NOT NULL AUTO_INCREMENT, name VARCHAR(50) NOT NULL COMMENT 分類名稱, parent_id BIGINT DEFAULT 0 COMMENT 父級分類0表示一級, sort INT DEFAULT 0 COMMENT 排序值越小越靠前, status TINYINT DEFAULT 1 COMMENT 1啟用 0停用, create_time DATETIME DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT商品分類表; CREATE TABLE t_product ( id BIGINT NOT NULL AUTO_INCREMENT, category_id BIGINT NOT NULL COMMENT 分類ID, name VARCHAR(100) NOT NULL COMMENT 商品名稱, sub_title VARCHAR(255) DEFAULT NULL COMMENT 副標題, main_image VARCHAR(255) DEFAULT NULL COMMENT 主圖, detail TEXT COMMENT 富文本詳情, status TINYINT DEFAULT 0 COMMENT 0下架 1上架, create_time DATETIME DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (id), KEY idx_category (category_id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT商品SPU表; CREATE TABLE t_product_sku ( id BIGINT NOT NULL AUTO_INCREMENT, product_id BIGINT NOT NULL COMMENT SPU ID, sku_name VARCHAR(100) DEFAULT NULL COMMENT 規(guī)格描述如黑色-L, price DECIMAL(10,2) NOT NULL COMMENT 銷售價, stock INT NOT NULL DEFAULT 0 COMMENT 庫存, sales INT DEFAULT 0 COMMENT 銷量, status TINYINT DEFAULT 1 COMMENT 1啟用 0停用, create_time DATETIME DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (id), KEY idx_product (product_id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT商品SKU表; CREATE TABLE t_cart ( id BIGINT NOT NULL AUTO_INCREMENT, user_id BIGINT NOT NULL, product_id BIGINT NOT NULL, sku_id BIGINT NOT NULL, quantity INT NOT NULL DEFAULT 1, checked TINYINT DEFAULT 1 COMMENT 1勾選結(jié)算 0不勾選, create_time DATETIME DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (id), UNIQUE KEY uk_user_sku (user_id, sku_id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT購物車表; CREATE TABLE t_order ( id BIGINT NOT NULL AUTO_INCREMENT, order_no VARCHAR(32) NOT NULL COMMENT 業(yè)務訂單號, user_id BIGINT NOT NULL COMMENT 下單用戶, total_amount DECIMAL(10,2) NOT NULL COMMENT 商品總金額, pay_amount DECIMAL(10,2) NOT NULL COMMENT 實付金額優(yōu)惠后可等于總金額, status TINYINT NOT NULL DEFAULT 0 COMMENT 0待支付 1已支付 2已發(fā)貨 3已完成 4已取消, receiver_name VARCHAR(50) NOT NULL, receiver_phone VARCHAR(20) NOT NULL, receiver_address VARCHAR(255) NOT NULL, pay_time DATETIME DEFAULT NULL, ship_time DATETIME DEFAULT NULL, finish_time DATETIME DEFAULT NULL, create_time DATETIME DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (id), UNIQUE KEY uk_order_no (order_no), KEY idx_user (user_id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT訂單主表; CREATE TABLE t_order_item ( id BIGINT NOT NULL AUTO_INCREMENT, order_id BIGINT NOT NULL, product_id BIGINT NOT NULL, sku_id BIGINT NOT NULL, product_name VARCHAR(100) NOT NULL COMMENT 商品名稱快照, sku_name VARCHAR(100) DEFAULT NULL COMMENT SKU描述快照, product_image VARCHAR(255) DEFAULT NULL, price DECIMAL(10,2) NOT NULL, quantity INT NOT NULL, total_amount DECIMAL(10,2) NOT NULL, PRIMARY KEY (id), KEY idx_order (order_id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT訂單明細表;訂單明細必須保存商品名稱和 SKU 描述快照。原因是下單后如果管理員修改了商品名稱或價格歷史訂單不應該跟著變化否則對賬和售后都會出問題。這一條是訂單系統(tǒng)最常見的隱藏設計點。3. 搭建 Spring Boot 后端先把登錄和商品接口跑通3.1 后端分層與包結(jié)構(gòu)后端如果只寫 controller 和 mapper前期跑通快但訂單邏輯一復雜就會在 service 里堆出幾百行代碼。推薦按常見項目結(jié)構(gòu)分層mall-server/src/main/java/com/example/outdoor/ ├── common/ # 統(tǒng)一返回結(jié)果、異常、常量 │ ├── Result.java │ ├── BizException.java │ └── JwtUtil.java ├── config/ # Web配置、攔截器、Redis配置 ├── controller/ # 接口入口 │ ├── AuthController.java │ ├── ProductController.java │ ├── CartController.java │ ├── OrderController.java │ └── admin/ # 后臺管理接口按角色再分層 ├── entity/ # 數(shù)據(jù)庫實體 ├── mapper/ # MyBatis-Plus Mapper ├── service/ # 業(yè)務邏輯接口和實現(xiàn) └── dto/ # 請求參數(shù)和返回 VO分層的目的不是為了讓目錄好看而是改訂單邏輯時不需要去 controller 里翻業(yè)務代碼排查庫存問題時也能快速定位到 service 和 mapper 哪一層出錯。3.2 Maven 依賴與應用配置在 pom.xml 中加入核心依賴。下面的版本號是用來跑通項目的示例正式落地要以倉庫中實際可用版本為準。parent groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-parent/artifactId version3.3.5/version relativePath/ /parent properties java.version17/java.version mybatis-plus.version3.5.7/mybatis-plus.version /properties dependencies dependency groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-web/artifactId /dependency dependency groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-data-redis/artifactId /dependency dependency groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-validation/artifactId /dependency dependency groupIdcom.baomidou/groupId artifactIdmybatis-plus-spring-boot3-starter/artifactId version${mybatis-plus.version}/version /dependency dependency groupIdcom.mysql/groupId artifactIdmysql-connector-j/artifactId scoperuntime/scope /dependency dependency groupIdio.jsonwebtoken/groupId artifactIdjjwt-api/artifactId version0.11.5/version /dependency dependency groupIdio.jsonwebtoken/groupId artifactIdjjwt-impl/artifactId version0.11.5/version scoperuntime/scope /dependency dependency groupIdorg.projectlombok/groupId artifactIdlombok/artifactId optionaltrue/optional /dependency /dependenciesapplication.yml 中的配置要和 Spring Boot 版本對齊。以 3.x 穩(wěn)定版為例Redis 配置要寫在spring.data.redis下server: port: 8080 spring: application: name: outdoor-mall-server datasource: url: jdbc:mysql://localhost:3306/outdoor_mall?useUnicodetruecharacterEncodingutf8useSSLfalseserverTimezoneAsia/Shanghai username: root password: 123456 driver-class-name: com.mysql.cj.jdbc.Driver data: redis: host: localhost port: 6379 database: 0 mybatis-plus: configuration: map-underscore-to-camel-case: true 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 wechat: appid: 你的小程序AppID secret: 你的小程序Secret如果使用 Spring Boot 2.x需要把spring.data.redis改回spring.redis。這是從 2 升到 3 之后最影響項目跑通的一處變更。3.3 微信登錄不是“登錄注冊”而是 code 換 openid小程序端不能像 Web 端一樣讓用戶先輸入用戶名密碼推薦登錄方式是小程序通過wx.login()拿到臨時 code。小程序把 code 傳給后端。后端調(diào)用微信jscode2session接口用 code 換取 openid 和 session_key。后端根據(jù) openid 在 t_user 中查找用戶不存在則創(chuàng)建。后端生成 JWT token 返回給小程序后續(xù)請求通過請求頭攜帶 token。Controller 代碼可以這樣寫RestController RequestMapping(/api/auth) public class AuthController { private final UserService userService; public AuthController(UserService userService) { this.userService userService; } PostMapping(/wx-login) public ResultLoginVO wxLogin(RequestBody WxLoginDTO dto) { if (dto null || dto.getCode() null) { return Result.error(code不能為空); } return Result.ok(userService.wxLogin(dto.getCode())); } }Service 中的關鍵邏輯是請求微信接口并處理返回結(jié)果。下面代碼用來說明思路實際請根據(jù)選擇 HTTP 客戶端調(diào)整參數(shù)和異常處理Service public class UserServiceImpl implements UserService { private final UserMapper userMapper; public UserServiceImpl(UserMapper userMapper) { this.userMapper userMapper; } Override Transactional(rollbackFor Exception.class) public LoginVO wxLogin(String code) { String url https://api.weixin.qq.com/sns/jscode2session ?appidAPP_ID secretAPP_SECRET js_code code grant_typeauthorization_code; // 這里使用 HttpClient 或 RestTemplate 請求 url // 返回 JSON 中會包含 openid 和 session_key String openid ; if (openid null || openid.isEmpty()) { throw new BizException(微信登錄失敗請檢查code和AppID配置); } User user userMapper.selectOne( new LambdaQueryWrapperUser() .eq(User::getOpenid, openid)); if (user null) { user new User(); user.setOpenid(openid); user.setNickname(戶外用戶); user.setStatus(1); userMapper.insert(user); } String token JwtUtil.createToken(user.getId()); LoginVO vo new LoginVO(); vo.setToken(token); vo.setUserId(user.getId()); return vo; } }這里的請求地址是微信官方登錄憑證校驗接口。真實項目不要在前端保存 appid 和 secret必須由后端通過服務端請求微信開放接口避免 secret 暴露到小程序包里。3.4 JWT 鑒權與攔截器JWT 的作用是讓后端在無狀態(tài)接口中識別“當前用戶是誰”。登錄成功后token 放到請求頭Authorization中后端通過攔截器讀取并解析。Component public class AuthInterceptor implements HandlerInterceptor { Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { if (request.getMethod().equalsIgnoreCase(OPTIONS)) { return true; } String token request.getHeader(Authorization); if (token ! null token.startsWith(Bearer )) { token token.substring(7); } Long userId JwtUtil.parseToken(token); if (userId null) {