苍穹外卖【day8| 用户下单】
2026/8/28 7:34:25 网站建设 项目流程

🌈个人主页:一条泥憨鱼(欢迎各位大佬莅临)

🎬精选专栏传送门:

❄️《数据结构》 ❄️《AI与Agent那些事》

❄️《从0开始学计算机网络》 ❄️《后端开发》

需求分析和设计

代码开发

第1层:Controller — 接收请求

OrderController.java

@RestController("userOrderController") @RequestMapping("/user/order") @Api(tags = "用户端订单相关接口") @Slf4j public class OrderController { @Autowired private OrderService orderService; @PostMapping("/submit") @ApiOperation("用户下单") public Result<OrderSubmitVO> submit(@RequestBody OrdersSubmitDTO ordersSubmitDTO){ log.info("用户下单,参数为{}", ordersSubmitDTO); OrderSubmitVO orderSubmitVO = orderService.submitOrder(ordersSubmitDTO); return Result.success(orderSubmitVO); } }
  • @PostMapping("/submit")— 接口路径为POST /user/order/submit
  • @RequestBody OrdersSubmitDTO— 前端 JSON 自动反序列化为 DTO 对象
  • @Slf4j— Lombok 自动生成log对象,用于记录日志
  • Result.success(...)— 统一响应格式,包裹返回数据

第2层:DTO — 前端提交的数据结构

OrdersSubmitDTO.java

