news 2026/9/21 23:53:16

3天搞定照烧鸡肉饭订单系统,图解原理避坑指南

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
3天搞定照烧鸡肉饭订单系统,图解原理避坑指南

3天搞定照烧鸡肉饭订单系统,图解原理避坑指南

面试被问原理答不上来?别慌。很多后端新人背八股文很溜,一让写个带库存扣减的下单接口就卡壳,尤其是这种看似简单的“照烧鸡肉饭”业务场景,底层涉及并发、状态机和数据一致性,光靠背是学不会的。今天不玩虚的,直接带你从零搭建一个高可用的订单服务,用图解原理的方式拆解核心逻辑。

项目目标与场景拆解

我们要做的不是一个简单的CRUD,而是一个具备生产级特性的订单模块。场景很简单:用户点击“立即购买”,后端接收请求,扣减库存,创建订单,返回订单号。但魔鬼在细节里。

假设照烧鸡肉饭每日限量100份。如果两个用户同时点击,传统写法 if stock > 0 然后 stock = stock - 1 会发生什么?两个线程都读到 stock=1,都判断通过,最后 stock=-1,超卖了。这是经典的竞态条件。

我们的目标:

  1. 防超卖:确保库存不出现负数。
  2. 幂等性:用户网络抖动重复点击,不会创建两个订单。
  3. 状态清晰:订单从“待支付”到“已取消”或“已完成”的状态流转要严谨。

很多教程直接上Redis,但对于初学者,先搞懂数据库层面的悲观锁和乐观锁,再引入缓存,理解才深刻。我们先用最基础的MySQL + Java Spring Boot来夯实地基。

目录结构与依赖配置

一个清晰的工程结构是代码可维护性的基础。以下是本项目核心目录,遵循分层架构原则:

src/main/java/com/food/order/
├── config/
│   └── DataSourceConfig.java    # 数据源配置,开启事务
├── controller/
│   └── OrderController.java     # REST接口入口
├── service/
│   ├── impl/
│   │   └── OrderServiceImpl.java # 核心业务逻辑
│   └── OrderService.java         # 接口定义
├── entity/
│   ├── Order.java                # 订单实体
│   └── Product.java              # 商品实体(照烧鸡肉饭)
├── mapper/
│   ├── OrderMapper.java          # 订单数据访问
│   └── ProductMapper.java        # 商品数据访问
└── exception/└── GlobalExceptionHandler.java # 全局异常处理

pom.xml 中除了基础的 Spring Boot Web 和 MyBatis Plus,务必加上 mysql-connector-jlombok。不要手动写 Getter/Setter,Lombok 能让代码更聚焦于业务逻辑本身。

核心代码实现与图解原理

这是本文的重头戏。我们将重点讲解如何用 乐观锁 解决超卖问题,并通过代码图解状态流转。

1. 数据库表结构设计

先建表。注意 version 字段,这是乐观锁的关键。

