Day | 09 【苍穹外卖:订单售后业务】

🔥个人主页:北极的代码(欢迎来访)

🎬作者简介:java后端学习者

❄️个人专栏:苍穹外卖日记SSM框架深入JavaWeb

命运的结局尽可永在,不屈的挑战却不可须臾或缺!

前言:经过一段时间的学习,我们学了很多,今天是课后作业,主要完成用户和商家端的点餐的售后服务。

第一步:查询历史订单

3.1 创建DTO和VO

OrdersPageQueryDTO.java(分页查询条件)

java 复制代码
java

package com.sky.dto;

import lombok.Data;
import java.time.LocalDateTime;

@Data
public class OrdersPageQueryDTO {
    private Integer page;        // 页码
    private Integer pageSize;    // 每页大小
    private Long userId;         // 用户ID
    private Integer status;      // 订单状态
    private String number;       // 订单号
    private String phone;        // 手机号
    private LocalDateTime beginTime;  // 开始时间
    private LocalDateTime endTime;    // 结束时间
}

OrderVO.java(返回给前端的订单对象)

java 复制代码
java

package com.sky.vo;

import com.sky.entity.OrderDetail;
import lombok.Data;
import java.io.Serializable;
import java.math.BigDecimal;
import java.time.LocalDateTime;
import java.util.List;

@Data
public class OrderVO implements Serializable {
    private static final long serialVersionUID = 1L;
    
    // 订单基本信息
    private Long id;
    private String number;
    private Integer status;
    private BigDecimal amount;
    private LocalDateTime orderTime;
    private String consignee;
    private String phone;
    private String address;
    private Integer payStatus;
    private String cancelReason;
    private LocalDateTime cancelTime;
    private LocalDateTime deliveryTime;
    
    // 订单明细(用户端需要)
    private List<OrderDetail> orderDetailList;
    
    // 菜品信息字符串(商家端需要)
    private String orderDishes;
}

3.2 创建Mapper

OrderMapper.java

java 复制代码
java

package com.sky.mapper;

import com.github.pagehelper.Page;
import com.sky.dto.OrdersPageQueryDTO;
import com.sky.entity.Orders;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Select;

@Mapper
public interface OrderMapper {
    
    /**
     * 分页条件查询订单
     */
    Page<Orders> pageQuery(OrdersPageQueryDTO ordersPageQueryDTO);
    
    /**
     * 根据ID查询订单
     */
    @Select("select * from orders where id = #{id}")
    Orders getById(Long id);
    
    /**
     * 更新订单
     */
    void update(Orders orders);
}

OrderMapper.xml

XML 复制代码
xml

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" 
    "http://mybatis.org/dtd/mybatis-3-mapper.dtd" >
<mapper namespace="com.sky.mapper.OrderMapper">
    
    <update id="update" parameterType="Orders">
        UPDATE orders
        <set>
            <if test="status != null">status = #{status},</if>
            <if test="payStatus != null">pay_status = #{payStatus},</if>
            <if test="cancelReason != null">cancel_reason = #{cancelReason},</if>
            <if test="cancelTime != null">cancel_time = #{cancelTime},</if>
            <if test="deliveryTime != null">delivery_time = #{deliveryTime},</if>
        </set>
        WHERE id = #{id}
    </update>
    
    <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>
            <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="beginTime != null">
                AND order_time &gt;= #{beginTime}
            </if>
            <if test="endTime != null">
                AND order_time &lt;= #{endTime}
            </if>
        </where>
        ORDER BY order_time DESC
    </select>
    
</mapper>

OrderDetailMapper.java

java 复制代码
java

package com.sky.mapper;

import com.sky.entity.OrderDetail;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Select;
import java.util.List;

@Mapper
public interface OrderDetailMapper {
    
    /**
     * 根据订单ID查询订单明细
     */
    @Select("SELECT * FROM order_detail WHERE order_id = #{orderId}")
    List<OrderDetail> getByOrderId(Long orderId);
    
    /**
     * 批量插入订单明细
     */
    void insertBatch(List<OrderDetail> orderDetailList);
}

3.3 创建Service

OrderService.java

java 复制代码
java

package com.sky.service;

import com.sky.dto.OrdersPageQueryDTO;
import com.sky.result.PageResult;
import com.sky.vo.OrderVO;

public interface OrderService {
    
    /**
     * 用户端分页查询历史订单
     */
    PageResult pageQuery4User(Integer page, Integer pageSize, Integer status);
    
    /**
     * 查询订单详情
     */
    OrderVO details(Long id);
    
    /**
     * 用户取消订单
     */
    void userCancelById(Long id);
    
    /**
     * 再来一单
     */
    void repetition(Long id);
}

OrderServiceImpl.java

java 复制代码
java

package com.sky.service.impl;

