欢迎光临
我们一直在努力

【苍穹外卖 | 项目日记】第九天

目录

前言

一、今日完结任务

二、今日核心知识点总结

1. RESTful接口设计规范

2. 复杂分页查询的实现

三、遇到的问题及解决方案

问题1:@RequestBody注解使用错误导致400错误

问题描述

问题分析

解决方案

问题2:一对多表关系的复杂查询

问题描述

解决方案

四、今日实战收获

1. 用户端历史订单模块实现

1.1 查询历史订单(分页+状态筛选)

1.2 取消订单(状态校验+退款)

1.3 再来一单(购物车重载)

2. 商家端订单管理模块实现

2.1 订单搜索(多条件动态查询)

2.2 各状态订单数量统计

2.3 商家操作订单的完整流程

五、小知识点总结

1. 分页查询最佳实践

2. 注解使用规范

3. 表关系处理技巧

4. 状态流转校验

5. 批量操作优化

总结


前言

        今天是学习苍穹外卖项目的第九天,也是项目实战的重要阶段。按照课程安排,今天需要独立完成用户端历史订单模块和商家端订单管理模块的完整开发。

        这是我第一次独立完成如此复杂的业务逻辑,从查询历史订单到商家完成订单的整个闭环流程。虽然挑战重重,但收获满满,对于订单状态流转和分页查询有了更深的理解。