CREATE TABLE t_product (id BIGINT PRIMARY KEY AUTO_INCREMENT,name VARCHAR(50) NOT NULL COMMENT '商品名,如:照烧鸡肉饭',stock INT NOT NULL DEFAULT 0 COMMENT '库存',version INT NOT NULL DEFAULT 0 COMMENT '乐观锁版本号'
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;CREATE TABLE t_order (id BIGINT PRIMARY KEY AUTO_INCREMENT,user_id BIGINT NOT NULL,product_id BIGINT NOT NULL,status TINYINT NOT NULL COMMENT '0:待支付, 1:已支付, 2:已取消',create_time DATETIME DEFAULT CURRENT_TIMESTAMP
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

2. 商品Mapper:原子性扣减库存

很多新手喜欢先查后改:select stock; if(stock>0) update stock = stock-1。这在单线程下没问题,多线程下必死无疑。

正确的做法是将判断和更新合并为一条 SQL。MyBatis Plus 支持这种方式,或者写原生 XML。这里我们用原生 SQL 更直观。

@Mapper
public interface ProductMapper extends BaseMapper<Product> {/*** 乐观锁扣减库存* @param id 商品ID* @param expectedVersion 预期的当前版本号* @return 影响行数*/@Update("UPDATE t_product SET stock = stock - 1, version = version + 1 " +"WHERE id = #{id} AND stock > 0 AND version = #{expectedVersion}")int decreaseStock(@Param("id") Long id, @Param("expectedVersion") int expectedVersion);
}

图解原理: 想象 version 是一个计数器。

  1. 线程A读到 version=0。
  2. 线程B读到 version=0。
  3. 线程A执行更新:WHERE version=0,成功,version变为1。
  4. 线程B执行更新:WHERE version=0,但数据库里已经是1了,条件不匹配,更新行数为0。
  5. 业务层检测到行数为0,抛出“库存不足”或“操作冲突”异常,重试或直接失败。

这种机制避免了行级锁带来的性能损耗,适合高并发读多写少场景。

3. 订单Service:事务与状态机

核心业务逻辑在 Service 层。这里要特别注意事务边界。

@Service
public class OrderServiceImpl implements OrderService {@Autowiredprivate ProductMapper productMapper;@Autowiredprivate OrderMapper orderMapper;@Override@Transactional(rollbackFor = Exception.class)public Long createOrder(Long userId, Long productId) {// 1. 查询商品,获取当前版本号Product product = productMapper.selectById(productId);if (product == null) {throw new RuntimeException("商品不存在");}// 2. 尝试扣减库存int rows = productMapper.decreaseStock(productId, product.getVersion());if (rows == 0) {// 库存不足或并发冲突// 这里可以选择重试,但为了简单,直接抛出异常throw new BusinessException("抢购失败,库存不足或网络拥堵,请刷新重试");}// 3. 创建订单Order order = new Order();order.setUserId(userId);order.setProductId(productId);order.setStatus(0); // 待支付orderMapper.insert(order);return order.getId();}
}

避坑指南:

  • 事务回滚@Transactional 默认只回滚 RuntimeException。如果抛出的是 Checked Exception,事务不会回滚,导致库存扣了但订单没创建。务必加上 rollbackFor = Exception.class
  • 幂等性缺失:上面的代码如果用户快速双击,会创建两个订单。生产环境必须加分布式锁或唯一索引约束(如 user_id + product_id + status 的唯一索引,仅限待支付状态)。

运行与测试

代码写完了,怎么验证?不能只靠眼睛看。

  1. 启动服务:确保 MySQL 连接配置正确,启动 Spring Boot 应用。
  2. 接口测试:使用 Postman 或 Swagger。
    • URL: POST /api/orders
    • Body: {"userId": 1001, "productId": 1}
  3. 并发测试:这是检验“图解原理”是否落地的关键。
    • 使用 JMeter 或简单的 Python 脚本,发起 50 个并发请求,库存设为 10。
    • 预期结果:10 个请求成功,40 个请求返回“抢购失败”。
    • 数据库检查:SELECT stock, version FROM t_product WHERE id=1; 库存应为 0,version 应增加 10。

如果库存变成了 -40,说明你的 SQL 没写对,或者事务没生效。此时不要慌,检查 UPDATE 语句是否包含了 stock > 0 的条件。

优化扩展

基础版跑通了,但离生产还有距离。以下是三个进阶方向:

1. 引入 Redis 缓存库存

当 QPS 达到几千时,MySQL 的行锁会成为瓶颈。

  • 方案:将库存预热到 Redis。
  • 流程:请求先到 Redis 扣减,成功再异步写 MySQL。
  • 风险:Redis 和 MySQL 数据不一致。需要监听 Binlog 或使用 Canal 进行最终一致性补偿。

2. 状态机模式

目前的 setStatus(0) 是硬编码。如果状态多了(待支付、已支付、已发货、已完成、已退款),if-else 会爆炸。

  • 方案:使用 Spring Statemachine 或自定义状态机类。
  • 好处:状态转换规则集中管理,非法转换(如从“已取消”直接到“已发货”)会被自动拦截。

3. 异步化非核心逻辑

创建订单后,发短信、积分、日志记录等操作不要同步执行。

  • 方案:发送 MQ 消息(RabbitMQ/Kafka)。
  • 价值:降低接口响应时间,解耦业务逻辑。

小结

从照烧鸡肉饭的订单系统入手,我们不仅仅是在写一个买饭的功能,更是在实践分布式系统中的核心难题:并发控制数据一致性系统扩展性

记住,面试被问原理答不上来,往往是因为只背了结论,没跑过代码。当你亲手调试过 version 字段在并发下的变化,当你在 JMeter 压力下看到数据库的 CPU 飙升,你才能真正理解什么是“乐观锁”,什么是“事务边界”。

技术没有银弹,但有底线。这个底线就是:你的代码在并发下是否依然正确?

还有什么不懂的?评论区留言挨个回。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/21 23:53:15

5个坑让你白学中国诗词大会第二季避坑指南

5个坑让你白学中国诗词大会第二季避坑指南 版本升级后 API 全变了,你的旧代码直接报错 500,是不是觉得头大?别慌,这不是你代码写得烂,是官方接口动了刀。这篇《中国诗词大会第二季》的 避坑指南 ,就是帮你把那些藏在文档褶皱里的坑,一个个挖出来填平。…

作者头像 李华
网站建设 2026/9/21 23:53:02

5个实操细节,一文搞懂公司运营管理方案代码逻辑

5个实操细节,一文搞懂公司运营管理方案代码逻辑 复制来的代码跑不通,报错信息像天书,你盯着屏幕发呆,心里直骂街:这谁写的烂代码?别急,这种痛苦我太懂了。很多中小施工企业的负责人,拿到一份《公司运营管理方案》的电子档,里面夹杂着Python脚本用于自动生成月度报表,结果一运行就崩,根本不知道从哪下手调…

作者头像 李华
网站建设 2026/9/21 23:53:01

图解天津落户流程:版本升级API全变后的避坑指南

图解天津落户流程:版本升级API全变后的避坑指南 刚拿到天津户口指标的朋友,是不是瞬间感觉“版本升级后 API 全变了”?昨天还查得通的旧政策,今天一提交系统直接报错;原本以为简单的学历落户,现在却卡在社保月份的计算逻辑里。这种割裂感,就像你精心维护了五年的代码库,核心框架突然从 v2 迁移到…

作者头像 李华
网站建设 2026/9/21 23:52:16

3天搞定迷宫式油封源码解析,告别配置卡壳

3天搞定迷宫式油封源码解析,告别配置卡壳 配置环境就卡半天,这大概是很多转行做机械密封或者流体仿真工程师的噩梦。你以为迷宫式油封只是画个图、算个尺寸?错。当你打开那些复杂的 CFD 仿真代码或者流体动力学求解器源码时,发现光编译环境就让你抓狂,依赖库版本冲突、编译器报错、内存溢出,直接劝退。…

作者头像 李华
网站建设 2026/9/21 23:51:41

自己搭建服务器避坑指南:从入门到实战速查手册

自己搭建服务器避坑指南:从入门到实战速查手册 刚学完 Python 或 Java,看着满屏的 print("Hello World") 觉得爽,结果真让你搭个能跑起来的项目,脑子瞬间宕机?是不是对着空白的 IDE…

作者头像 李华
网站建设 2026/9/21 23:51:37

鱼虾蟹游戏下载源码解析:面试原理答不上来?3个最佳实践避坑指南

鱼虾蟹游戏下载源码解析:面试原理答不上来?3个最佳实践避坑指南 面试被问原理答不上来,是不少后端开发者的噩梦。 明明照着教程敲过代码,真到八股文环节就大脑空白。 想搞定这类问题,光看文档不够,得结合 最佳实践 去拆解底层逻辑。 很多新人喜欢从《鱼虾蟹游戏下载》这类休闲H5游戏入手练手。…

作者头像 李华