import com.github.pagehelper.Page;
import com.github.pagehelper.PageHelper;
import com.sky.constant.MessageConstant;
import com.sky.context.BaseContext;
import com.sky.dto.OrdersPageQueryDTO;
import com.sky.entity.OrderDetail;
import com.sky.entity.Orders;
import com.sky.exception.OrderBusinessException;
import com.sky.mapper.OrderDetailMapper;
import com.sky.mapper.OrderMapper;
import com.sky.result.PageResult;
import com.sky.service.OrderService;
import com.sky.vo.OrderVO;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.List;

@Service
@Slf4j
public class OrderServiceImpl implements OrderService {
    
    @Autowired
    private OrderMapper orderMapper;
    
    @Autowired
    private OrderDetailMapper orderDetailMapper;
    
    /**
     * 用户端分页查询历史订单
     */
    @Override
    public PageResult pageQuery4User(Integer page, Integer pageSize, Integer status) {
        // 1. 设置分页
        PageHelper.startPage(page, pageSize);
        
        // 2. 构建查询条件
        OrdersPageQueryDTO queryDTO = new OrdersPageQueryDTO();
        queryDTO.setUserId(BaseContext.getCurrentId());
        queryDTO.setStatus(status);
        
        // 3. 执行查询
        Page<Orders> pageResult = orderMapper.pageQuery(queryDTO);
        
        // 4. 转换数据
        List<OrderVO> list = new ArrayList<>();
        if (pageResult != null && pageResult.getTotal() > 0) {
            for (Orders orders : pageResult) {
                // 查询订单明细
                List<OrderDetail> details = orderDetailMapper.getByOrderId(orders.getId());
                
                // 组装VO
                OrderVO orderVO = new OrderVO();
                BeanUtils.copyProperties(orders, orderVO);
                orderVO.setOrderDetailList(details);
                list.add(orderVO);
            }
        }
        
        // 5. 返回结果
        return new PageResult(pageResult.getTotal(), list);
    }
    
    /**
     * 查询订单详情
     */
    @Override
    public OrderVO details(Long id) {
        // 1. 查询订单
        Orders orders = orderMapper.getById(id);
        
        // 2. 查询订单明细
        List<OrderDetail> details = orderDetailMapper.getByOrderId(id);
        
        // 3. 组装VO
        OrderVO orderVO = new OrderVO();
        BeanUtils.copyProperties(orders, orderVO);
        orderVO.setOrderDetailList(details);
        
        return orderVO;
    }
    
    /**
     * 用户取消订单
     */
    @Override
    @Transactional
    public void userCancelById(Long id) {
        // 1. 查询订单
        Orders ordersDB = orderMapper.getById(id);
        
        // 2. 校验订单是否存在
        if (ordersDB == null) {
            throw new OrderBusinessException(MessageConstant.ORDER_NOT_FOUND);
        }
        
        // 3. 校验订单状态(只有1和2可以取消)
        if (ordersDB.getStatus() > Orders.TO_BE_CONFIRMED) {
            throw new OrderBusinessException(MessageConstant.ORDER_STATUS_ERROR);
        }
        
        // 4. 创建更新对象
        Orders orders = new Orders();
        orders.setId(ordersDB.getId());
        
        // 5. 待接单状态需要退款
        if (ordersDB.getStatus().equals(Orders.TO_BE_CONFIRMED)) {
            // TODO: 调用微信支付退款接口
            orders.setPayStatus(Orders.REFUND);
        }
        
        // 6. 更新订单状态
        orders.setStatus(Orders.CANCELLED);
        orders.setCancelReason("用户取消");
        orders.setCancelTime(LocalDateTime.now());
        
        // 7. 执行更新
        orderMapper.update(orders);
    }
    
    /**
     * 再来一单
     */
    @Override
    @Transactional
    public void repetition(Long id) {
        // 1. 获取当前用户ID
        Long userId = BaseContext.getCurrentId();
        
        // 2. 查询原订单明细
        List<OrderDetail> orderDetails = orderDetailMapper.getByOrderId(id);
        
        // 3. 转换为购物车对象
        List<ShoppingCart> shoppingCartList = orderDetails.stream().map(x -> {
            ShoppingCart cart = new ShoppingCart();
            BeanUtils.copyProperties(x, cart, "id");
            cart.setUserId(userId);
            cart.setCreateTime(LocalDateTime.now());
            return cart;
        }).collect(Collectors.toList());
        
        // 4. 批量插入购物车
        shoppingCartMapper.insertBatch(shoppingCartList);
    }
}

3.4 创建Controller

UserOrderController.java

java 复制代码
java

package com.sky.controller.user;

import com.sky.result.PageResult;
import com.sky.result.Result;
import com.sky.service.OrderService;
import com.sky.vo.OrderVO;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;

@RestController("userOrderController")
@RequestMapping("/user/order")
@Api(tags = "用户端订单接口")
@Slf4j
public class OrderController {
    
