

🔥个人主页:北极的代码(欢迎来访)
🎬作者简介:java后端学习者
✨命运的结局尽可永在,不屈的挑战却不可须臾或缺!
前言:经过一段时间的学习,我们学了很多,今天是课后作业,主要完成用户和商家端的点餐的售后服务。

第一步:查询历史订单
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 >= #{beginTime}
</if>
<if test="endTime != null">
AND order_time <= #{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 | 无 | 送达完成 |
关键技术点
-
DTO复用:同一个DTO支持多场景查询
-
动态SQL:灵活组合查询条件
-
状态机:订单状态的流转控制
-
权限校验:用户端校验订单归属
-
数据转换:Orders → OrderVO(字符串拼接)