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

🎬精选专栏传送门:
❄️《数据结构》 ❄️《AI与Agent那些事》
❄️《从0开始学计算机网络》 ❄️《后端开发》

订单搜索 GET /admin/order/conditionSearch
Controller 层 (OrderController)
java
@GetMapping("/conditionSearch")
public Result<PageResult> conditionSearch(OrdersPageQueryDTO ordersPageQueryDTO) {
PageResult pageResult = orderService.conditionSearch(ordersPageQueryDTO);
return Result.success(pageResult);
}
- 入参
OrdersPageQueryDTO是查询条件:订单号、手机号、状态、下单时间段、用户ID、分页参数 - 这是 GET 请求,参数通过 URL query string 传递,不是 request body
Service 层 (OrderServiceImpl)
java
public PageResult conditionSearch(OrdersPageQueryDTO ordersPageQueryDTO) {
// ① PageHelper 开启分页 --- 它会自动给 SQL 加 LIMIT
PageHelper.startPage(ordersPageQueryDTO.getPage(), ordersPageQueryDTO.getPageSize());
// ② Mapper 动态条件查询(XML 里写的 <if> 标签)
Page<Orders> page = (Page<Orders>) orderMapper.pageQuery(ordersPageQueryDTO);
// ③ Orders 只存了基础信息,要拼成前端需要的 OrderVO(带菜品详情字符串)
List<OrderVO> orderVOList = getOrderVOList(page);
return new PageResult(page.getTotal(), orderVOList);
}
关键点在 getOrderVOList (OrderServiceImpl):
java
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); // 例如 "宫保鸡丁*2;米饭*1;"
orderVOList.add(orderVO);
}
}
return orderVOList;
}
getOrderDishesStr (OrderServiceImpl):
java
private String getOrderDishesStr(Orders orders) {
// 查 order_detail 表
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);
}
Mapper 层 (XML SQL)
XML
<select id="pageQuery" resultType="com.sky.entity.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 <![CDATA[>=]]> #{beginTime}
</if>
<if test="endTime != null">
and order_time <![CDATA[<=]]> #{endTime}
</if>
</where>
order by order_time desc
</select>
<![CDATA[>=]]>是因为 MyBatis XML 里><不能直接写,要用 CDATA 包裹。
各状态订单数量统计 GET /admin/order/statistics
Controller 层 (OrderController)
java
@GetMapping("/statistics")
public Result<OrderStatisticsVO> statistics() {
OrderStatisticsVO orderStatisticsVO = orderService.statistics();
return Result.success(orderStatisticsVO);
}
Service 层 (OrderServiceImpl)
java
public OrderStatisticsVO statistics() {
// 三个状态对应的订单数量:待接单(2)、已接单(3)、派送中(4)
Integer toBeConfirmed = orderMapper.countStatus(Orders.TO_BE_CONFIRMED); // status=2
Integer confirmed = orderMapper.countStatus(Orders.CONFIRMED); // status=3
Integer deliveryInProgress = orderMapper.countStatus(Orders.DELIVERY_IN_PROGRESS); // status=4
OrderStatisticsVO vo = new OrderStatisticsVO();
vo.setToBeConfirmed(toBeConfirmed);
vo.setConfirmed(confirmed);
vo.setDeliveryInProgress(deliveryInProgress);
return vo;
}
Mapper 层 (注解方式)
java
@Select("select count(*) from orders where status = #{status}")
Integer countStatus(Integer status);
返回的 VO (OrderStatisticsVO):
java
public class OrderStatisticsVO {
private Integer toBeConfirmed; // 待接单数量
private Integer confirmed; // 待派送数量
private Integer deliveryInProgress; // 派送中数量
}
这个接口是给商家首页做三个 Tab Badge 用的,告诉你每个状态有多少单。
查询订单详情 GET /admin/order/details/{id}
Controller 层 (OrderController)
java
@GetMapping("/details/{id}")
public Result<OrderVO> details(@PathVariable("id") Long id) {
OrderVO orderVO = orderService.details(id);
return Result.success(orderVO);
}
Service 层 (OrderServiceImpl)
java
public OrderVO details(Long id) {
// ① 查订单主表
Orders orders = orderMapper.getById(id);
// ② 查订单明细表(order_detail 表)
List<OrderDetail> orderDetailList = orderDetailMapper.getByOrderId(orders.getId());
// ③ 封装成 OrderVO(继承了 Orders,再加一个 orderDetailList 字段)
OrderVO orderVO = new OrderVO();
BeanUtils.copyProperties(orders, orderVO);
orderVO.setOrderDetailList(orderDetailList);
return orderVO;
}
Mapper 层 (都是注解)
java
// OrderMapper
@Select("select * from orders where id = #{id}")
Orders getById(Long id);
// OrderDetailMapper
@Select("select * from order_detail where order_id = #{orderId}")
List<OrderDetail> getByOrderId(Long orderId);
OrderVO 结构:继承 Orders(有 number、status、amount...)+ orderDetailList(菜品明细列表)+ orderDishes(菜品字符串)。一个查询,两次数据库调用,最后合成一个 VO 返回。
接单 PUT /admin/order/confirm
Controller 层 (OrderController)
java
@PutMapping("/confirm")
public Result confirm(@RequestBody OrdersConfirmDTO ordersConfirmDTO) {
orderService.confirm(ordersConfirmDTO);
return Result.success();
}
Service 层 (OrderServiceImpl)
java
public void confirm(OrdersConfirmDTO ordersConfirmDTO) {
Orders orders = Orders.builder()
.id(ordersConfirmDTO.getId()) // 订单ID
.status(Orders.CONFIRMED) // 状态改成 3(已接单/待派送)
.build();
orderMapper.update(orders);
}
DTO (OrdersConfirmDTO):
java
public class OrdersConfirmDTO {
private Long id;
private Integer status; // 这里虽然有 status 字段,但 Service 里没用它,而是硬编码了 CONFIRMED
}
这里业务逻辑很简单 --- 把订单状态从
2(待接单)改成3(已接单)。注意没有做状态前置校验(比如确认订单是不是真的处于「待接单」状态),而拒单、派送、完成这些操作都做了。这是个小瑕疵。
拒单 PUT /admin/order/rejection
Controller 层 (OrderController)
java
@PutMapping("/rejection")
public Result rejection(@RequestBody OrdersRejectionDTO ordersRejectionDTO) {
orderService.rejection(ordersRejectionDTO);
return Result.success();
}
Service 层 (OrderServiceImpl) --- 这是最复杂的接口
java
public void rejection(OrdersRejectionDTO ordersRejectionDTO) throws Exception {
// ① 先查订单,确保存在
Orders ordersDB = orderMapper.getById(ordersRejectionDTO.getId());
// ② 前置校验:订单必须存在 且 状态是 2(待接单),才允许拒单
if (ordersDB == null || !ordersDB.getStatus().equals(Orders.TO_BE_CONFIRMED)) {
throw new OrderBusinessException(MessageConstant.ORDER_STATUS_ERROR);
}
// ③ 如果用户已经付过钱了 → 调用微信退款接口
Integer payStatus = ordersDB.getPayStatus();
if (payStatus == Orders.PAID) {
try {
String refund = weChatPayUtil.refund(
ordersDB.getNumber(), // 商户订单号
ordersDB.getNumber(), // 商户退款单号
new BigDecimal(0.01), // 退款金额(演示用的 0.01 元)
new BigDecimal(0.01)); // 原订单金额
log.info("申请退款:{}", refund);
} catch (Exception e) {
// 本地环境没配微信商户证书,退款会失败 --- 但不让退款失败阻断拒单流程
log.warn("微信退款接口调用失败,本地环境模拟退款:{}", e.getMessage());
}
}
// ④ 更新订单:状态改成已取消 + 记录拒单原因和取消时间
Orders orders = new Orders();
orders.setId(ordersDB.getId());
orders.setStatus(Orders.CANCELLED); // 状态 6
orders.setRejectionReason(ordersRejectionDTO.getRejectionReason()); // 商家填的拒单理由
orders.setCancelTime(LocalDateTime.now());
orderMapper.update(orders);
}
三步走:校验 → 退款 → 改状态。 注意退款用了 try-catch 兜底,因为本地开发环境没有真实微信商户证书,退款一定会失败,但我们不希望退款失败导致拒单也失败 ,所以 catch 里只记日志,不抛出。这是一个好的容错设计。
取消订单 PUT /admin/order/cancel
Controller 层 (OrderController)
java
@PutMapping("/cancel")
public Result cancel(@RequestBody OrdersCancelDTO ordersCancelDTO) {
orderService.cancel(ordersCancelDTO);
return Result.success();
}
Service 层 (OrderServiceImpl)
java
public void cancel(OrdersCancelDTO ordersCancelDTO) throws Exception {
Orders ordersDB = orderMapper.getById(ordersCancelDTO.getId());
// ① 如果已支付,也要退款
Integer payStatus = ordersDB.getPayStatus();
if (payStatus == 1) {
try {
weChatPayUtil.refund(
ordersDB.getNumber(),
ordersDB.getNumber(),
new BigDecimal(0.01),
new BigDecimal(0.01));
} catch (Exception e) {
log.warn("微信退款接口调用失败,本地环境模拟退款:{}", e.getMessage());
}
}
// ② 更新状态为已取消 + 记录取消原因
Orders orders = new Orders();
orders.setId(ordersCancelDTO.getId());
orders.setStatus(Orders.CANCELLED);
orders.setCancelReason(ordersCancelDTO.getCancelReason()); // 商家填的取消理由
orders.setCancelTime(LocalDateTime.now());
orderMapper.update(orders);
}
和拒单逻辑几乎一模一样,区别只是没有做状态前置校验 (商家可以取消任意状态的订单?这在真实业务中需要限制)。拒单用的是
rejectionReason,取消用的是cancelReason。
派送订单 PUT /admin/order/delivery/{id}
Controller 层 (OrderController)
java
@PutMapping("/delivery/{id}")
public Result delivery(@PathVariable("id") Long id) {
orderService.delivery(id);
return Result.success();
}
Service 层 (OrderServiceImpl)
java
public void delivery(Long id) {
Orders ordersDB = orderMapper.getById(id);
// 校验:订单存在 且 状态必须是 3(已接单/待派送)
if (ordersDB == null || !ordersDB.getStatus().equals(Orders.CONFIRMED)) {
throw new OrderBusinessException(MessageConstant.ORDER_STATUS_ERROR);
}
Orders orders = new Orders();
orders.setId(ordersDB.getId());
orders.setStatus(Orders.DELIVERY_IN_PROGRESS); // 改成状态 4
orderMapper.update(orders);
}
派送的前提是商家已经接单(状态3),不能跳过接单直接派送。这个状态校验就是防止「跨状态跳步」的。
完成订单 PUT /admin/order/complete/{id}
Controller 层 (OrderController)
java
@PutMapping("/complete/{id}")
public Result complete(@PathVariable("id") Long id) {
orderService.complete(id);
return Result.success();
}
Service 层 (OrderServiceImpl)
java
public void complete(Long id) {
Orders ordersDB = orderMapper.getById(id);
// 校验:订单存在 且 状态必须是 4(派送中)
if (ordersDB == null || !ordersDB.getStatus().equals(Orders.DELIVERY_IN_PROGRESS)) {
throw new OrderBusinessException(MessageConstant.ORDER_STATUS_ERROR);
}
Orders orders = new Orders();
orders.setId(ordersDB.getId());
orders.setStatus(Orders.COMPLETED); // 改成状态 5
orders.setDeliveryTime(LocalDateTime.now()); // 额外记录一下送达时间
orderMapper.update(orders);
}
完成订单是状态流转的最后一步。唯一比派送多做了一件事 --- 记录了
deliveryTime(送达时间)。
8 个接口横向对比总结
| 接口 | 路径 | 状态变化 | 有前置校验 | 有退款逻辑 | 核心操作 |
|---|---|---|---|---|---|
| 订单搜索 | GET /conditionSearch |
--- | --- | --- | 分页+动态条件查询+拼装菜品串 |
| 数量统计 | GET /statistics |
--- | --- | --- | count(*) where status = ? 查三次 |
| 订单详情 | GET /details/{id} |
--- | --- | --- | 查 orders 表 + 查 order_detail 表 → 合成 VO |
| 接单 | PUT /confirm |
2→3 | ❌ | ❌ | 纯 update 状态 |
| 拒单 | PUT /rejection |
2→6 | ✅ 状态=2 | ✅ 已支付就退款 | 校验→退款→改状态+记录原因 |
| 取消订单 | PUT /cancel |
任意→6 | ❌ | ✅ 已支付就退款 | 退款→改状态+记录原因 |
| 派送订单 | PUT /delivery/{id} |
3→4 | ✅ 状态=3 | ❌ | 校验→update 状态 |
| 完成订单 | PUT /complete/{id} |
4→5 | ✅ 状态=4 | ❌ | 校验→update 状态+记录送达时间 |
设计模式
整个商家端订单管理其实就是 一个订单状态机:
confirm() delivery() complete()
[2待接单]──→[3已接单]──→[4派送中]──→[5已完成]
│ │
└──rejection─┘──cancel()──→[6已取消]
每个操作本质上就是:
- 校验当前状态是不是合法前置状态(防止状态跳跃)
- 如果涉及金额(拒单/取消),先退款
- update 新状态 + 必要的附属字段(原因、时间戳)