电商系统关键在边界、状态与并发控制:库存扣减需SQL校验+行锁,购物车用Redis Hash结构,关单用ZSET+Lua防重,价格必须服务端计算且数据库用DECIMAL。

Java 电商商城系统不是靠堆砌源码跑起来的,而是靠分清边界、管住状态、压住并发。直接拿所谓“完整源码”部署上线,90% 会卡在库存超卖、订单重复提交、支付状态不一致这三件事上。
为什么 Spring Boot + MyBatis 的「标准模板」撑不住真实下单流量
很多所谓“商城源码”用 @Transactional 包裹整个 createOrder() 方法,看似原子,实则埋雷:
– 数据库行锁只在事务内生效,但库存校验(SELECT)、扣减(UPDATE)和订单插入(INSERT)之间有时间窗口
– 如果并发请求同时读到库存=1,都会通过校验,接着都执行 UPDATE,最终库存变 -1
– MyBatis 的 selectOne() 不自动加锁,必须显式写 SELECT ... FOR UPDATE
实操建议:
– 库存扣减必须走单独的 deductStock(Long skuId, Integer quantity) 方法,SQL 写死 UPDATE product_sku SET stock = stock - #{quantity} WHERE id = #{skuId} AND stock >= #{quantity}
– 返回值判断 update 影响行数是否为 1,不是就抛 InsufficientStockException
– 禁止在事务里调用远程接口(如调用微信统一下单 API),否则事务拖太久,连接池耗尽
用户登录态和购物车怎么跨服务保持一致
单体项目把 HttpSession 当万能钥匙,拆成微服务后立刻失效。Redis 存 session 是常见解法,但容易踩两个坑:
– 用 StringRedisTemplate 存整个 CartDTO 对象,序列化用 JDK 默认方式,升级 JDK 或改类字段就反序列化失败
– 没设 TTL,用户登出没删 key,或长期未操作导致 Redis 内存爆满
立即学习“Java免费学习笔记(深入)”;
实操建议:
– 购物车数据用 Hash 结构存,HSET cart:{userId} sku:1001 2 sku:1002 1,每个 sku 单独增减,避免读全量再序列化
– 登录 token 存 Redis 用 SET login:token:{token} {userId} EX 1800,配合拦截器校验
– 所有跨服务调用带 requestId,日志用 MDC 打点,否则排查购物车丢了根本对不上链路
DESTOON B2B网站管理系统是一套完善的B2B(电子商务)行业门户解决方案。系统基于PHP+MySQL开发,采用B/S架构,模板与程序分离,源码开放。模型化的开发思路,可扩展或删除任何功能;创新的缓存技术与数据库设计,可负载千万级别数据容量及访问。
订单超时未支付怎么可靠关单
用 @Scheduled(fixedDelay = 60000) 扫表查 status = 'UNPAID' AND create_time ,看着简单,实际问题一堆:
– 扫描全表压力大,MySQL 慢查询飙升
– 多实例同时扫,重复关单
– 订单状态更新和库存回滚不在同一事务,可能关单成功但库存没加回去
实操建议:
– 建立独立的 order_timeout 表,下单时插入一条记录:INSERT INTO order_timeout(order_id, expire_time) VALUES (#{orderId}, DATE_ADD(NOW(), INTERVAL 30 MINUTE))
– 用 Redis ZSET 替代数据库扫描:ZADD order:timeout 1698765432000 order_123456(时间戳为 score)
– 每秒执行 ZRANGEBYSCORE order:timeout -inf #{nowTimestamp} 拿到期订单,用 Lua 脚本保证 ZREM + 关单逻辑 原子性
– 关单必须先改订单状态为 CLOSED,再异步发消息触发库存恢复,不能塞在同一个 DB 事务里
前端传来的 price 字段为什么绝不能直接入库
几乎所有“开源商城”都犯这个错:前端提交订单时带 price: 99.9,后端不做校验直接插进 order_item.price。结果是——活动价没生效、会员价被绕过、甚至被恶意篡改价格。
实操建议:
– 下单接口禁止接收任何价格类字段,price、discount、finalAmount 全部由服务端根据 SKU ID、用户等级、当前活动规则实时计算
– 查询商品时返回 currentPrice 和 originPrice,仅作展示用
– 支付回调里必须再次核对金额:if (notifyAmount != orderService.calcPayAmount(orderId)) { throw new InvalidPayAmountException(); }
– 数据库字段 price 类型必须是 DECIMAL(10,2),别用 Float 或 Double,否则 0.1 + 0.2 ≠ 0.3
// 示例:安全的库存扣减 Mapper XML 片段UPDATE product_sku SET stock = stock - #{quantity}, version = version + 1 WHERE id = #{skuId} AND stock >= #{quantity} AND version = #{version}
真正卡住电商系统的,从来不是“有没有源码”,而是每一笔订单背后那几毫秒的状态博弈。库存字段少个 FOR UPDATE,购物车用错 Redis 数据结构,超时任务没防重,价格没服务端兜底——这些地方不出事则已,一出就是资损。