一、今日完结任务

  • ✅ 完成用户端历史订单模块:

    • 查询历史订单(分页+状态筛选)

    • 查询订单详情

    • 取消订单(状态校验+退款逻辑)

    • 再来一单(购物车重载)

  • ✅ 完成商家端订单管理模块:

    • 订单搜索(多条件模糊查询)

    • 各状态订单数量统计

    • 查询订单详情

    • 接单、拒单、取消订单

    • 派送订单、完成订单

  • ✅ 优化分页查询逻辑,避免冗余代码

  • ✅ 修复@RequestBody注解使用错误的问题

  • ✅ 处理一对多表关系的遍历查询逻辑


  • 二、今日核心知识点总结

    1. RESTful接口设计规范

    在今日开发中,深刻理解了RESTful API的设计原则:

    • GET:查询操作(查询订单、分页搜索)

    • POST:新增操作(再来一单)

    • PUT:更新操作(取消订单、接单、拒单等)

    • DELETE:删除操作(今日未涉及)

    2. 复杂分页查询的实现

    订单查询涉及多表关联和复杂条件筛选,核心实现包括:

    • 使用PageHelper进行物理分页

    • 动态SQL构建查询条件

    • 一对多关系的关联查询(订单+订单明细)

      • 分页结果的自定义封装


    三、遇到的问题及解决方案

    问题1:@RequestBody注解使用错误导致400错误

    问题描述

            在开发接单功能时,Controller方法定义为:

    @PutMapping("/confirm")
    public Result confirm(@RequestBody Long id) {
    // …
    }

    前端传参id=123,但后端始终返回400 Bad Request错误。

    问题分析

    经过排查发现,@RequestBody注解使用不当:

    • @RequestBody用于接收JSON格式的对象或复杂数据结构

    • 当只接收单个基础类型参数(Long、Integer、String)时,不应该使用@RequestBody

    • 前端传参格式与后端接收格式不匹配

    解决方案

    根据参数传递方式的不同,有3种解决方案:

    方案一:使用@PathVariable(推荐)

    @PutMapping("/confirm/{id}")
    public Result confirm(@PathVariable("id") Long id) {
    // …
    }
    // 请求路径:/admin/order/confirm/123

    方案二:使用@RequestParam

    @PutMapping("/confirm")
    public Result confirm(@RequestParam("id") Long id) {
    // …
    }
    // 请求路径:/admin/order/confirm?id=123

    方案三:封装DTO对象(适合多个参数)

    @Data
    public class OrdersConfirmDTO {
    private Long id;
    }

    @PutMapping("/confirm")
    public Result confirm(@RequestBody OrdersConfirmDTO dto) {
    Long id = dto.getId();
    // …
    }
    // 请求体:{"id":123}

    最终选择:考虑到接口语义清晰和RESTful规范,选择方案一(@PathVariable)。


    问题2:一对多表关系的复杂查询

    问题描述

            在查询历史订单时,需要同时返回订单基本信息和订单明细(商品列表)。订单表(orders)和订单明细表(order_detail)是一对多关系。

    解决方案

            采用分两次查询的方式,避免复杂的SQL关联:

    第一步:查询订单列表(分页)

    PageHelper.startPage(pageNum, pageSize);
    Page<Orders> page = orderMapper.pageQuery(ordersPageQueryDTO);

    第二步:遍历订单列表,查询每个订单的明细

    List<OrderVO> orderVOList = new ArrayList<>();
    for (Orders order : page.getResult()) {
    // 根据订单id查询订单明细
    List<OrderDetail> orderDetails = orderDetailMapper.getByOrderId(order.getId());

    // 封装到VO
    OrderVO orderVO = new OrderVO();
    BeanUtils.copyProperties(order, orderVO);
    orderVO.setOrderDetailList(orderDetails);
    orderVOList.add(orderVO);
    }

    优点:

  • SQL简单清晰,易于维护

  • 可以利用MyBatis的延迟加载优化性能

  • 避免多表关联查询的复杂性和性能问题


  • 四、今日实战收获

    1. 用户端历史订单模块实现

    1.1 查询历史订单(分页+状态筛选)

    核心代码:

    // OrderServiceImpl.java
    public PageResult pageQuery4User(int pageNum, int pageSize, Integer status) {
    // 设置分页
    PageHelper.startPage(pageNum, pageSize);

    // 构建查询条件
    OrdersPageQueryDTO ordersPageQueryDTO = new OrdersPageQueryDTO();
    ordersPageQueryDTO.setUserId(BaseContext.getCurrentId());
    ordersPageQueryDTO.setStatus(status);

    // 分页查询订单
    Page<Orders> page = orderMapper.pageQuery(ordersPageQueryDTO);

    // 封装结果
    List<OrderVO> list = new ArrayList<>();
    if (page != null && page.getTotal() > 0) {
    for (Orders orders : page) {
    // 查询订单明细
    List<OrderDetail> orderDetails = orderDetailMapper.getByOrderId(orders.getId());

    OrderVO orderVO = new OrderVO();
    BeanUtils.copyProperties(orders, orderVO);
    orderVO.setOrderDetailList(orderDetails);
    list.add(orderVO);
    }
    }
    return new PageResult(page.getTotal(), list);
    }

    SQL映射:

    <!– OrderMapper.xml –>
    <select id="pageQuery" resultType="Orders">
    select * from orders
    <where>
    <if test="userId != null">and user_id = #{userId}</if>
    <if test="status != null">and status = #{status}</if>
    <!– 其他条件… –>
    </where>
    order by order_time desc
    </select>

    1.2 取消订单(状态校验+退款)

    业务逻辑:

    • 待付款和待接单状态下可直接取消

    • 待接单状态下取消需要退款

    • 已接单和派送中状态下需要联系商家

    核心代码:

    public void userCancelById(Long id) throws Exception {
    // 查询订单
    Orders ordersDB = orderMapper.getById(id);

    // 状态校验
    if (ordersDB == null) {
    throw new OrderBusinessException(MessageConstant.ORDER_NOT_FOUND);
    }
    if (ordersDB.getStatus() > 2) { // 已接单、派送中、已完成
    throw new OrderBusinessException(MessageConstant.ORDER_STATUS_ERROR);
    }

    Orders orders = new Orders();
    orders.setId(ordersDB.getId());

    // 待接单状态取消需要退款
    if (ordersDB.getStatus().equals(Orders.TO_BE_CONFIRMED)) {
    // 调用微信退款接口(模拟)
    weChatPayUtil.refund(ordersDB.getNumber(), ordersDB.getNumber(),
    new BigDecimal(0.01), new BigDecimal(0.01));
    orders.setPayStatus(Orders.REFUND);
    }

    // 更新订单状态
    orders.setStatus(Orders.CANCELLED);
    orders.setCancelReason("用户取消");
    orders.setCancelTime(LocalDateTime.now());
    orderMapper.update(orders);
    }

    1.3 再来一单(购物车重载)

    核心思路: 将历史订单中的商品重新加入到当前用户的购物车中

    核心代码:

    public void repetition(Long id) {
    Long userId = BaseContext.getCurrentId();

    // 查询原订单明细
    List<OrderDetail> orderDetailList = orderDetailMapper.getByOrderId(id);

    // 转换为购物车对象
    List<ShoppingCart> shoppingCartList = orderDetailList.stream().map(x -> {
    ShoppingCart shoppingCart = new ShoppingCart();
    BeanUtils.copyProperties(x, shoppingCart, "id"); // 排除id字段
    shoppingCart.setUserId(userId);
    shoppingCart.setCreateTime(LocalDateTime.now());
    return shoppingCart;
    }).collect(Collectors.toList());

    // 批量插入购物车
    shoppingCartMapper.insertBatch(shoppingCartList);
    }


    2. 商家端订单管理模块实现

    2.1 订单搜索(多条件动态查询)

    支持条件:

    • 订单号(模糊查询)

    • 手机号(模糊查询)

    • 订单状态

    • 下单时间范围

    核心SQL:

    <select id="pageQuery" resultType="Orders">
    select * from orders
    <where>
    <if test="number != null and number!=''">
    and number like concat('%',#{number},'%')
    </if>
    <if test="phone != null and phone!=''">
    and phone like concat('%',#{phone},'%')
    </if>
    <if test="status != null">and status = #{status}</if>
    <if test="beginTime != null">and order_time >= #{beginTime}</if>
    <if test="endTime != null">and order_time <= #{endTime}</if>
    </where>
    order by order_time desc
    </select>

    2.2 各状态订单数量统计

    核心代码:

    public OrderStatisticsVO statistics() {
    // 分别查询不同状态的订单数量
    Integer toBeConfirmed = orderMapper.countStatus(Orders.TO_BE_CONFIRMED);
    Integer confirmed = orderMapper.countStatus(Orders.CONFIRMED);
    Integer deliveryInProgress = orderMapper.countStatus(Orders.DELIVERY_IN_PROGRESS);

    // 封装结果
    OrderStatisticsVO vo = new OrderStatisticsVO();
    vo.setToBeConfirmed(toBeConfirmed);
    vo.setConfirmed(confirmed);
    vo.setDeliveryInProgress(deliveryInProgress);
    return vo;
    }

    SQL:

    @Select("select count(id) from orders where status = #{status}")
    Integer countStatus(Integer status);

    2.3 商家操作订单的完整流程

    接单:

    public void confirm(OrdersConfirmDTO ordersConfirmDTO) {
    Orders orders = Orders.builder()
    .id(ordersConfirmDTO.getId())
    .status(Orders.CONFIRMED)
    .build();
    orderMapper.update(orders);
    }

    拒单:

    public void rejection(OrdersRejectionDTO dto) throws Exception {
    Orders ordersDB = orderMapper.getById(dto.getId());

    // 状态校验
    if (ordersDB == null || !ordersDB.getStatus().equals(Orders.TO_BE_CONFIRMED)) {
    throw new OrderBusinessException(MessageConstant.ORDER_STATUS_ERROR);
    }

    // 已支付的需要退款
    if (ordersDB.getPayStatus() == Orders.PAID) {
    weChatPayUtil.refund(ordersDB.getNumber(), ordersDB.getNumber(),
    new BigDecimal(0.01), new BigDecimal(0.01));
    }

    // 更新订单
    Orders orders = new Orders();
    orders.setId(ordersDB.getId());
    orders.setStatus(Orders.CANCELLED);
    orders.setRejectionReason(dto.getRejectionReason());
    orders.setCancelTime(LocalDateTime.now());
    orderMapper.update(orders);
    }

    派送订单:

    public void delivery(Long id) {
    Orders ordersDB = orderMapper.getById(id);

    // 校验:必须为已接单状态
    if (ordersDB == null || !ordersDB.getStatus().equals(Orders.CONFIRMED)) {
    throw new OrderBusinessException(MessageConstant.ORDER_STATUS_ERROR);
    }

    // 更新为派送中
    Orders orders = new Orders();
    orders.setId(id);
    orders.setStatus(Orders.DELIVERY_IN_PROGRESS);
    orderMapper.update(orders);
    }

    完成订单:

    public void complete(Long id) {
    Orders ordersDB = orderMapper.getById(id);

    // 校验:必须为派送中状态
    if (ordersDB == null || !ordersDB.getStatus().equals(Orders.DELIVERY_IN_PROGRESS)) {
    throw new OrderBusinessException(MessageConstant.ORDER_STATUS_ERROR);
    }

    // 更新为已完成
    Orders orders = new Orders();
    orders.setId(id);
    orders.setStatus(Orders.COMPLETED);
    orders.setDeliveryTime(LocalDateTime.now());
    orderMapper.update(orders);
    }


    五、小知识点总结

    1. 分页查询最佳实践

    // PageQuery方法在Service层的基本步骤:
    // 1. 启动分页
    PageHelper.startPage(pageNum, pageSize);

    // 2. 执行查询
    Page<Orders> page = orderMapper.pageQuery(queryDTO);

    // 3. 封装结果
    return new PageResult(page.getTotal(), page.getResult());

    // 注意点:返回值为Page<?>的SQL语句全部写成所有条件动态查询的SQL

    2. 注解使用规范

    • @PathVariable:从URL路径中获取参数(如:/orders/{id})

    • @RequestParam:从URL查询参数获取(如:?id=123)

    • @RequestBody:从请求体获取JSON格式的对象

    3. 表关系处理技巧

    • 一对多关系:采用"先查一,再遍历查多"的方式

    • 物理外键:通过外键字段进行关联查询

    • DTO/VO设计:为复杂查询结果设计专门的传输/视图对象

    4. 状态流转校验

    每个状态变更操作都需要:

  • 查询原始状态

  • 校验当前状态是否允许变更

  • 执行状态变更

  • 记录变更时间和原因

  • 5. 批量操作优化

    • 使用<foreach>标签实现SQL批量插入

    • 使用Stream API进行集合转换

    • 批量操作需要事务控制


    总结

            今天是我独立完成复杂业务模块开发的第一天,从用户端的历史订单查询到商家端的完整订单管理,实现了订单从创建到完成的完整生命周期管理。通过今天的实战,我不仅掌握了复杂分页查询、状态机流转、一对多关系处理等核心技术,还学会了如何调试和解决接口参数绑定、状态校验等常见问题。

            最大的收获是理解了业务驱动开发的重要性:每个功能点都需要先理解业务规则,然后设计合理的接口,最后编写清晰的代码实现。订单模块的复杂状态流转也让我认识到状态模式在实际业务中的应用价值。


    赞(0)
    未经允许不得转载:171主机测评 » 【苍穹外卖 | 项目日记】第九天
    分享到: 更多 (0)

    评论 抢沙发

    • 昵称 (必填)
    • 邮箱 (必填)
    • 网址