    @Autowired
    private OrderService orderService;
    
    /**
     * 查询历史订单
     */
    @GetMapping("/historyOrders")
    @ApiOperation("历史订单查询")
    public Result<PageResult> page(
            @RequestParam(defaultValue = "1") Integer page,
            @RequestParam(defaultValue = "10") Integer pageSize,
            Integer status) {
        log.info("查询历史订单:page={}, pageSize={}, status={}", page, pageSize, status);
        PageResult pageResult = orderService.pageQuery4User(page, pageSize, status);
        return Result.success(pageResult);
    }
    
    /**
     * 查询订单详情
     */
    @GetMapping("/orderDetail/{id}")
    @ApiOperation("查询订单详情")
    public Result<OrderVO> details(@PathVariable("id") Long id) {
        log.info("查询订单详情:{}", id);
        OrderVO orderVO = orderService.details(id);
        return Result.success(orderVO);
    }
    
    /**
     * 取消订单
     */
    @PutMapping("/cancel/{id}")
    @ApiOperation("取消订单")
    public Result cancel(@PathVariable("id") Long id) {
        log.info("取消订单:{}", id);
        orderService.userCancelById(id);
        return Result.success();
    }
    
    /**
     * 再来一单
     */
    @PostMapping("/repetition/{id}")
    @ApiOperation("再来一单")
    public Result repetition(@PathVariable("id") Long id) {
        log.info("再来一单:{}", id);
        orderService.repetition(id);
        return Result.success();
    }
}

四、第二步:集成百度地图配送范围校验

4.1 配置 application.yml

java 复制代码
yaml

# 在原有配置基础上添加
sky:
  shop:
    address: 北京市朝阳区建国路93号万达广场  # 替换为实际店铺地址
  baidu:
    ak: xxxxxxxxxxxxxxxxxxxxxxxx  # 替换为你的百度地图AK

4.2 创建HttpClientUtil工具类

java 复制代码
java

package com.sky.utils;

import org.apache.http.client.config.RequestConfig;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.utils.URIBuilder;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.util.EntityUtils;
import java.io.IOException;
import java.net.URI;
import java.util.Map;

public class HttpClientUtil {
    
    private static final int CONNECT_TIMEOUT = 5000;
    private static final int SOCKET_TIMEOUT = 5000;
    
