ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

Spring Boot + Vue3珠宝小程序商城实战:SKU库存与支付设计

Spring Boot + Vue3珠宝小程序商城实战:SKU库存与支付设计 一个高客单价珠宝首饰线上商城项目最让人头疼的并不是“能下单”这三个字而是规格、库存、支付、多端数据一致性这些细节。最近在整理一套基于 Spring Boot Vue3 的珠宝首饰交易小程序也可理解为珠宝首饰电商平台 / 珠宝首饰小程序商城时我把核心链路重新梳理了一遍。这套结构不局限于某一个业务后续接玉石、黄金、钻石类商品都可以复用。这篇文章会把完整设计与关键实现拆开讲。读者如果正打算做在线珠宝首饰销售系统或珠宝首饰电商系统可以把它当作一份系统教程先理解业务模型再看数据库、后端、小程序、Vue3 管理后台是怎么串起来的最后还能直接用里面的代码解决开发中常见的报错。1. 项目背景与整体功能设计1.1 珠宝首饰交易小程序解决什么问题珠宝首饰交易小程序本质上是一个微信端商城但它和普通服装、数码电商有明显差异。普通商品下单时通常只需要确认颜色、尺码等少数规格而首饰需要考虑材质、成色、圈口、克重、证书编号、定制刻字等信息。尤其是黄金饰品不同克重对应不同价格同一款设计往往有成百上千种 SKU 组合。从用户角度看这类小程序需要给买家提供清晰的商品搜索、分类浏览、详情查看、购买咨询、下单支付、订单跟踪和售后入口。从商家角度看又需要后台能维护商品上下架、价格库存、订单履约、支付配置、退款售后和基础数据统计。若只是简单复刻一个商城模板很容易出现“能展示但难管理”“能下单但库存乱”的问题。所以本文采用的思路是后台管理端与 C 端小程序分离后端统一提供 REST API。C 端基于 uni-app Vue3 编写并可编译到微信小程序管理后台单独使用 Vue3 Element Plus 开发服务端则统一由 Spring Boot4Spring Boot 新一代版本基线承载。这样既保留了小程序的触达能力又让运营人员有独立、称手的商品管理工具。1.2 系统角色与功能模块划分典型的小程序商城平台从角色上可以分为三类角色使用端核心职能普通用户/买家微信小程序端注册登录、浏览商品、搜索筛选、查看详情、加入购物车、下单、支付、查看订单、评价或售后运营人员Vue3 管理后台商品分类管理、商品 SPU/SKU 维护、上下架、库存管理、订单处理、售后审核、营销配置系统管理员Vue3 管理后台后台账号权限、基础配置、数据统计、操作日志、支付参数配置围绕珠宝交易场景C 端小程序的重点模块包括小程序一键登录、首页金刚区与 Banner、商品分类导航、商品列表与搜索、商品 SPU/SKU 详情、购物车、确认订单、微信支付、订单状态列表、售后申请。管理后台的重点模块包括仪表盘统计、商品管理、SKU 库存管理、订单管理、用户管理、售后管理、系统配置。从工程实现角度看这套系统至少要拆分成三个独立工程jewelry-backend、jewelry-admin、jewelry-mall-app。三端通过统一的安全校验和接口规范协作能够避免把页面逻辑、请求逻辑、业务逻辑全部揉在一个代码仓库里导致后期难以维护的问题。1.3 珠宝类电商的核心业务难点第一个难点是商品模型分层。因为存在“款式”与“具体可售商品”的差异数据库通常要分为 SPU标准产品单元和 SKU库存量单位。例如一款“18K 金钻石戒指”是 SPU而“18K 白金、圈口 16、克重 3.2g”才是一个可以支付购买的 SKU。下单后库存扣减的对象永远是 SKU而不是 SPU。第二个难点是订单状态与库存一致性。用户提交订单先锁定库存支付成功后真正扣减库存未支付订单超过一定时间要自动关单并释放库存。期间如果存在并发很可能出现超卖。第三个难点是微信生态的接入。小程序登录需要临时 code 换会话信息支付需要商户号与回调验签文件上传与前端图片展示还需要处理域名和大小限制。整套流程并不是只有“调一个接口”那么简单安全边界和异常时序都必须在设计阶段考虑清楚。第四个难点是前端多端一致性。同一个业务既要出现在小程序端也要出现在 Vue3 管理后台。若接口没有统一的分页结构、返回码、异常提示前端会陷入大量的字段适配。因此后续所有后端接口我都会先约定统一响应体再展开具体代码。2. 技术栈说明与工程结构设计2.1 技术选型与版本说明项目标题中的 SpringBoot4 比较容易让新手误以为有一个完全不同的框架体系。实际上 Spring Boot 4 是新一代启动框架基线底层依赖更新版本的 Spring Framework整体要求 JDK 17 或更高版本同时在自动配置、可观测性、依赖管理等方面做了不少升级。对大多数商城系统来说我们在 Spring Boot 3.x 时代积累的分层思想、自动配置方式和 REST 接口写法依然适用。因此就算你的脚手架生成的是 3.x 版本也完全可以参考本文的项目结构与代码思路只需把依赖版本号调整成自己工程实际使用的版本即可。下面给出推荐技术栈层次技术选型说明后端开发Spring Boot4 / Spring Boot 3.x负责接口服务、业务逻辑、数据持久化权限与安全Spring Security JWT管理后台账号认证、C 端 Token 登录态ORM 框架MyBatis-Plus简化单表 CRUD分页查询体验好数据库MySQL 8.x存储商品、用户、订单、支付等核心数据缓存Redis验证码缓存、购物车临时数据、热点商品缓存C 端小程序uni-app Vue3编译到微信小程序兼顾后续 H5 扩展管理后台Vue3 Vite Element Plus使用 Vue3 组合式 API 开发接口调用Axios管理后台统一请求库2.2 统一工程目录结构多端项目如果目录结构一开始没有规划好后面经常出现“不知道某个文件该放哪里”的尴尬。下面是一套可复制的目录规范jewelry-mall ├── jewelry-backend # Spring Boot 后端服务 │ ├── src/main/java/com/jewelry/mall │ │ ├── common # 统一响应、异常、常量、工具类 │ │ ├── config # 跨域、安全、Redis、MyBatis-Plus 配置 │ │ ├── controller # 接口入口 │ │ ├── entity # 数据库实体 │ │ ├── mapper # MyBatis-Plus Mapper │ │ ├── service # 业务逻辑层 │ │ ├── dto/vo # 入参与出参对象 │ │ └── captcha # 滑块验证码封装 │ └── src/main/resources │ ├── application.yml │ └── mapper ├── jewelry-admin # Vue3 管理后台 │ ├── src/api │ ├── src/views │ ├── src/router │ ├── src/store │ └── src/utils └── jewelry-mall-app # uni-app 小程序端 ├── pages ├── components ├── static ├── store └── utils后端分层时要注意Controller 层只做参数接收与响应包装不写 SQLService 层处理事务和业务规则Mapper 层做数据访问。小程序端与管理后台是独立工程它们通过接口完成交互这样某一端发生故障时不会影响另一端开发。2.3 统一返回体与接口约定前后端接口如果不约定统一返回结构前端每个方法都要额外判断代码会显得非常脏。这里定义一个最常用的泛型响应类package com.jewelry.mall.common; import lombok.Data; Data public class RT { private Integer code; private String message; private T data; public static T RT success(T data) { RT r new R(); r.setCode(200); r.setMessage(success); r.setData(data); return r; } public static T RT error(String message) { RT r new R(); r.setCode(500); r.setMessage(message); return r; } public static T RT error(Integer code, String message) { RT r new R(); r.setCode(code); r.setMessage(message); return r; } }配套的全局异常处理器可以捕获业务异常、参数校验异常和兜底异常。这样用户看到的提示是“商品已下架”而不是一堆堆栈信息管理员也能通过日志服务看到真正异常链路。约定好code 200表示成功、code 401表示登录态失效、code 500表示业务或系统错误后续小程序和管理后台拦截器都能按这套规则做统一处理。3. 核心数据库设计与 SQL 示例3.1 珠宝商品 SPU / SKU 模型拆分商品表设计是整个商城的基石。如果把商品名称、主图、规格、价格、库存全部塞进一张表当某件饰品有 10 个圈口、3 种材质、5 种克重时维护成本会急剧上升因为很多公共信息被反复重复。更合理的方式是让 SPU 表存储商品公共属性SKU 表存储每个可销售版本的价格、库存和图片。具体到珠宝首饰场景SPU 公共信息包括商品名称、副标题、类目、主图、详情轮播图、材质、纯度、售后说明、证书编号等。SKU 可销售信息包括规格组合描述、克重、圈口、定制文案、价格、划线价、库存、锁定库存、启用状态等。买家在详情页选择“圈口 16 / 18K 白金”时前端拿到的是某个 SKU 的价格和库存。3.2 商品表、SKU 表与订单表 SQL下面给出三张核心表的 MySQL 建表 SQL。实际生产环境还可以补充普通索引与唯一索引这里以结构演示为主。CREATE TABLE product_spu ( id bigint NOT NULL AUTO_INCREMENT COMMENT 商品ID, category_id bigint NOT NULL COMMENT 分类ID, name varchar(200) NOT NULL COMMENT 商品名称, sub_title varchar(255) DEFAULT NULL COMMENT 副标题/卖点, main_image varchar(500) DEFAULT NULL COMMENT 主图地址, detail_images text COMMENT 详情轮播图多个用逗号分隔, material varchar(50) DEFAULT NULL COMMENT 材质, purity varchar(30) DEFAULT NULL COMMENT 纯度/成色, certificate_no varchar(100) DEFAULT NULL COMMENT 证书编号, status tinyint NOT NULL DEFAULT 1 COMMENT 状态0下架 1上架, sort int NOT NULL DEFAULT 0 COMMENT 排序值, sales int NOT NULL DEFAULT 0 COMMENT 销量, create_time datetime DEFAULT CURRENT_TIMESTAMP COMMENT 创建时间, update_time datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT 更新时间, PRIMARY KEY (id), KEY idx_category_status (category_id, status) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT珠宝首饰商品SPU表;CREATE TABLE product_sku ( id bigint NOT NULL AUTO_INCREMENT COMMENT SKU ID, product_id bigint NOT NULL COMMENT SPU商品ID, sku_code varchar(50) NOT NULL COMMENT SKU编码, spec_info varchar(255) NOT NULL COMMENT 规格信息如18K白金 圈口16 克重3.5g, price decimal(10,2) NOT NULL COMMENT 售价, market_price decimal(10,2) DEFAULT NULL COMMENT 市场价/划线价, stock int NOT NULL DEFAULT 0 COMMENT 库存, locked_stock int NOT NULL DEFAULT 0 COMMENT 锁定库存, image varchar(500) DEFAULT NULL COMMENT SKU图片, status tinyint NOT NULL DEFAULT 1 COMMENT 状态0禁用 1启用, create_time datetime DEFAULT CURRENT_TIMESTAMP, update_time datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, PRIMARY KEY (id), KEY idx_product_id (product_id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT珠宝首饰商品SKU表;订单主表放在单独的库中更利于后续按需扩展。电商订单表通常会冗余收货人、订单金额、商品快照等字段因为订单一旦生成不能因为商品改名或改价而受到影响。CREATE TABLE order_info ( id bigint NOT NULL AUTO_INCREMENT, order_no varchar(32) NOT NULL COMMENT 订单编号, user_id bigint NOT NULL COMMENT 用户ID, total_amount decimal(10,2) NOT NULL COMMENT 商品总金额, discount_amount decimal(10,2) NOT NULL DEFAULT 0.00 COMMENT 优惠金额, pay_amount decimal(10,2) NOT NULL COMMENT 实付金额, status tinyint NOT NULL DEFAULT 0 COMMENT 订单状态0待支付 1待发货 2待收货 3已完成 4已取消 5售后中, receiver_name varchar(50) NOT NULL COMMENT 收货人, receiver_phone varchar(20) NOT NULL COMMENT 收货手机号, receiver_address varchar(500) NOT NULL COMMENT 收货地址, pay_time datetime DEFAULT NULL COMMENT 支付时间, deliver_time datetime DEFAULT NULL COMMENT 发货时间, finish_time datetime DEFAULT NULL COMMENT 完成时间, cancel_reason varchar(500) DEFAULT NULL COMMENT 取消原因, create_time datetime DEFAULT CURRENT_TIMESTAMP, update_time datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, PRIMARY KEY (id), UNIQUE KEY uk_order_no (order_no), KEY idx_user_id (user_id), KEY idx_status (status) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT订单主表;订单商品明细表需要记录商品名、SKU 描述、成交单价、购买数量和商品快照图片。它的核心作用是当运营修改 SPU 或 SKU 信息时历史订单依然能展示用户当时购买的商品规格。3.3 订单状态机与数据一致性订单状态不是随便 switch 的字段需要设计成可理解的状态机。建议把订单主状态定义成几个明确的节点状态值状态名称触发动作后续可操作0待支付用户提交订单去支付 / 取消订单1待发货支付回调成功商家发货2待收货商家点击发货用户确认收货3已完成用户确认收货售后4已取消用户取消或超时关单无5售后中用户发起售后商家审核/退款这里最关键的是“待支付”状态下的库存策略。如果用户在提交订单时直接把库存扣成实际负数会被大量未支付订单占用如果不锁库存支付成功后又有可能发现库存不足。常见做法是把库存拆成“可用库存”和“锁定库存”。用户提交订单时扣减可用库存并增加锁定库存支付成功后真正扣减库存并释放锁定库存订单超时取消时则反向恢复可用库存、释放锁定库存。上述操作通常需要在一个数据库事务里完成而在高并发场景下还要考虑分布式事务。对大部分中小型珠宝商城先用 MySQL 行锁或乐观锁保住库存不超卖再通过 Redis 提升热点商品查询性能已经足够应付日常活动流量。4. Spring Boot 后端核心实现4.1 基础配置与启动配置后端工程创建一个标准的 Spring Boot Web 项目Jar 包依赖中需要引入 Web、MyBatis-Plus、Spring Security、Redis、MySQL、Lombok 等组件。以application.yml为例核心配置如下server: port: 8088 spring: application: name: jewelry-mall datasource: url: jdbc:mysql://localhost:3306/jewelry_mall?useUnicodetruecharacterEncodingutf8serverTimezoneAsia/Shanghai username: root password: 你的数据库密码 driver-class-name: com.mysql.cj.jdbc.Driver data: redis: host: localhost port: 6379 database: 0 jackson: date-format: yyyy-MM-dd HH:mm:ss time-zone: GMT8 mybatis-plus: mapper-locations: classpath*:mapper/**/*.xml configuration: map-underscore-to-camel-case: true log-impl: org.apache.ibatis.logging.stdout.StdOutImpl这里的数据库连接串需要按本地环境调整尤其是serverTimezoneAsia/Shanghai不能省略否则 MySQL 驱动在解析时间类型时会因时区差异报错。密码不要直接写在提交到 Git 的配置文件里生产环境建议使用环境变量或配置中心占位符注入。启动类和服务分层可以这样拆分。启动类只负责引导 Spring 容器Controller 定义接口Service 定义业务Mapper 负责 SQL。如果业务较复杂还可以增加 Manager 层用它编排多个 Mapper 的调用避免 Service 膨胀。4.2 用户登录与 JWT 签发小程序端“微信一键登录”的完整时序是小程序调用uni.login获取临时code后端拿code换微信openid和session_key随后在本地创建或更新用户记录并生成 Token 返回给小程序。小程序后续请求统一在 Header 中携带Authorization由后端拦截器校验登录态。在管理后台登录方式通常改为账号密码加滑块验证码。密码不落明文使用 BCrypt 加密存储登录成功后签发 JWTJWT 中携带用户 ID、角色标识和过期时间。这里以小型商城场景为例提供一个简化版登录服务方法package com.jewelry.mall.service.impl; import com.baomidou.mybatisplus.core.toolkit.Wrappers; import com.jewelry.mall.entity.AdminUser; import com.jewelry.mall.mapper.AdminUserMapper; import com.jewelry.mall.service.AdminAuthService; import com.jewelry.mall.util.JwtUtil; import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; import org.springframework.stereotype.Service; import javax.annotation.Resource; Service public class AdminAuthServiceImpl implements AdminAuthService { Resource private AdminUserMapper adminUserMapper; Resource private BCryptPasswordEncoder passwordEncoder; Resource private JwtUtil jwtUtil; Override public String login(String username, String password) { AdminUser user adminUserMapper.selectOne( Wrappers.AdminUserlambdaQuery() .eq(AdminUser::getUsername, username) ); if (user null || !passwordEncoder.matches(password, user.getPassword())) { throw new RuntimeException(用户名或密码错误); } if (user.getStatus() ! null user.getStatus() 0) { throw new RuntimeException(账号已被禁用); } return jwtUtil.createToken(user.getId(), user.getUsername(), user.getRoleCode()); } }实际登录前后端应当先校验滑块验证码。验证码校验通过后再执行用户名密码校验。这样做能有效降低恶意脚本对登录接口的暴力请求频率。4.3 滑块验证码集成SpringBoot4 如何对接 Tianai 滑块热词中经常出现“springboot4如何对接tianai滑块”就是因为商城登录或抢购场景需要防机器刷。Tianai-Captcha 是一套开源的滑块验证码组件部署后能提供滑块拼图、旋转图片、文字点选等多种验证方式。和传统图片验证码相比滑块的交互体验更好也能阻挡大部分基础脚本。对接思路不复杂核心分两步引入依赖并配置好存储在业务服务中调用组件生成滑块和校验滑块。由于该组件版本更新频率并不低具体 Maven 版本号建议以当时仓库中的最新稳定版本为准并不建议在文章中锁定某个旧版本号。你可以在pom.xml中加入官方对应 Spring Boot starter然后在自己的代码中做一层CaptchaService隔离。示例伪代码如下package com.jewelry.mall.captcha; import lombok.Data; Data public class CaptchaResult { private String captchaId; private String backgroundImage; private String sliderImage; }package com.jewelry.mall.captcha; public interface CaptchaService { CaptchaResult generate(); boolean verify(String captchaId, String userAnswer); }接口设计好以后实现类中由你当前引入的CaptchaTemplate具体接管底层逻辑。生成接口返回给前端的只有图片 ID 和图片信息永远不能把正确答案直接下发。校验成功后再执行登录流程同一 captchaId 通常应限制只能成功一次防止验证码被反复重放。整体接入可以理解为前端先请求“生成滑块”接口拿到背景图和拼图用户拖动拼图完成后前端将滑块轨迹、x 轴位移等结果传给后端后端通过组件校验轨迹和断言结果最后返回校验是否成功。业务系统只需把验证结果放在登录接口前面就能形成第一层防护。4.4 商品查询与库存扣减接口商品查询接口既要支撑小程序首页列表也要支撑搜索页关键词搜索。下面是控制层示例package com.jewelry.mall.controller; import com.jewelry.mall.common.R; import com.jewelry.mall.service.ProductService; import com.jewelry.mall.vo.ProductSkuVO; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RestController; import javax.annotation.Resource; import java.util.List; RestController RequestMapping(/api/product) public class ProductController { Resource private ProductService productService; GetMapping(/page) public R? page(RequestParam(defaultValue 1) Integer page, RequestParam(defaultValue 10) Integer size, RequestParam(required false) Long categoryId, RequestParam(required false) String keyword) { return R.success(productService.queryPage(page, size, categoryId, keyword)); } GetMapping(/sku/list) public RListProductSkuVO skuList(RequestParam Long productId) { return R.success(productService.querySkuList(productId)); } }商品列表和详情查询是典型读多写少场景热点 SKU 价格与库存可以缓存到 Redis。库存扣减则不建议用“先查库存再在代码里判断最后 update”的流程因为并发情况下会出现严重超卖。更稳妥的方式是直接执行带条件的更新语句UPDATE product_sku SET stock stock - #{num}, locked_stock locked_stock #{num} WHERE id #{skuId} AND stock #{num};如果这条 SQL 影响行数为 0说明库存不足或 SKU 已下架事务直接抛出异常回滚。这种条件更新天然具备行锁效果逻辑简单且性能可以接受。对于高并发抢购场景再升级到 Redis Lua 或消息队列削峰也不迟不建议在第一版就做大而全的库存中心。5. 小程序端商城实战uni-app Vue35.1 为什么选择 uni-app Vue3 开发小程序端很多新手纠结小程序端到底用原生微信小程序还是 uni-app。如果只做微信小程序原生开发没有太大问题但如果后续希望业务也能快速输出 H5 或支付宝小程序版本uni-app 会更有优势。uni-app 支持 Vue3 语法开发可以使用组合式 API 组织业务逻辑这让项目的代码风格能够和管理后台保持一定程度统一
返回列表