ARTICLE DETAIL

资讯详情

深耕网站视觉设计与运营推广的一线实战洞察。

苍穹外卖【day8| 用户下单】

苍穹外卖【day8| 用户下单】 个人主页:一条泥憨鱼(欢迎各位大佬莅临)精选专栏传送门:❄️《数据结构》 ❄️《AI与Agent那些事》❄️《从0开始学计算机网络》 ❄️《后端开发》需求分析和设计代码开发第1层Controller — 接收请求OrderController.javaRestController(userOrderController) RequestMapping(/user/order) Api(tags 用户端订单相关接口) Slf4j public class OrderController { Autowired private OrderService orderService; PostMapping(/submit) ApiOperation(用户下单) public ResultOrderSubmitVO submit(RequestBody OrdersSubmitDTO ordersSubmitDTO){ log.info(用户下单参数为{}, ordersSubmitDTO); OrderSubmitVO orderSubmitVO orderService.submitOrder(ordersSubmitDTO); return Result.success(orderSubmitVO); } }PostMapping(/submit)— 接口路径为POST /user/order/submitRequestBody OrdersSubmitDTO— 前端 JSON 自动反序列化为 DTO 对象Slf4j— Lombok 自动生成log对象用于记录日志Result.success(...)— 统一响应格式包裹返回数据第2层DTO — 前端提交的数据结构OrdersSubmitDTO.javaData 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); ListShoppingCart 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 实体如payMethod、amount、remark等setNumber(String.valueOf(System.currentTimeMillis()))— 用时间戳作为订单号简单但有碰撞风险生产环境应用雪花算法orderMapper.insert(orders)对应的 XML 中配置了useGeneratedKeystrue keyPropertyid插入后orders.getId()会自动回填主键值后续步骤要用步骤 ④遍历购物车生成订单明细列表ListOrderDetail 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);下单成功后删除该用户购物车中的所有数据。对应的 SQLDELETE 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订单IDorderNumber订单号时间戳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 │ └──────────────────────────┬──────────────────────────────────────┘ │ ▼ ┌─────────────────────────────────────────────────────────────────┐ │ 前端收到 ResultOrderSubmitVO │ │ → 跳转支付页面 │ └─────────────────────────────────────────────────────────────────┘五、关键设计要点总结要点说明Transactional整个方法在一个事务中步骤③④⑤任意一步失败都会回滚保证数据一致性useGeneratedKeystrue插入 orders 后自动回填主键 ID供步骤④使用BeanUtils.copyProperties减少手写 setterDTO→Entity、Cart→OrderDetail 都复用了此工具BaseContext(ThreadLocal)从 JWT 令牌解析出的用户 ID 存入 ThreadLocalService 层直接取用无需层层传参批量插入insertBatch用一条 SQL 插入所有订单明细避免循环单条插入的性能问题Builder 模式OrderSubmitVO.builder()...build()链式构建对象代码清晰可读
返回列表