    public static String doGet(String url, Map<String, String> param) {
        CloseableHttpClient httpClient = HttpClients.createDefault();
        CloseableHttpResponse response = null;
        String resultString = "";
        
        try {
            URIBuilder builder = new URIBuilder(url);
            if (param != null) {
                for (Map.Entry<String, String> entry : param.entrySet()) {
                    builder.addParameter(entry.getKey(), entry.getValue());
                }
            }
            URI uri = builder.build();
            
            HttpGet httpGet = new HttpGet(uri);
            RequestConfig requestConfig = RequestConfig.custom()
                    .setConnectTimeout(CONNECT_TIMEOUT)
                    .setSocketTimeout(SOCKET_TIMEOUT)
                    .build();
            httpGet.setConfig(requestConfig);
            
            response = httpClient.execute(httpGet);
            if (response.getStatusLine().getStatusCode() == 200) {
                resultString = EntityUtils.toString(response.getEntity(), "UTF-8");
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            try {
                if (response != null) {
                    response.close();
                }
                httpClient.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
        return resultString;
    }
}

4.3 在OrderServiceImpl中添加校验方法

OrderServiceImpl 中添加:

java 复制代码
java

@Value("${sky.shop.address}")
private String shopAddress;

@Value("${sky.baidu.ak}")
private String ak;

/**
 * 检查收货地址是否超出配送范围
 */
private void checkOutOfRange(String address) {
    if (address == null || address.trim().isEmpty()) {
        throw new OrderBusinessException("收货地址不能为空");
    }
    
    Map<String, String> map = new HashMap<>();
    map.put("output", "json");
    map.put("ak", ak);
    
    try {
        // 1. 获取店铺经纬度
        map.put("address", shopAddress);
        String shopResult = HttpClientUtil.doGet(
            "https://api.map.baidu.com/geocoding/v3", map);
        
        JSONObject shopJson = JSON.parseObject(shopResult);
        if (!"0".equals(shopJson.getString("status"))) {
            throw new OrderBusinessException("店铺地址解析失败");
        }
        
        JSONObject shopLocation = shopJson.getJSONObject("result")
                                          .getJSONObject("location");
        String shopLngLat = shopLocation.getString("lat") + "," + 
                           shopLocation.getString("lng");
        
        // 2. 获取用户地址经纬度
        map.put("address", address);
        String userResult = HttpClientUtil.doGet(
            "https://api.map.baidu.com/geocoding/v3", map);
        
        JSONObject userJson = JSON.parseObject(userResult);
        if (!"0".equals(userJson.getString("status"))) {
            throw new OrderBusinessException("收货地址解析失败");
        }
        
        JSONObject userLocation = userJson.getJSONObject("result")
                                          .getJSONObject("location");
        String userLngLat = userLocation.getString("lat") + "," + 
                           userLocation.getString("lng");
        
        // 3. 计算驾车距离
        map.clear();
        map.put("origin", shopLngLat);
        map.put("destination", userLngLat);
        map.put("steps_info", "0");
        map.put("ak", ak);
        
        String drivingResult = HttpClientUtil.doGet(
            "https://api.map.baidu.com/directionlite/v1/driving", map);
        
        JSONObject drivingJson = JSON.parseObject(drivingResult);
        if (!"0".equals(drivingJson.getString("status"))) {
            throw new OrderBusinessException("配送路线规划失败");
        }
        
        JSONObject result = drivingJson.getJSONObject("result");
        JSONArray routes = result.getJSONArray("routes");
        Integer distance = routes.getJSONObject(0).getInteger("distance");
        
        // 4. 判断是否超出范围(5000米)
        if (distance > 5000) {
            throw new OrderBusinessException("超出配送范围");
        }
        
        log.info("配送距离:{}米,在配送范围内", distance);
        
    } catch (OrderBusinessException e) {
        throw e;
    } catch (Exception e) {
        log.error("配送范围校验失败", e);
        throw new OrderBusinessException("配送范围校验失败");
    }
}

4.4 在提交订单时调用校验

找到 submitOrder 方法,在开头添加:

java 复制代码
java

@Transactional
public Long submitOrder(OrdersSubmitDTO ordersSubmitDTO) {
    // 1. 校验收货地址是否超出配送范围
    checkOutOfRange(ordersSubmitDTO.getAddress());
    
    // 2. 原有的下单逻辑
    // ...
}

常见问题解决

问题1:PageHelper分页不生效

解决 :确保 PageHelper.startPage() 在 Mapper 查询之前调用

问题2:订单状态常量值不正确

解决:检查 Orders 类中的常量定义

问题3:百度地图API调用失败

解决:检查AK是否正确,地址格式是否规范

问题4:N+1查询问题

解决:使用批量查询优化

管理端下单售后服务:

创建DTO

OrdersConfirmDTO.java(接单DTO)

java 复制代码
java

package com.sky.dto;

import lombok.Data;
import java.io.Serializable;

@Data
public class OrdersConfirmDTO implements Serializable {
    private static final long serialVersionUID = 1L;
    
    private Long id;  // 订单ID
}

OrdersRejectionDTO.java(拒单DTO)

java 复制代码
java

package com.sky.dto;

import lombok.Data;
import java.io.Serializable;

@Data
public class OrdersRejectionDTO implements Serializable {
    private static final long serialVersionUID = 1L;
    
    private Long id;           // 订单ID
    private String rejectionReason;  // 拒单原因
}

OrdersCancelDTO.java(商家取消订单DTO)

java 复制代码
java

package com.sky.dto;

import lombok.Data;
import java.io.Serializable;

@Data
public class OrdersCancelDTO implements Serializable {
    private static final long serialVersionUID = 1L;
    
    private Long id;               // 订单ID
    private String cancelReason;   // 取消原因
}

创建VO

OrderStatisticsVO.java(订单统计VO)

java 复制代码
java

package com.sky.vo;

import lombok.Data;
import java.io.Serializable;

@Data
public class OrderStatisticsVO implements Serializable {
    private static final long serialVersionUID = 1L;
    
    private Integer toBeConfirmed;      // 待接单数量
    private Integer confirmed;          // 待派送数量
    private Integer deliveryInProgress; // 派送中数量
}

三、Service层实现

3.1 扩展OrderService接口

java 复制代码
java

package com.sky.service;

import com.sky.dto.*;
import com.sky.result.PageResult;
import com.sky.vo.OrderStatisticsVO;
import com.sky.vo.OrderVO;

public interface OrderService {
    
    // ========== 用户端 ==========
    PageResult pageQuery4User(Integer page, Integer pageSize, Integer status);
    OrderVO details(Long id);
    void userCancelById(Long id);
    void repetition(Long id);
    
    // ========== 管理端 ==========
    /**
     * 条件搜索订单
     */
    PageResult conditionSearch(OrdersPageQueryDTO ordersPageQueryDTO);
    
    /**
     * 各状态订单数量统计
     */
    OrderStatisticsVO statistics();
    
    /**
     * 接单
     */
    void confirm(OrdersConfirmDTO ordersConfirmDTO);
    
    /**
     * 拒单
     */
    void rejection(OrdersRejectionDTO ordersRejectionDTO) throws Exception;
    
    /**
     * 商家取消订单
     */
    void cancel(OrdersCancelDTO ordersCancelDTO) throws Exception;
    
    /**
     * 派送订单
     */
    void delivery(Long id);
    
    /**
     * 完成订单
     */
    void complete(Long id);
}

3.2 OrderServiceImpl完整实现

java 复制代码
java

package com.sky.service.impl;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import com.github.pagehelper.Page;
import com.github.pagehelper.PageHelper;
import com.sky.constant.MessageConstant;
import com.sky.context.BaseContext;
import com.sky.dto.*;
import com.sky.entity.OrderDetail;
import com.sky.entity.Orders;
import com.sky.entity.ShoppingCart;
import com.sky.exception.OrderBusinessException;
import com.sky.mapper.OrderDetailMapper;
import com.sky.mapper.OrderMapper;
import com.sky.mapper.ShoppingCartMapper;
import com.sky.result.PageResult;
import com.sky.service.OrderService;
import com.sky.utils.HttpClientUtil;
import com.sky.vo.OrderStatisticsVO;
import com.sky.vo.OrderVO;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.CollectionUtils;

import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

@Service
@Slf4j
public class OrderServiceImpl implements OrderService {
    
    @Autowired
    private OrderMapper orderMapper;
    
    @Autowired
    private OrderDetailMapper orderDetailMapper;
    
    @Autowired
    private ShoppingCartMapper shoppingCartMapper;
    
    @Value("${sky.shop.address}")
    private String shopAddress;
    
    @Value("${sky.baidu.ak}")
    private String ak;
    
    // ==================== 用户端方法 ====================
    
    @Override
    public PageResult pageQuery4User(Integer page, Integer pageSize, Integer status) {
        PageHelper.startPage(page, pageSize);
        
        OrdersPageQueryDTO queryDTO = new OrdersPageQueryDTO();
        queryDTO.setUserId(BaseContext.getCurrentId());
        queryDTO.setStatus(status);
        
        Page<Orders> pageResult = orderMapper.pageQuery(queryDTO);
        
        List<OrderVO> list = new ArrayList<>();
        if (pageResult != null && pageResult.getTotal() > 0) {
            for (Orders orders : pageResult) {
                List<OrderDetail> details = orderDetailMapper.getByOrderId(orders.getId());
                OrderVO orderVO = new OrderVO();
                BeanUtils.copyProperties(orders, orderVO);
                orderVO.setOrderDetailList(details);
                list.add(orderVO);
            }
        }
        
        return new PageResult(pageResult.getTotal(), list);
    }
    
    @Override
    public OrderVO details(Long id) {
        Orders orders = orderMapper.getById(id);
        List<OrderDetail> details = orderDetailMapper.getByOrderId(id);
        
        OrderVO orderVO = new OrderVO();
        BeanUtils.copyProperties(orders, orderVO);
        orderVO.setOrderDetailList(details);
        
        return orderVO;
    }
    
    @Override
    @Transactional
    public void userCancelById(Long id) {
        Orders ordersDB = orderMapper.getById(id);
        
        if (ordersDB == null) {
            throw new OrderBusinessException(MessageConstant.ORDER_NOT_FOUND);
        }
        
        if (ordersDB.getStatus() > Orders.TO_BE_CONFIRMED) {
            throw new OrderBusinessException(MessageConstant.ORDER_STATUS_ERROR);
        }
        
        Orders orders = new Orders();
        orders.setId(ordersDB.getId());
        
        if (ordersDB.getStatus().equals(Orders.TO_BE_CONFIRMED)) {
            // TODO: 调用微信支付退款接口
            orders.setPayStatus(Orders.REFUND);
        }
        
        orders.setStatus(Orders.CANCELLED);
        orders.setCancelReason("用户取消");
        orders.setCancelTime(LocalDateTime.now());
        
        orderMapper.update(orders);
    }
    
    @Override
    @Transactional
    public void repetition(Long id) {
        Long userId = BaseContext.getCurrentId();
        List<OrderDetail> orderDetails = orderDetailMapper.getByOrderId(id);
        
        List<ShoppingCart> shoppingCartList = orderDetails.stream().map(x -> {
            ShoppingCart cart = new ShoppingCart();
            BeanUtils.copyProperties(x, cart, "id");
            cart.setUserId(userId);
            cart.setCreateTime(LocalDateTime.now());
            return cart;
        }).collect(Collectors.toList());
        
        shoppingCartMapper.insertBatch(shoppingCartList);
    }
    
    // ==================== 管理端方法 ====================
    
    /**
     * 条件搜索订单
     */
    @Override
    public PageResult conditionSearch(OrdersPageQueryDTO ordersPageQueryDTO) {
        PageHelper.startPage(ordersPageQueryDTO.getPage(), ordersPageQueryDTO.getPageSize());
        
        Page<Orders> page = orderMapper.pageQuery(ordersPageQueryDTO);
        
        List<OrderVO> orderVOList = getOrderVOList(page);
        
        return new PageResult(page.getTotal(), orderVOList);
    }
    
    /**
     * 将订单列表转换为OrderVO列表(生成菜品信息字符串)
     */
    private List<OrderVO> getOrderVOList(Page<Orders> page) {
        List<OrderVO> orderVOList = new ArrayList<>();
        List<Orders> ordersList = page.getResult();
        
        if (!CollectionUtils.isEmpty(ordersList)) {
            for (Orders orders : ordersList) {
                OrderVO orderVO = new OrderVO();
                BeanUtils.copyProperties(orders, orderVO);
                
                // 生成菜品信息字符串
                String orderDishes = getOrderDishesStr(orders);
                orderVO.setOrderDishes(orderDishes);
                
                orderVOList.add(orderVO);
            }
        }
        return orderVOList;
    }
    
    /**
     * 根据订单获取菜品信息字符串
     * 格式:宫保鸡丁*1;米饭*2;
     */
    private String getOrderDishesStr(Orders orders) {
        List<OrderDetail> orderDetailList = orderDetailMapper.getByOrderId(orders.getId());
        
        List<String> orderDishList = orderDetailList.stream().map(x -> {
            return x.getName() + "*" + x.getNumber() + ";";
        }).collect(Collectors.toList());
        
        return String.join("", orderDishList);
    }
    
    /**
     * 各状态订单数量统计
     */
    @Override
    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 orderStatisticsVO = new OrderStatisticsVO();
        orderStatisticsVO.setToBeConfirmed(toBeConfirmed);
        orderStatisticsVO.setConfirmed(confirmed);
        orderStatisticsVO.setDeliveryInProgress(deliveryInProgress);
        
        return orderStatisticsVO;
    }
    
    /**
     * 接单
     */
    @Override
    public void confirm(OrdersConfirmDTO ordersConfirmDTO) {
        Orders orders = Orders.builder()
                .id(ordersConfirmDTO.getId())
                .status(Orders.CONFIRMED)
                .build();
        
        orderMapper.update(orders);
    }
    
    /**
     * 拒单
     */
    @Override
    @Transactional
    public void rejection(OrdersRejectionDTO ordersRejectionDTO) throws Exception {
        // 1. 查询订单
        Orders ordersDB = orderMapper.getById(ordersRejectionDTO.getId());
        
        // 2. 校验订单状态(只有待接单可以拒单)
        if (ordersDB == null || !ordersDB.getStatus().equals(Orders.TO_BE_CONFIRMED)) {
            throw new OrderBusinessException(MessageConstant.ORDER_STATUS_ERROR);
        }
        
        // 3. 如果已支付,需要退款
        if (ordersDB.getPayStatus().equals(Orders.PAID)) {
            // TODO: 调用微信支付退款接口
            log.info("订单已支付,需要退款:{}", ordersDB.getNumber());
        }
        
        // 4. 更新订单状态
        Orders orders = new Orders();
        orders.setId(ordersDB.getId());
        orders.setStatus(Orders.CANCELLED);
        orders.setRejectionReason(ordersRejectionDTO.getRejectionReason());
        orders.setCancelTime(LocalDateTime.now());
        
        orderMapper.update(orders);
    }
    
    /**
     * 商家取消订单
     */
    @Override
    @Transactional
    public void cancel(OrdersCancelDTO ordersCancelDTO) throws Exception {
        // 1. 查询订单
        Orders ordersDB = orderMapper.getById(ordersCancelDTO.getId());
        
        // 2. 如果已支付,需要退款
        if (ordersDB.getPayStatus().equals(Orders.PAID)) {
            // TODO: 调用微信支付退款接口
            log.info("订单已支付,需要退款:{}", ordersDB.getNumber());
        }
        
        // 3. 更新订单状态
        Orders orders = new Orders();
        orders.setId(ordersCancelDTO.getId());
        orders.setStatus(Orders.CANCELLED);
        orders.setCancelReason(ordersCancelDTO.getCancelReason());
        orders.setCancelTime(LocalDateTime.now());
        
        orderMapper.update(orders);
    }
    
    /**
     * 派送订单
     */
    @Override
    public void delivery(Long id) {
        // 1. 查询订单
        Orders ordersDB = orderMapper.getById(id);
        
        // 2. 校验订单状态(只有已接单可以派送)
        if (ordersDB == null || !ordersDB.getStatus().equals(Orders.CONFIRMED)) {
            throw new OrderBusinessException(MessageConstant.ORDER_STATUS_ERROR);
        }
        
        // 3. 更新状态
        Orders orders = new Orders();
        orders.setId(ordersDB.getId());
        orders.setStatus(Orders.DELIVERY_IN_PROGRESS);
        
        orderMapper.update(orders);
    }
    
    /**
     * 完成订单
     */
    @Override
    public void complete(Long id) {
        // 1. 查询订单
        Orders ordersDB = orderMapper.getById(id);
        
        // 2. 校验订单状态(只有派送中可以完成)
        if (ordersDB == null || !ordersDB.getStatus().equals(Orders.DELIVERY_IN_PROGRESS)) {
            throw new OrderBusinessException(MessageConstant.ORDER_STATUS_ERROR);
        }
        
        // 3. 更新状态
        Orders orders = new Orders();
        orders.setId(ordersDB.getId());
        orders.setStatus(Orders.COMPLETED);
        orders.setDeliveryTime(LocalDateTime.now());
        
        orderMapper.update(orders);
    }
    
    /**
     * 检查收货地址是否超出配送范围
     */
    private void checkOutOfRange(String address) {
        // ... 同用户端的实现
    }
}

四、Mapper层扩展

OrderMapper添加方法

java 复制代码
java

package com.sky.mapper;

import org.apache.ibatis.annotations.Select;

@Mapper
public interface OrderMapper {
    
    // ... 原有方法
    
    /**
     * 根据状态统计订单数量
     */
    @Select("select count(id) from orders where status = #{status}")
    Integer countStatus(Integer status);
}

4OrderMapper.xml添加更新方法

xml

复制代码
<!-- 已经存在,无需添加 -->

五、Controller层实现

AdminOrderController

java 复制代码
java

package com.sky.controller.admin;

import com.sky.dto.OrdersCancelDTO;
import com.sky.dto.OrdersConfirmDTO;
import com.sky.dto.OrdersPageQueryDTO;
import com.sky.dto.OrdersRejectionDTO;
import com.sky.result.PageResult;
import com.sky.result.Result;
import com.sky.service.OrderService;
import com.sky.vo.OrderStatisticsVO;
import com.sky.vo.OrderVO;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;

@RestController("adminOrderController")
@RequestMapping("/admin/order")
@Api(tags = "管理端订单接口")
@Slf4j
public class OrderController {
    
    @Autowired
    private OrderService orderService;
    
    /**
     * 订单搜索
     */
    @GetMapping("/conditionSearch")
    @ApiOperation("订单搜索")
    public Result<PageResult> conditionSearch(OrdersPageQueryDTO ordersPageQueryDTO) {
        log.info("订单搜索:{}", ordersPageQueryDTO);
        PageResult pageResult = orderService.conditionSearch(ordersPageQueryDTO);
        return Result.success(pageResult);
    }
    
    /**
     * 各状态订单数量统计
     */
    @GetMapping("/statistics")
    @ApiOperation("各状态订单数量统计")
    public Result<OrderStatisticsVO> statistics() {
        log.info("订单数量统计");
        OrderStatisticsVO orderStatisticsVO = orderService.statistics();
        return Result.success(orderStatisticsVO);
    }
    
    /**
     * 订单详情
     */
    @GetMapping("/details/{id}")
    @ApiOperation("查询订单详情")
    public Result<OrderVO> details(@PathVariable("id") Long id) {
        log.info("查询订单详情:{}", id);
        OrderVO orderVO = orderService.details(id);
        return Result.success(orderVO);
    }
    
    /**
     * 接单
     */
    @PutMapping("/confirm")
    @ApiOperation("接单")
    public Result confirm(@RequestBody OrdersConfirmDTO ordersConfirmDTO) {
        log.info("接单:{}", ordersConfirmDTO);
        orderService.confirm(ordersConfirmDTO);
        return Result.success();
    }
    
    /**
     * 拒单
     */
    @PutMapping("/rejection")
    @ApiOperation("拒单")
    public Result rejection(@RequestBody OrdersRejectionDTO ordersRejectionDTO) throws Exception {
        log.info("拒单:{}", ordersRejectionDTO);
        orderService.rejection(ordersRejectionDTO);
        return Result.success();
    }
    
    /**
     * 取消订单
     */
    @PutMapping("/cancel")
    @ApiOperation("取消订单")
    public Result cancel(@RequestBody OrdersCancelDTO ordersCancelDTO) throws Exception {
        log.info("取消订单:{}", ordersCancelDTO);
        orderService.cancel(ordersCancelDTO);
        return Result.success();
    }
    
    /**
     * 派送订单
     */
    @PutMapping("/delivery/{id}")
    @ApiOperation("派送订单")
    public Result delivery(@PathVariable("id") Long id) {
        log.info("派送订单:{}", id);
        orderService.delivery(id);
        return Result.success();
    }
    
    /**
     * 完成订单
     */
    @PutMapping("/complete/{id}")
    @ApiOperation("完成订单")
    public Result complete(@PathVariable("id") Long id) {
        log.info("完成订单:{}", id);
        orderService.complete(id);
        return Result.success();
    }
}

六、难点详解

难点1:用户端和管理端为什么用同一个Mapper?

java 复制代码
java

// 同一个 pageQuery 方法,通过 DTO 传不同条件
// 用户端:只传 userId 和 status
OrdersPageQueryDTO userDTO = new OrdersPageQueryDTO();
userDTO.setUserId(currentUserId);
userDTO.setStatus(status);

// 管理端:传多个条件
OrdersPageQueryDTO adminDTO = new OrdersPageQueryDTO();
adminDTO.setNumber(orderNumber);
adminDTO.setPhone(phone);
adminDTO.setStatus(status);
adminDTO.setBeginTime(beginTime);
adminDTO.setEndTime(endTime);

好处:复用代码,减少重复


难点2:为什么管理端要拼接菜品字符串?

java 复制代码
java

// 用户端:需要完整列表
orderVO.setOrderDetailList(details);  // List结构

// 管理端:只需要字符串
orderVO.setOrderDishes("宫保鸡丁*1;米饭*2;");  // String结构

原因

  • 管理端表格列宽有限,显示完整列表会换行

  • 字符串更节省空间,加载更快

  • 减少前端渲染复杂度


难点3:订单状态流转图

text

复制代码
用户下单
    ↓
[1] 待付款 ←────────┐
    ↓                │
用户付款              │
    ↓                │
[2] 待接单            │
    ↓                │
商家接单              │ 用户/商家取消
    ↓                │
[3] 已接单            │
    ↓                │
商家派送              │
    ↓                │
[4] 派送中            │
    ↓                │
用户确认收货          │
    ↓                │
[5] 已完成           │
                     │
[6] 已取消 ←─────────┘

难点4:为什么取消/拒单要创建新对象?

java 复制代码
java

// ❌ 错误:直接修改查询出来的对象
Orders ordersDB = orderMapper.getById(id);
ordersDB.setStatus(Orders.CANCELLED);
orderMapper.update(ordersDB);  // 会更新所有字段

// ✅ 正确:创建新对象,只设置要修改的字段
Orders orders = new Orders();
orders.setId(id);
orders.setStatus(Orders.CANCELLED);
orderMapper.update(orders);  // 只更新status字段

原因:避免误更新其他字段,提高安全性


难点5:用户ID和订单ID的区别

类型 来源 作用 示例
用户ID Token中获取 确定谁在操作 1001
订单ID 前端传入 确定操作哪个订单 123
java 复制代码
java

// 用户取消订单时必须校验归属
Long currentUserId = BaseContext.getCurrentId();  // 从Token获取
Orders orders = orderMapper.getById(orderId);      // 从数据库查询

if (!orders.getUserId().equals(currentUserId)) {
    throw new OrderBusinessException("不能操作他人订单");
}

难点6:为什么需要两个查询订单详情的方法

java 复制代码
java

// 用户端:需要校验权限
public OrderVO details(Long id) {
    Long userId = BaseContext.getCurrentId();
    Orders orders = orderMapper.getById(id);
    
    // 只能看自己的订单
    if (!orders.getUserId().equals(userId)) {
        throw new OrderBusinessException("无权限");
    }
    // ...
}

// 管理端:不需要校验权限
public OrderVO details(Long id) {
    Orders orders = orderMapper.getById(id);
    // 管理员可以看所有订单
    // ...
}

总结

管理端核心功能

功能 状态变化 退款 原因
接单 2→3 确认接单
拒单 2→6 需要 无法配送
取消 任意→6 需要 商家主动取消
派送 3→4 开始配送
完成 4→5 送达完成

关键技术点

  1. DTO复用:同一个DTO支持多场景查询

  2. 动态SQL:灵活组合查询条件

  3. 状态机:订单状态的流转控制

  4. 权限校验:用户端校验订单归属

  5. 数据转换:Orders → OrderVO(字符串拼接)

相关推荐
minji...1 小时前
Linux 进程间通信(一)进程间通信与匿名管道
linux·运维·服务器·数据结构·数据库·c++
XDHCOM1 小时前
ORA-12532: TNS:invalid argument 故障解析,Oracle报错远程处理技巧与修复方法分享
数据库·oracle
码农的小菜园1 小时前
Java线程池学习笔记
java·笔记·学习
IMPYLH2 小时前
Linux 的 csplit 命令
linux·运维·服务器·数据库
清汤饺子2 小时前
搞懂 Cursor 后,我一行代码都不敲了《实战篇》
前端·javascript·后端
cm6543202 小时前
使用XGBoost赢得Kaggle比赛
jvm·数据库·python
星辰_mya2 小时前
利用 BeanPostProcessor 实现动态增强与框架开发
数据库
hongtianzai2 小时前
Laravel8.x核心特性全解析
java·c语言·开发语言·golang·php
qq_416018722 小时前
游戏与图形界面(GUI)
jvm·数据库·python