一、思维导图整体知识结构梳理
业务本质
数据库新增操作
- order 订单主表:保存订单整体信息
- orderItem 订单详情表:保存订单内每一件商品
数据库修改操作
- book 书籍表:扣减商品库存
- cart 购物车表:更新已选中购物项的结算状态
附加业务逻辑
- 超时未支付自动取消订单,采用 RabbitMQ 延时消息实现
核心注解 @Transactional
- 作用范围:标注在业务方法上
- 事务特性:方法内所有数据库操作,要么全部提交成功,一旦抛出异常则全部回滚
- 业务目的:防止部分 SQL 执行成功、部分失败,保障多表操作的数据一致性
- 注意事项:默认只捕获 runtimeException 运行时异常进行回滚,受异常传播配置影响
①接收参数 & 查询购物车数据
- 入参 CartDto
- cartIds:用户勾选的购物车 id 集合,用来确定哪些商品要下单
- addressId:用户选择的收货地址编号
- 执行 SQL:cartMapper.findListByIds (cartIds)
- 根据购物车 id 集合,批量查询多条购物车记录,封装为 CartVo 集合
- CartVo 封装信息:书籍 id、书籍名称、单价、购买数量、当前库存
②库存校验逻辑
- 需求:下单前校验每一件选中商品库存,库存不足直接拒绝下单
- 实现方式:Stream 流式处理
- filter 过滤:筛选出【购买数量 > 商品库存数量】的记录
- map 映射:提取库存不足的书籍名称
- collect 收集:把名称存入 List 集合
- 分支判断
- CollectionUtils 判断集合不为空:代表存在库存不足商品
- 直接返回 ResponseUtil,携带 FAIL 失败枚举,携带库存不足的书籍名称集合返回前端
- 终止后续下单流程,不会执行任何数据库写入操作
- 集合为空:全部商品库存充足,继续往下执行下单
- CollectionUtils 判断集合不为空:代表存在库存不足商品
③雪花 ID 生成订单编号
- 工具类:SnowflakeIdGenerator 雪花算法生成器
- 调用 sfg.nextId () 生成全局唯一 ID,转为字符串作为 orderNum 订单编号
- 结构:时间戳 + 机器 ID + 序列号
- 雪花 ID 特点:分布式场景多台服务器生成 ID 不重复,天然有序
- 作为订单主表唯一业务主键
④订单总金额计算
- 类型选择 BigDecimal
- 不能使用 double/float:浮点类型存在精度丢失,金额计算必须使用 BigDecimal
- 初始值:BigDecimal.ZERO
- 计算逻辑
- 循环遍历 CartVo 购物项集合
- 单项金额 = 商品单价 multiply (购买数量)
- totalPrice = totalPrice.add (单项金额),累加得到订单总金额
- 安全要点
- 前端页面只展示金额,后端必须重新完整计算金额
- 防止用户通过浏览器篡改前端提交的价格,造成资金漏洞
⑤封装并插入订单主表 order
- Order 实体字段
- orderNum:雪花算法生成的订单编号(业务唯一标识)
- totalPrice:后端计算得到的订单总金额
- userId:当前登录用户 ID,从 UserContext 获取
- addressId:前端传入的收货地址 ID
- createTime:订单创建时间 new Date ()
- status 订单状态:1 代表【待付款】
- UserContext 用户上下文
- 底层 ThreadLocal 线程本地存储
- 作用:在整个请求链路中,随时获取当前登录用户 id,不用每次都从头解析 token
- 执行 SQL:orderMapper.addOrder (order)
- 必须先插入订单主表,拿到 orderNum,才能给订单详情设置外键
⑥批量封装、新增订单详情 orderItem
- 循环 CartVo 集合,逐个构建 OrderItem 订单详情对象
- OrderItem 字段
- bookId:书籍 id
- bookName:书籍名称
- price:下单时商品单价(保存快照,后续商品改价不影响历史订单)
- buyCount:购买数量
- sumPrice:该商品小计金额
- orderId:关联订单编号 orderNum,绑定属于哪一笔订单
- createTime:详情记录创建时间
- 批量插入
- 使用 MyBatis 的 foreach 标签一次性插入多条详情记录
- orderItemMapper.batchAddOrderItem(orderItemList)
- 优势:减少多次数据库连接 IO,提升性能,相比循环单次 insert 效率更高
⑦批量更新数据库
- cartMapper.batchUpdateCartStatus(cartVoList)
- 批量修改本次下单对应的购物项状态,标记为已结算
- 业务效果:结算后的商品,不再展示在用户购物待结算列表
- bookMapper.batchUpdateBookCount(cartVoList)
- 批量扣减书籍库存,增加商品的已购买统计数量
- MyBatis foreach 批量 update,避免循环逐条更新数据库
⑧超时取消订单(业务预留逻辑)
- 业务需求:下单后 15 分钟未完成付款,自动取消订单,释放商品库存
- 技术方案:RabbitMQ 延时消息队列
- 下单成功发送延时消息,延迟 15 分钟投递
- 消费者接收消息,查询订单状态;如果依旧是待付款,则执行订单取消逻辑,回滚库存
工具类与规范类
- ResponseUtil:统一返回结果工具类,封装后端返回给前端的 JSON 结构
- ResponseEnum:响应枚举,统一管理状态码、提示信息(成功、库存不足、失败等)
- CollectionUtils:集合工具类,安全判断集合是否为空,避免空指针异常
雪花 ID(SnowflakeId)
概念
- 推特开源分布式 ID 生成算法
- 用于生成全局唯一的 ID,本项目用来生成订单号 orderNum
组成结构(64 位 Long 类型)
- 时间戳(毫秒):毫秒级时间,从指定基准时间开始的差值,保证 ID 整体递增
- 机器 ID:区分不同服务器 / 实例,集群环境防止重复
- 序列号:同一毫秒内,生成多个 ID 的自增序号;同一毫秒最多可生成 4096 个 ID
项目使用方式
- 工具类:SnowflakeIdGenerator
- 调用方法:sfg.nextId (),返回 long 类型 Id,转为字符串作为订单编号
核心特点
- 唯一性:分布式多台服务器生成 ID 不会重复
- 有序性:大体上按时间递增,数据库索引性能更好
- 高性能:内存生成,不依赖数据库,生成速度快
适用场景
- 订单号、商品 ID 等分布式系统唯一主键
- 替代数据库自增主键;自增 ID 在多库多表集群环境会冲突
优缺点
- 优点:不依赖数据库,生成效率高;自带时间信息,可以从 ID 反推生成时间
- 缺点:依赖服务器系统时间;服务器回拨时间,可能产生重复 ID
项目代码相关
SnowflakeIdGenerator sfg = new SnowflakeIdGenerator();
String orderNum = sfg.nextId().toString();
- 生成结果转为字符串,存入 order 订单表 orderNum 字段,作为业务订单编号
二、制作意图与规划
- 梳理订单下单完整业务链路:参数接收→库存校验→订单主表 + 详情入库→库存 & 购物车更新,完整掌握事务控制场景
- 重点理解分布式唯一 ID 雪花算法的原理、优缺点和业务落地,区分数据库自增主键的局限
- 掌握金额计算 BigDecimal 规范,理解前端传参不可信,后端必须重算金额的安全思想
- 熟悉 MyBatis 批量新增 / 更新,对比循环单条 SQL 的性能差异
- 了解延时消息 RabbitMQ 实现订单超时关闭的设计思路,为后续消息队列业务开发铺垫
三、当日学习总结
- 今天完整落地书城项目订单下单核心业务 ,打通从前端传入购物车 ID 到多表更新的全链路开发,理解电商下单场景是典型多表事务业务,必须使用
@Transactional保证原子性,一旦中间任何步骤异常,全部数据库操作回滚。 - 掌握下单前置校验逻辑:通过 Stream 流式表达式批量校验库存,提前拦截库存不足场景,避免无效数据库写入,学会集合工具类判断空集合,规避空指针。
- 理解雪花算法的原理、结构与适用场景,对比数据库自增 ID 的短板,学会在项目中使用雪花 ID 生成业务订单号,满足分布式环境下 ID 唯一需求,同时知晓时间回拨带来的潜在缺陷。
- 夯实金额开发规范:金融 / 订单场景必须使用 BigDecimal 做计算,严禁 double 浮点类型;建立后端校验思想,不能信任前端传来的价格,后端要独立重新计算订单总价,防止恶意篡改价格漏洞。
- 熟练 MyBatis foreach 批量操作,批量新增订单详情、批量更新购物车和书籍库存,相比循环单条 SQL,减少 IO 交互,提升接口性能。
- 了解订单超时关闭的业务设计,使用 RabbitMQ 延时消息实现 15 分钟未支付自动取消订单,为后续消息队列编码实现铺垫业务思路。
- 巩固 ThreadLocal 的 UserContext 用户上下文,理解在请求链路中传递登录用户信息的实现方式,不用反复解析 token 获取用户 ID。
