Procházet zdrojové kódy

perf: 订单列表接口批量查询,消除 N+1 查询瓶颈

原逻辑每行订单分别查询商品列表和退款数量,30 笔订单 = 91 次查询。
现在一次性批量查询所有订单的商品和退款明细,固定 3 次查询:
1. 查订单列表
2. 查所有订单的商品 (IN)
3. 查所有订单的退款明细 (IN)

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
skyline před 1 týdnem
rodič
revize
99c5949901

+ 9 - 0
haha-service/src/main/java/com/haha/service/OrderGoodsService.java

@@ -5,6 +5,7 @@ import com.haha.common.vo.OrderItemVO;
 import com.haha.entity.OrderGoods;
 
 import java.util.List;
+import java.util.Map;
 
 /**
  * 订单商品服务接口
@@ -42,4 +43,12 @@ public interface OrderGoodsService extends IService<OrderGoods> {
      * @return 商品VO列表
      */
     List<OrderItemVO> getVOByOrderId(Long orderId);
+
+    /**
+     * 批量根据订单ID列表查询商品VO列表
+     *
+     * @param orderIds 订单ID列表
+     * @return Map<orderId, 商品VO列表>
+     */
+    Map<Long, List<OrderItemVO>> getVOByOrderIds(List<Long> orderIds);
 }

+ 6 - 0
haha-service/src/main/java/com/haha/service/RefundService.java

@@ -39,6 +39,12 @@ public interface RefundService extends IService<Refund> {
      */
     Map<Long, Integer> getRefundedQuantityMap(Long orderId);
 
+    /**
+     * 批量查询多个订单的已退款商品数量
+     * @return Map<orderId, Map<orderGoodsId, 已退款总数量>>
+     */
+    Map<Long, Map<Long, Integer>> getRefundedQuantityMapByOrderIds(List<Long> orderIds);
+
     /**
      * 查询订单的所有退款记录
      */

+ 41 - 0
haha-service/src/main/java/com/haha/service/impl/OrderGoodsServiceImpl.java

@@ -10,7 +10,10 @@ import lombok.extern.slf4j.Slf4j;
 import org.springframework.stereotype.Service;
 
 import java.util.ArrayList;
+import java.util.HashMap;
 import java.util.List;
+import java.util.Map;
+import java.util.stream.Collectors;
 
 /**
  * 订单商品服务实现
@@ -71,4 +74,42 @@ public class OrderGoodsServiceImpl extends ServiceImpl<OrderGoodsMapper, OrderGo
         
         return voList;
     }
+
+    @Override
+    public Map<Long, List<OrderItemVO>> getVOByOrderIds(List<Long> orderIds) {
+        Map<Long, List<OrderItemVO>> result = new HashMap<>();
+        if (orderIds == null || orderIds.isEmpty()) {
+            return result;
+        }
+        List<OrderGoods> allGoods = lambdaQuery()
+                .in(OrderGoods::getOrderId, orderIds)
+                .list();
+        if (allGoods == null || allGoods.isEmpty()) {
+            return result;
+        }
+        for (OrderGoods goods : allGoods) {
+            result.computeIfAbsent(goods.getOrderId(), k -> new ArrayList<>());
+        }
+        // 按 orderId 分组后,对每组做相同的数据转换(与 getVOByOrderId 逻辑一致)
+        Map<Long, List<OrderGoods>> grouped = allGoods.stream()
+                .collect(Collectors.groupingBy(OrderGoods::getOrderId));
+        for (Map.Entry<Long, List<OrderGoods>> entry : grouped.entrySet()) {
+            List<OrderItemVO> voList = entry.getValue().stream().map(goods -> {
+                OrderItemVO vo = new OrderItemVO();
+                vo.setId(goods.getId());
+                vo.setProductId(goods.getProductId());
+                vo.setCode(goods.getCode());
+                vo.setBarCode(goods.getBarCode());
+                vo.setProductName(goods.getProductName());
+                vo.setPic(goods.getPic());
+                vo.setProductNum(goods.getProductNum());
+                vo.setMoney(goods.getMoney());
+                vo.setOriginPrice(goods.getOriginPrice());
+                vo.setPrice(goods.getPrice());
+                return vo;
+            }).collect(Collectors.toList());
+            result.put(entry.getKey(), voList);
+        }
+        return result;
+    }
 }

+ 82 - 4
haha-service/src/main/java/com/haha/service/impl/OrderServiceImpl.java

@@ -829,11 +829,10 @@ public class OrderServiceImpl extends ServiceImpl<OrderMapper, Order> implements
     @Override
     public List<Map<String, Object>> getUserOrderListWithProducts(Long userId, Integer status) {
         List<Order> orders = getOrderListByUserId(userId, status);
-        List<Map<String, Object>> orderList = new ArrayList<>();
-        for (Order order : orders) {
-            orderList.add(buildOrderMap(order));
+        if (orders.isEmpty()) {
+            return new ArrayList<>();
         }
-        return orderList;
+        return buildOrderListBatch(orders);
     }
 
     @Override
@@ -980,6 +979,85 @@ public class OrderServiceImpl extends ServiceImpl<OrderMapper, Order> implements
         return cancelUserOrder(userId, orderId);
     }
 
+    /**
+     * 批量构建订单列表(一次查询商品和退款数据,避免 N+1)
+     */
+    private List<Map<String, Object>> buildOrderListBatch(List<Order> orders) {
+        List<Long> orderIds = orders.stream().map(Order::getId).collect(Collectors.toList());
+
+        // 批量查询所有订单的商品
+        Map<Long, List<OrderItemVO>> goodsMap = orderGoodsService.getVOByOrderIds(orderIds);
+
+        // 批量查询所有订单的退款商品数量
+        Map<Long, Map<Long, Integer>> refundQtyMap = null;
+        if (refundService != null) {
+            refundQtyMap = refundService.getRefundedQuantityMapByOrderIds(orderIds);
+        }
+
+        List<Map<String, Object>> orderList = new ArrayList<>();
+        for (Order order : orders) {
+            Map<String, Object> orderMap = buildOrderMapBase(order);
+            List<OrderItemVO> goodsList = goodsMap.getOrDefault(order.getId(), new ArrayList<>());
+            Map<Long, Integer> refundQtys = refundQtyMap != null
+                    ? refundQtyMap.getOrDefault(order.getId(), new HashMap<>())
+                    : new HashMap<>();
+            orderMap.put("products", buildProductList(goodsList, refundQtys));
+            orderList.add(orderMap);
+        }
+        return orderList;
+    }
+
+    /**
+     * 构建订单Map基础字段(不含 products)
+     */
+    private Map<String, Object> buildOrderMapBase(Order order) {
+        Map<String, Object> orderMap = new HashMap<>();
+        orderMap.put("id", order.getId());
+        orderMap.put("orderNo", order.getOrderNo());
+        orderMap.put("orderName", order.getOrderName() != null ? order.getOrderName() : "");
+        orderMap.put("outTradeNo", order.getOutTradeNo());
+        orderMap.put("deviceId", order.getDeviceId());
+        orderMap.put("storeName", order.getStoreName() != null ? order.getStoreName() : "");
+        orderMap.put("totalAmount", order.getTotalAmount());
+        orderMap.put("discountAmount", order.getDiscountAmount() != null ? order.getDiscountAmount() : 0);
+        orderMap.put("paidAmount", order.getPaidAmount() != null ? order.getPaidAmount() : order.getTotalAmount());
+        orderMap.put("payStatus", order.getPayStatus());
+        orderMap.put("payChannel", order.getPayChannel());
+        orderMap.put("status", order.getStatus());
+        orderMap.put("createTime", order.getCreateTime());
+        orderMap.put("payTime", order.getPayTime());
+        orderMap.put("confidence", order.getConfidence());
+        orderMap.put("refundStatus", order.getRefundStatus());
+        orderMap.put("refundAmount", order.getRefundAmount());
+        orderMap.put("currentRefundId", order.getCurrentRefundId());
+        orderMap.put("payScoreOrderId", order.getPayScoreOrderId());
+        return orderMap;
+    }
+
+    /**
+     * 构建商品列表(使用预查询数据)
+     */
+    private List<Map<String, Object>> buildProductList(List<OrderItemVO> goodsList, Map<Long, Integer> refundQtys) {
+        List<Map<String, Object>> products = new ArrayList<>();
+        for (OrderItemVO goods : goodsList) {
+            Map<String, Object> product = new HashMap<>();
+            product.put("id", goods.getId());
+            product.put("orderGoodsId", goods.getId());
+            product.put("productId", goods.getProductId());
+            product.put("name", goods.getProductName());
+            product.put("price", goods.getPrice());
+            product.put("quantity", goods.getProductNum());
+            product.put("image", normalizeImageUrl(goods.getPic()));
+            product.put("pic", goods.getPic());
+            product.put("subtotal", goods.getPrice() != null && goods.getProductNum() != null
+                    ? goods.getPrice().multiply(new BigDecimal(goods.getProductNum()))
+                    : new BigDecimal(0));
+            product.put("refundedQuantity", refundQtys.getOrDefault(goods.getId(), 0));
+            products.add(product);
+        }
+        return products;
+    }
+
     /**
      * 构建订单Map(含商品信息)
      */

+ 35 - 0
haha-service/src/main/java/com/haha/service/impl/RefundServiceImpl.java

@@ -215,6 +215,41 @@ public class RefundServiceImpl extends ServiceImpl<RefundMapper, Refund> impleme
         return result;
     }
 
