SpringBoot+Vue3 商城系统设计:商品·SKU·购物车·订单全流程拆解
SpringBoot+Vue3 商城系统设计:商品·SKU·购物车·订单全流程拆解
🌐 演示地址:http://ruoyioffice.com | 📦 源码1·GitHub:ruoyi-office | 📦 源码2·GitCode:ruoyi-office | 📦 源码3·Gitee:ruoyi-office | 💬 微信:17156169080(备注「RuoYi Office」)
商城系统是所有电商业务的底座。它看起来只是"上架商品、加购物车、下单付款",但真正落地后会发现:一件衣服有红色 XL、蓝色 M 等十几种规格,价格库存怎么存?购物车要不要实时算优惠?同一个 SKU 只剩 1 件,两个人同时下单会不会超卖?优惠券、满减、积分要怎么叠加才不会算错?RuoYi Office 的商城模块(
yudao-module-mall)用 SPU/SKU 双层模型承载商品,用一张轻量购物车表记录选购,用一条可插拔的价格计算责任链(TradePriceCalculator)统一算价,下单即原子扣减 SKU 库存防超卖,并让订单与支付单、售后单彻底解耦——构成一套可二次开发的完整商城内核。
▲ 全景图:商品域(SPU/SKU/分类)→ 购物车(轻量选购)→ 价格责任链(秒杀·折扣·满减·券·积分·运费逐级叠加)→ 订单(下单扣库存、解耦支付单)→ 售后,全链路一张图看懂
引言:商城系统到底难在哪?
“不就是商品列表加个购物车加个下单吗?”——这是很多人对商城的第一印象。但真正做成系统,复杂度会从四个地方集中爆发:
商品的"一物多规格"怎么建模? 一件 T 恤有颜色、尺码两个维度,组合出"红色 S / 红色 M / 蓝色 XL"等多个可售单元,每个单元价格、库存、条码都不同。如果只用一张商品表,规格一多就会字段爆炸。正确做法是 SPU(标准产品单元)+ SKU(库存量单位) 双层建模。
购物车要不要实时算优惠价? 如果加购时就算秒杀、满减、券,用户每改一次数量都要重算一遍,性能和一致性都难保证。RuoYi Office 的选择是:购物车只存"买什么、买几个、选不选",营销价留到结算页统一算。
优惠怎么叠加才不会乱? 秒杀价、限时折扣、会员价、满减送、优惠券、积分抵扣、运费——这些优惠有先后顺序、有互斥关系。把它们硬编码在一个大方法里,迟早改不动。需要一条可插拔、可排序的价格计算责任链。
库存什么时候扣、怎么防超卖? 下单扣还是支付扣?并发下单会不会扣成负数?这两个问题直接决定商城会不会"卖出去发不出货"。
本文以 RuoYi Office 的 yudao-module-mall 模块为例,基于真实源码,完整拆解商品 SPU/SKU、购物车、价格计算责任链、订单创建与库存防超卖的设计与实现。
一、业务设计:SPU/SKU 双层商品模型
1.1 核心抽象:SPU 是"商品",SKU 是"能下单的那一件"
结论先行:SPU(Standard Product Unit,标准产品单元)描述"一款商品",SKU(Stock Keeping Unit,库存量单位)描述"这款商品的某个具体规格组合"。 用户在列表看到的是 SPU(如"纯棉圆领 T 恤"),真正加入购物车、扣减库存、计算成交价的是 SKU(如"红色 / XL")。
ProductSpuDO(表product_spu):一款商品,1 个 SPU 对应 N 个 SKU;冗余下属 SKU 的最低价和总库存,方便列表展示与排序。ProductSkuDO(表product_sku):通过spu_id关联 SPU,存放真实的price / stock / properties(规格属性)。
spec_type 字段区分单规格(false)与多规格(true):单规格商品也有一条 SKU,只是没有规格属性,这样下单逻辑可以统一。
1.2 规格属性用 JSON 存,SKU 自带"它是什么规格"
SKU 的规格组合存在 properties 字段里,用 JacksonTypeHandler 序列化为 JSON。每个 SKU 自己就携带"我是颜色=红色、尺码=XL"这组属性:
[
{"propertyId": 1, "propertyName": "颜色", "valueId": 10, "valueName": "红色"},
{"propertyId": 2, "propertyName": "尺码", "valueId": 20, "valueName": "XL"}
]
下单时,订单项 TradeOrderItemDO.properties 会冗余这份 JSON——这样即使商品后续改了规格,历史订单看到的仍是"下单那一刻的规格",保证订单可追溯。
1.3 商品分类:两级树形,商品必须挂在第二级
商品分类 ProductCategoryDO(表 product_category)是 parent_id 树形结构,但有一条硬约束:层级限制为 2(CATEGORY_LEVEL = 2),商品必须挂在第二级分类下。这是电商的通用实践——一级分类做大类导航,二级分类才挂具体商品,避免分类层级无限延伸导致筛选混乱。
商品状态 ProductSpuStatusEnum 只有三态,简单清晰:
| 枚举 | status | 含义 |
|---|---|---|
RECYCLE |
-1 | 回收站(软删除) |
DISABLE |
0 | 下架 |
ENABLE |
1 | 上架 |
二、系统设计:模块组成与核心决策
2.1 模块组成
商城内核由"商品 + 购物车 + 交易订单 + 售后"组成,分属 product 与 trade 两个子模块:
| 子模块 | 数据表 | 功能 |
|---|---|---|
| 商品 SPU | product_spu |
商品主体,冗余最低价/总库存 |
| 商品 SKU | product_sku |
规格单元,真实价格/库存/规格 JSON |
| 商品分类 | product_category |
两级树形分类 |
| 购物车 | trade_cart |
用户×SKU×数量×选中 |
| 交易订单 | trade_order(+item) |
订单主表 + 订单项 |
| 售后单 | trade_after_sale |
退款/退货退款 |
| 分销佣金 | trade_brokerage_* |
推广分佣(可选) |
2.2 核心设计决策
| 决策点 | 方案 | 理由 |
|---|---|---|
| 商品建模 | SPU/SKU 双层 + 规格 JSON | 一物多规格,价格库存独立 |
| 价格冗余 | SPU 冗余最低价/总库存 | 列表展示快,无需聚合 SKU |
| 购物车 | 只存选购,不算营销价 | 简单、一致,性能好 |
| 价格计算 | TradePriceCalculator 责任链 + @Order |
优惠可插拔、可排序、可扩展 |
| 库存扣减 | 下单即扣 SKU 库存 | 防止"下单后被别人买走" |
| 防超卖 | 条件 UPDATE:stock = stock - x WHERE stock >= x |
单行原子,扣不动则失败 |
| 订单与支付 | 解耦,trade_order.pay_order_id 关联 |
支付渠道可换,互不影响 |
| 订单扩展 | TradeOrderHandler 生命周期钩子 |
库存/积分/券/分佣各司其职 |
三、PC 端功能实现
3.1 商品(SPU)列表
商品管理是商城后台的入口,按 SPU 展示,支持按分类、状态、名称筛选。列表里的"价格""库存"直接读 SPU 的冗余字段,无需实时聚合 SKU。
▲ 商品列表:每行是一个 SPU,价格/库存为下属 SKU 的最低价与求和;状态分上架/下架/回收站,可批量上下架
列表设计要点:
- 展示维度是 SPU:用户视角的"一款商品",点进去才看到多个 SKU 规格。
- 价格库存是冗余值:SPU 的
price是下属 SKU 最低价,stock是求和,列表查询不需要 join SKU。 - 三态管理:上架(
ENABLE)/下架(DISABLE)/回收站(RECYCLE),回收站实现软删除可恢复。
3.2 商品编辑(SPU + 多 SKU 规格)
商品编辑页是商城后台最复杂的表单:先配规格项(颜色、尺码),再由规格值笛卡尔积生成 SKU 表格,逐个 SKU 填价格、库存、条码。
▲ 商品编辑:在"价格库存"页选择单/多规格,多规格时配置规格项→生成 SKU 矩阵→逐行填价格/库存/图片;保存时校验分类为二级
设计要点:
- 单/多规格切换:
spec_type=false时只有一条无属性 SKU;true时由规格值组合生成多条 SKU。 - 价格单位是"分":
price / market_price / cost_price均以分为单位存储,避免浮点误差。 - SPU 冗余自动算:保存时取所有 SKU 的最低价写入 SPU
price,库存求和写入 SPUstock。
3.3 订单列表与详情
订单列表展示交易全貌,按订单状态(待支付/待发货/已发货/已完成/已取消)筛选,可下钻到订单详情查看订单项、收货地址、价格明细、物流信息。
▲ 订单列表:状态彩色标签区分,价格明细含商品价、优惠、运费、实付;点详情可发货、查看售后
设计要点:
- 价格明细透明:实付 = 商品总价 − 优惠券 − 积分抵扣 − 折扣 + 运费 + 调价 − 会员价,每一项都落库可追溯。
- 订单项冗余商品快照:
spu_name / properties / pic_url在下单时冗余,商品改名/改规格不影响历史订单。 - 状态驱动操作:待发货才能发货,已发货才能确认收货,非法操作在 Service 层被拦截(详见本周《订单状态机设计》)。
四、价格计算责任链:商城最优雅的设计
4.1 一条链,把所有优惠按顺序算清楚
这是整个商城最值得借鉴的设计。 算价不是写在一个大方法里,而是定义一个 TradePriceCalculator 接口,每种优惠是一个独立实现类,用 @Order 控制执行顺序,Spring 注入成 List 后依次执行:
执行顺序(@Order) |
计算器 | 职责 |
|---|---|---|
| 8 | 秒杀 / 砍价 / 拼团 / 积分商城 | 活动专享价(互斥,取其一) |
| 10 | TradeDiscountActivityPriceCalculator |
限时折扣 + 会员 VIP 价(取优惠大的) |
| 20 | TradeRewardActivityPriceCalculator |
满减送 |
| 30 | TradeCouponPriceCalculator |
优惠券 |
| 40 | TradePointUsePriceCalculator |
积分抵现 |
| 50 | TradeDeliveryPriceCalculator |
运费 |
| 999 | TradePointGiveCalculator |
计算赠送积分(放最后,含运费后) |
新增一种优惠玩法,只需写一个新的 Calculator 实现类、标个 @Order,完全不用改原有代码——这就是责任链 + 开闭原则的威力。
4.2 价格计算入口:先校验库存,再跑责任链
TradePriceServiceImpl#calculateOrderPrice 是算价入口:先校验 SKU/SPU 存在且库存充足,再用 Helper 组装初始价格,最后让责任链逐个计算:
@Override
public TradePriceCalculateRespBO calculatePrice(TradePriceCalculateReqBO calculateReqBO) {
// 1. 校验 SKU、SPU 存在且库存充足
List<ProductSkuRespDTO> skuList = checkSkuList(calculateReqBO);
List<ProductSpuRespDTO> spuList = checkSpuList(skuList);
// 2. 组装初始价格(从 SKU 取 price/stock/properties),判定订单类型
TradePriceCalculateRespBO resp = TradePriceCalculatorHelper
.buildCalculateResp(calculateReqBO, spuList, skuList);
// 3. 责任链逐个计算(秒杀→折扣→满减→券→积分→运费→赠送积分)
priceCalculators.forEach(calculator -> calculator.calculate(calculateReqBO, resp));
// 4. 兜底:非积分订单实付必须 > 0
if (calculateReqBO.getPointActivityId() == null
&& resp.getPrice().getPayPrice() <= 0) {
throw exception(PRICE_CALCULATE_PAY_PRICE_ILLEGAL);
}
return resp;
}
4.3 优惠互斥:限时折扣 vs 会员 VIP 价,取优惠更大的
同一件商品既参加限时折扣、用户又是 VIP 会员时,不能两个优惠叠加,而是取更划算的那个。这段代码体现了"互斥优惠"的处理思路:
// TradeDiscountActivityPriceCalculator#calculate(@Order=10,仅普通订单)
Integer discountPrice = calculateActivityPrice(discountProduct, orderItem); // 限时折扣优惠额
Integer vipPrice = calculateVipPrice(level, orderItem); // 会员价优惠额
if (discountPrice > vipPrice) {
orderItem.setDiscountPrice(orderItem.getDiscountPrice() + discountPrice); // 折扣更优
} else {
orderItem.setVipPrice(vipPrice); // 会员价更优
}
// 重算该 item 应付,再汇总订单总价
TradePriceCalculatorHelper.recountPayPrice(orderItem);
TradePriceCalculatorHelper.recountAllPrice(result);
TradePriceCalculatorHelper 还负责把订单级的优惠券、积分按 payPrice 比例分摊到每个 SKU(dividePrice),这样发生部分退款时,能精确算出每个 SKU 该退多少钱。
五、后端核心实现:下单与库存防超卖
5.1 下单全流程:算价 → 建单 → 扣库存 → 删购物车 → 建支付单
订单创建是商城的核心事务,TradeOrderUpdateServiceImpl#createOrder 把多个步骤压在一个事务里完成:
@Transactional(rollbackFor = Exception.class)
public TradeOrderDO createOrder(Long userId, AppTradeOrderCreateReqVO createReqVO) {
// 1. 价格计算(跑责任链,含库存校验、选中校验)
TradePriceCalculateRespBO calculateRespBO = calculatePrice(userId, createReqVO);
// 2. 构建订单主表(status=UNPAID,生成订单号 no)与订单项
TradeOrderDO order = buildTradeOrder(userId, createReqVO, calculateRespBO);
List<TradeOrderItemDO> orderItems = buildTradeOrderItems(order, calculateRespBO);
// 3. 前置 Handler:扣减 SKU 库存(关键)
tradeOrderHandlers.forEach(h -> h.beforeOrderCreate(order, orderItems));
// 4. 落库
tradeOrderMapper.insert(order);
orderItems.forEach(item -> item.setOrderId(order.getId()));
tradeOrderItemMapper.insertBatch(orderItems);
// 5. 后置:用券/扣积分/删购物车/创建支付单
afterCreateTradeOrder(order, orderItems, createReqVO);
return order;
}
注意第 3 步和第 5 步——库存、优惠券、积分、购物车、支付单这些"副作用"都不是硬编码,而是通过 TradeOrderHandler 钩子分散到各个处理器,下面会讲。
5.2 库存扣减时机:下单即扣,不等支付
这是一个重要的产品决策。 RuoYi Office 选择"创建订单就扣减 SKU 库存",而不是"支付成功才扣"。原因是:如果等支付才扣,用户下单后库存还在,别人也能下单同一批库存,支付时才发现没货,体验极差。下单即扣(库存预占)能保证"下了单就一定有货",未支付超时取消时再把库存加回去。
扣减逻辑在 TradeProductSkuOrderHandler#beforeOrderCreate,把订单项数量取负数传给商品模块扣减:
// 下单:incrCount = -count(负数表示扣减)
@Override
public void beforeOrderCreate(TradeOrderDO order, List<TradeOrderItemDO> orderItems) {
productSkuApi.updateSkuStock(
TradeOrderConvert.INSTANCE.convertNegative(orderItems)).checkError();
}
// 取消订单时反向调用 convert(orderItems)(正数)把库存加回去
5.3 防超卖的关键:SKU 条件 UPDATE(CAS)
库存扣减如果用"先查再改",并发下必然超卖。RuoYi Office 把"判断 + 扣减"压成一条原子 SQL——UPDATE ... SET stock = stock - x WHERE id = ? AND stock >= x,扣得动返回 1,库存不足返回 0:
// ProductSkuServiceImpl#updateSkuStock(节选)
items.forEach(item -> {
if (item.getIncrCount() > 0) { // 入库/回滚:直接加
productSkuMapper.updateStockIncr(item.getId(), item.getIncrCount());
} else if (item.getIncrCount() < 0) { // 下单扣减:条件 UPDATE
int rows = productSkuMapper.updateStockDecr(item.getId(), item.getIncrCount());
if (rows == 0) { // 影响 0 行 = 库存不足,抛异常回滚
throw exception(SKU_STOCK_NOT_ENOUGH);
}
}
});
// 汇总后同步更新 SPU 冗余库存
productSpuService.updateSpuStock(spuStockIncrCounts);
updateStockDecr 的 MyBatis-Plus 写法核心是 .ge(ProductSkuDO::getStock, incrCount)——即 WHERE stock >= 扣减量,数据库行锁保证并发安全。关于库存并发的更多方案(悲观锁/乐观锁/Redis 预扣减)对比,可参考本系列《库存扣减的并发难题》。
5.4 购物车:故意做得很"轻"
购物车 CartDO(表 trade_cart)只有五个核心字段:user_id / spu_id / sku_id / count / selected。加购逻辑也很简单——同一 SKU 已存在就累加数量并自动选中,否则新增:
// CartServiceImpl#addCart
CartDO cart = cartMapper.selectByUserIdAndSkuId(userId, addReqVO.getSkuId());
ProductSkuRespDTO sku = checkProductSku(addReqVO.getSkuId(), count); // 校验库存
if (cart != null) { // 已有该 SKU → 数量累加 + 选中
cartMapper.updateById(new CartDO().setId(cart.getId())
.setSelected(true).setCount(cart.getCount() + count));
} else { // 新增购物车项
cartMapper.insert(new CartDO().setUserId(userId).setSelected(true)
.setSpuId(sku.getSpuId()).setSkuId(sku.getId()).setCount(count));
}
购物车不算营销价(源码 TODO 注释明确说明):选中、改数量都只更新这张表,真正的优惠在用户点"去结算"时才由价格责任链统一计算。这样购物车操作永远是 O(1) 的简单更新,不受营销规则变化影响。
六、RuoYi Office 的创新设计
6.1 SPU/SKU 双层 + 规格 JSON,一物多规格优雅落地
商品主体(SPU)与可售单元(SKU)分离,规格组合用 JSON 存在 SKU 上,SPU 冗余最低价和总库存。列表查 SPU 不用聚合 SKU,下单认 SKU 精确到规格,订单项再冗余规格快照——展示快、下单准、历史可追溯。
6.2 价格计算责任链,优惠玩法即插即用
把"算价"拆成一条 TradePriceCalculator 责任链,每种优惠是一个 @Order 排序的独立实现类。新增秒杀、拼团、新人券等玩法只加类不改老代码,是开闭原则在业务系统里的教科书式应用。
6.3 订单 Handler 钩子,副作用各司其职
下单/支付/取消/发货/收货等节点的副作用(扣库存、用券、扣积分、分佣、发通知)通过 TradeOrderHandler 生命周期钩子分散到 9 个处理器。核心状态流转代码保持干净,扩展业务只需实现钩子方法。
6.4 下单即扣库存 + 条件 UPDATE 防超卖
库存预占策略(下单即扣)保证"下了单就有货",配合 stock = stock - x WHERE stock >= x 的原子 SQL 防超卖,未支付超时取消自动回滚库存,兼顾体验与安全。
6.5 订单与支付单解耦,渠道随便换
交易订单 trade_order 只通过 pay_order_id 关联支付单,支付渠道(微信/支付宝/钱包)的接入、切换全在 yudao-module-pay 完成,商城侧不感知。详见本周《聚合支付中心设计》。
七、数据结构
7.1 表结构:product_spu(商品主体,节选)
| 字段 | 类型 | 说明 |
|---|---|---|
id |
bigint | 主键 |
name / keyword / introduction |
varchar | 名称 / 关键字 / 卖点 |
category_id / brand_id |
bigint | 分类(二级)/ 品牌 |
spec_type |
bit | 规格类型:false 单规格 / true 多规格 |
price / market_price / cost_price |
int | 最低价 / 市场价 / 成本价(单位:分,冗余) |
stock |
int | 总库存(下属 SKU 求和,冗余) |
status |
int | 状态(-1 回收/0 下架/1 上架) |
sales_count / browse_count |
int | 销量 / 浏览量 |
7.2 表结构:product_sku(库存量单位,节选)
| 字段 | 类型 | 说明 |
|---|---|---|
id |
bigint | 主键 |
spu_id |
bigint | 所属 SPU |
properties |
json | 规格属性(propertyId/Name + valueId/Name) |
price / market_price / cost_price |
int | 售价 / 市场价 / 成本价(单位:分) |
stock |
int | 真实库存(扣减发生在此) |
bar_code / pic_url / weight |
varchar/varchar/double | 条码 / 图 / 重量 |
7.3 表结构:trade_order(订单主表,节选)
| 字段 | 类型 | 说明 |
|---|---|---|
id / no |
bigint/varchar | 主键 / 订单号 |
type / status |
int | 订单类型 / 订单状态 |
user_id / pay_order_id |
bigint | 买家 / 关联支付单 |
total_price / pay_price |
int | 商品总价 / 实付(单位:分) |
discount_price / coupon_price / point_price |
int | 折扣 / 券 / 积分抵扣 |
delivery_price / vip_price |
int | 运费 / 会员优惠 |
receiver_name / receiver_mobile / receiver_detail_address |
varchar | 收货信息 |
7.4 设计要点
- 金额单位统一为"分":所有价格字段用
Integer存分,避免浮点误差,展示时除以 100。 - 订单项冗余商品快照:
spu_name / properties / pic_url下单时冗余,商品变更不影响历史订单。 - 多租户:所有表基于 Yudao 多租户体系,自动按租户隔离。
- 应付公式:
pay_price = total_price − coupon_price − point_price − discount_price + delivery_price + adjust_price − vip_price。
八、技术亮点总结
| 设计要点 | 实现方式 | 价值 |
|---|---|---|
| SPU/SKU 双层 | SPU 冗余最低价/库存,SKU 存真实价/规格 JSON | 一物多规格,展示快下单准 |
| 规格属性 JSON | JacksonTypeHandler 存 properties |
规格灵活,订单可追溯 |
| 两级分类约束 | CATEGORY_LEVEL=2 商品挂二级 |
分类清晰不滥用层级 |
| 轻量购物车 | 只存 user×sku×count×selected | 操作 O(1),不受营销影响 |
| 价格责任链 | TradePriceCalculator + @Order |
优惠即插即用,开闭原则 |
| 优惠分摊 | Helper dividePrice 按比例分摊 |
部分退款金额可精确算 |
| 下单即扣库存 | beforeOrderCreate 负数扣减 |
下单即有货,超时回滚 |
| 条件 UPDATE 防超卖 | stock = stock - x WHERE stock >= x |
原子扣减,扣不动则失败 |
| 订单 Handler 钩子 | 9 个 TradeOrderHandler |
副作用解耦,扩展不改核心 |
| 订单/支付解耦 | pay_order_id 关联 |
渠道可换,互不影响 |
九、快速体验
在线演示:http://ruoyioffice.com/web/(账号 admin / 密码 admin123)
操作路径:商城系统 → 商品管理 / 订单管理 / 售后管理 / 营销中心
推荐体验流程:
- 建分类:创建一级、二级商品分类(商品须挂二级)。
- 建商品:新建多规格商品,配置颜色/尺码规格项,生成 SKU 矩阵,逐行填价格、库存。
- 上架商品:把商品状态切到"上架"。
- 下单体验:在 App/H5 端将商品加入购物车,进入结算页观察价格明细(商品价、运费、实付)。
- 看库存扣减:下单后回后台看对应 SKU 库存减少。
- 超卖验证:把某 SKU 库存改为 1,并发下两单,观察第二单被"库存不足"拦截。
- 订单流转:后台对订单发货、用户确认收货,观察订单状态流转。
源码仓库:
| 平台 | 地址 |
|---|---|
| GitHub | https://github.com/yuqing2026/ruoyi-office |
| GitCode | https://gitcode.com/zhouzhongyan/ruoyi-office |
| Gitee | https://gitee.com/yqzy1688/ruoyi-office |
结语
商城系统的本质,是让"商品、选购、价格、订单、库存"五件事各自简单、彼此咬合。RuoYi Office 的答案是:用 SPU/SKU 双层模型承载一物多规格;用一张轻量购物车表记录选购;用一条可插拔的价格计算责任链把所有优惠按序算清;下单即原子扣减库存防超卖;最后让订单与支付、售后彻底解耦。
这套"双层商品 + 责任链算价 + 钩子解耦 + 原子扣减"的思路,不止适用于零售商城,也能迁移到票务、酒店、课程、SaaS 订阅等任何"有规格、要算价、扣库存"的交易场景。
如果你正在设计电商或交易系统,欢迎参考源码实现,也欢迎在评论区聊聊:你们的商城,优惠叠加是怎么算的?有没有被"算错价"坑过?
常见问题(FAQ)
RuoYi Office 的商城系统是开源免费的吗?
是。商城模块(yudao-module-mall)基于 RuoYi-Vue-Pro / Yudao 架构,后端 Spring Boot 3.5 + 前端 Vue3,开源可商用、无 license 限制,本地约 10 分钟即可启动体验。
SPU 和 SKU 有什么区别?
SPU(标准产品单元)是"一款商品",如"纯棉 T 恤";SKU(库存量单位)是这款商品的"某个具体规格组合",如"红色/XL"。用户看到的列表是 SPU,真正加购、扣库存、算成交价的是 SKU。一个 SPU 对应多个 SKU。
商城的优惠是怎么叠加计算的?
通过 TradePriceCalculator 价格计算责任链:秒杀/折扣/满减/优惠券/积分抵扣/运费各是一个独立计算器,用 @Order 控制执行顺序依次叠加。新增优惠玩法只需加一个计算器类,不改原有代码。
并发下单会超卖吗?
不会。库存扣减用原子 SQL —— UPDATE product_sku SET stock = stock - x WHERE id = ? AND stock >= x,扣得动返回 1、库存不足返回 0,Service 层按影响行数判断并回滚事务。且采用"下单即扣库存"策略,保证下单即有货。
库存是下单扣还是支付扣?
RuoYi Office 采用下单即扣(库存预占)。下单创建时就扣减 SKU 库存,保证"下了单就一定有货";若用户超时未支付,定时任务取消订单时再把库存加回去。
💡 想要体验 RuoYi Office 的强大功能?
🌐 在线演示:http://ruoyioffice.com/web/(账号 admin / admin123)
📦 源码仓库:GitHub | GitCode | Gitee
💬 技术咨询:添加微信 17156169080,备注「RuoYi Office」
⭐ 如果觉得不错,请给个 Star 支持一下!
更多推荐



所有评论(0)