@Data public class OrdersSubmitDTO implements Serializable { private Long addressBookId; // 地址簿id private int payMethod; // 付款方式(1微信 2支付宝) private String remark; // 备注 private LocalDateTime estimatedDeliveryTime; // 预计送达时间 private Integer deliveryStatus; // 配送状态(1立即送出 0选择具体时间) private Integer tablewareNumber; // 餐具数量 private Integer tablewareStatus; // 餐具数量状态 private Integer packAmount; // 打包费 private BigDecimal amount; // 总金额 }

这里前端提交的是用户下单时选择的信息,不包含具体菜品(菜品从购物车取)。

第3层:Service 核心业务逻辑 — 分步详解

OrderServiceImpl.java 的submitOrder方法,整个流程分为6 个步骤

步骤 ①:校验地址簿是否存在
AddressBook addressBook = addressBookMapper.getById(ordersSubmitDTO.getAddressBookId()); if (addressBook == null) { throw new AddressBookBusinessException(MessageConstant.ADDRESS_BOOK_IS_NULL); }
  • 根据前端传来的addressBookId查询地址表
  • 如果地址不存在,抛出业务异常,阻止下单
  • 同时这个addressBook对象后面还要用来提取收货人姓名、手机号
步骤 ②:获取当前用户ID + 校验购物车
Long userId = BaseContext.getCurrentId(); ShoppingCart shoppingCart = new ShoppingCart(); shoppingCart.setUserId(userId); List<ShoppingCart> shoppingCartList = shoppingCartMapper.list(shoppingCart); if (shoppingCartList == null || shoppingCartList.size() == 0) { throw new ShoppingCartBusinessException(MessageConstant.SHOPPING_CART_IS_NULL); }
  • BaseContext.getCurrentId()是从ThreadLocal中取出当前登录用户的 ID(由 JWT 拦截器在请求进入时存入)
  • userId查询购物车表,得到该用户所有购物车商品
  • 购物车为空则抛出异常,阻止下单
  • 这个shoppingCartList后面要用来生成订单明细
步骤 ③:组装 Orders 对象并插入订单表
Orders orders = new Orders(); BeanUtils.copyProperties(ordersSubmitDTO, orders); // 拷贝DTO同名属性 orders.setOrderTime(LocalDateTime.now()); // 下单时间 = 当前时间 orders.setPayStatus(Orders.UN_PAID); // 支付状态 = 未支付(0) orders.setStatus(Orders.PENDING_PAYMENT); // 订单状态 = 待付款(1) orders.setNumber(String.valueOf(System.currentTimeMillis())); // 订单号 = 时间戳 orders.setPhone(addressBook.getPhone()); // 从地址中取手机号 orders.setConsignee(addressBook.getConsignee()); // 从地址中取收货人 orders.setUserId(userId); // 设置用户ID orderMapper.insert(orders); // 插入数据库
  • BeanUtils.copyProperties(ordersSubmitDTO, orders)— Spring 的工具类,将 DTO 中同名属性自动拷贝到 Orders 实体(如payMethodamountremark等)
  • setNumber(String.valueOf(System.currentTimeMillis()))— 用时间戳作为订单号,简单但有碰撞风险(生产环境应用雪花算法)
  • orderMapper.insert(orders)对应的 XML 中配置了useGeneratedKeys="true" keyProperty="id",插入后orders.getId()会自动回填主键值,后续步骤要用
步骤 ④:遍历购物车,生成订单明细列表
List<OrderDetail> orderDetailList = new ArrayList<>(); for (ShoppingCart cart : shoppingCartList) { OrderDetail orderDetail = new OrderDetail(); BeanUtils.copyProperties(cart, orderDetail); // 拷贝购物车属性 orderDetail.setOrderId(orders.getId()); // 关联订单ID orderDetailList.add(orderDetail); } orderDetailMapper.insertBatch(orderDetailList); // 批量插入
  • BeanUtils.copyProperties(cart, orderDetail)自动拷贝同名属性
  • orderDetail.setOrderId(orders.getId())手动设置的,因为购物车没有orderId,需要关联到刚创建的订单
  • insertBatch使用 MyBatis 的<foreach>动态拼接多行 VALUES,一条 SQL 插入所有明细
步骤 ⑤:清空购物车
shoppingCartMapper.deleteByUserId(userId);

下单成功后,删除该用户购物车中的所有数据。对应的 SQL:

DELETE FROM shopping_cart WHERE user_id = #{userId}
步骤 ⑥:封装 VO 返回结果
OrderSubmitVO orderSubmitVO = OrderSubmitVO.builder() .id(orders.getId()) // 订单ID .orderTime(orders.getOrderTime()) // 下单时间 .orderNumber(orders.getNumber()) // 订单号 .orderAmount(orders.getAmount()) // 订单金额 .build(); return orderSubmitVO;

OrderSubmitVO 返回给前端 4 个字段:

字段说明
id订单ID
orderNumber订单号(时间戳)
orderAmount订单总金额
orderTime下单时间

前端拿到这些数据后,跳转到支付页面。

四、完整数据流图

┌─────────────────────────────────────────────────────────────────┐ │ 前端 POST /user/order/submit │ │ Body: { addressBookId, payMethod, amount, remark, ... } │ └──────────────────────────┬──────────────────────────────────────┘ │ ▼ ┌─────────────────────────────────────────────────────────────────┐ │ OrderController.submit(OrdersSubmitDTO) │ │ → orderService.submitOrder(ordersSubmitDTO) │ └──────────────────────────┬──────────────────────────────────────┘ │ ▼ ┌─────────────────────────────────────────────────────────────────┐ │ OrderServiceImpl.submitOrder() [@Transactional 事务保护] │ │ │ │ ① 查地址 → addressBookMapper.getById(...) │ │ ├─ null → 抛异常 AddressBookBusinessException │ │ └─ 有值 → 继续 │ │ │ │ ② 查购物车 → shoppingCartMapper.list(userId) │ │ ├─ 空 → 抛异常 ShoppingCartBusinessException │ │ └─ 有数据 → 继续 │ │ │ │ ③ 组装 Orders → orderMapper.insert(orders) │ │ └─ orders.id 自动回填(自增主键) │ │ │ │ ④ 遍历购物车 → 生成 OrderDetail 列表 │ │ └─ orderDetailMapper.insertBatch(list) │ │ │ │ ⑤ 清空购物车 → shoppingCartMapper.deleteByUserId(userId) │ │ │ │ ⑥ 封装 OrderSubmitVO → return │ └──────────────────────────┬──────────────────────────────────────┘ │ ▼ ┌─────────────────────────────────────────────────────────────────┐ │ 前端收到 Result<OrderSubmitVO> │ │ → 跳转支付页面 │ └─────────────────────────────────────────────────────────────────┘

五、关键设计要点总结

要点说明
@Transactional整个方法在一个事务中,步骤③④⑤任意一步失败都会回滚,保证数据一致性
useGeneratedKeys="true"插入 orders 后自动回填主键 ID,供步骤④使用
BeanUtils.copyProperties减少手写 setter,DTO→Entity、Cart→OrderDetail 都复用了此工具
BaseContext(ThreadLocal)从 JWT 令牌解析出的用户 ID 存入 ThreadLocal,Service 层直接取用,无需层层传参
批量插入insertBatch用一条 SQL 插入所有订单明细,避免循环单条插入的性能问题
Builder 模式OrderSubmitVO.builder()...build()链式构建对象,代码清晰可读

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询