+    @Override
+    public Map<Long, Map<Long, Integer>> getRefundedQuantityMapByOrderIds(List<Long> orderIds) {
+        Map<Long, Map<Long, Integer>> result = new java.util.HashMap<>();
+        if (orderIds == null || orderIds.isEmpty()) {
+            return result;
+        }
+        // 一次性查所有退款记录
+        List<Refund> refundedList = lambdaQuery()
+                .in(Refund::getOrderId, orderIds)
+                .eq(Refund::getStatus, RefundStatus.REFUNDED.getCode())
+                .list();
+        if (refundedList == null || refundedList.isEmpty()) {
+            return result;
+        }
+        // 收集所有 refundId 批量查退款明细
+        List<Long> refundIds = refundedList.stream().map(Refund::getId).collect(java.util.stream.Collectors.toList());
+        List<RefundItem> allItems = refundItemMapper.selectList(
+                new LambdaQueryWrapper<RefundItem>().in(RefundItem::getRefundId, refundIds));
+        if (allItems == null || allItems.isEmpty()) {
+            return result;
+        }
+        // 建立 refundId → orderId 映射
+        Map<Long, Long> refundToOrder = refundedList.stream()
+                .collect(java.util.stream.Collectors.toMap(Refund::getId, Refund::getOrderId));
+        // 按 orderId 聚合
+        for (RefundItem item : allItems) {
+            Long orderId = refundToOrder.get(item.getRefundId());
+            if (orderId != null) {
+                result.computeIfAbsent(orderId, k -> new java.util.HashMap<>())
+                        .merge(item.getOrderGoodsId(), item.getQuantity(), Integer::sum);
+            }
+        }
+        return result;
+    }
+
     @Override
     public List<Refund> getByOrderId(Long orderId) {
         return lambdaQuery()