Explorar el Código

perf: 批量修复 4 处 N+1 查询瓶颈

- DeviceServiceImpl: 设备列表门店信息批量查询 (selectBatchIds)
- UserCouponServiceImpl: 优惠券列表模板信息批量查询 (selectBatchIds)
- ReplenishmentOrderServiceImpl: 补货单列表明细数量批量查询 (IN)
- TimedDiscountServiceImpl: 折扣活动执行中门店+商品批量查询

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
skyline hace 1 semana
padre
commit
53228fa853

+ 48 - 4
haha-service/src/main/java/com/haha/service/impl/DeviceServiceImpl.java

@@ -37,6 +37,7 @@ import java.time.LocalDateTime;
 import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
+import java.util.stream.Collectors;
 
 @Slf4j
 @Service
@@ -60,10 +61,10 @@ private final HahaClient hahaClient;
                .orderByDesc(Device::getCreateTime);
 
         IPage<Device> devicePage = this.page(new Page<>(page, pageSize), wrapper);
-        
-        // 填充额外字段
-        devicePage.getRecords().forEach(this::fillDeviceLabels);
-        
+
+        // 批量填充额外字段(避免 N+1 查询)
+        fillDeviceLabelsBatch(devicePage.getRecords());
+
         return devicePage;
     }
 
@@ -350,6 +351,49 @@ private final HahaClient hahaClient;
         setDefaultValues(device);
     }
     
+    /**
+     * 批量填充设备标签(避免循环内逐条查门店)
+     */
+    private void fillDeviceLabelsBatch(List<Device> devices) {
+        if (devices == null || devices.isEmpty()) return;
+
+        // 收集所有 shopId,批量查询
+        List<Long> shopIds = devices.stream()
+                .map(Device::getShopId)
+                .filter(id -> id != null)
+                .distinct()
+                .collect(Collectors.toList());
+        Map<Long, Shop> shopMap = new HashMap<>();
+        if (!shopIds.isEmpty()) {
+            List<Shop> shops = shopMapper.selectBatchIds(shopIds);
+            if (shops != null) {
+                shopMap = shops.stream().collect(Collectors.toMap(Shop::getId, s -> s));
+            }
+        }
+
+        for (Device device : devices) {
+            fillDeviceStatusLabel(device);
+            if (device.getShopId() != null) {
+                Shop shop = shopMap.get(device.getShopId());
+                if (shop != null) {
+                    if (device.getShopName() == null) device.setShopName(shop.getName());
+                    if (device.getAddress() == null && shop.getAddress() != null)
+                        device.setAddress(shop.getAddress());
+                }
+            }
+            setDefaultValues(device);
+        }
+    }
+
+    private void fillDeviceStatusLabel(Device device) {
+        DeviceConstants.StatusLabel statusLabel = DeviceConstants.getStatusLabel(device.getStatus());
+        if (statusLabel != null) {
+            device.setStatusLabel(statusLabel.getLabel());
+            device.setStatusColor(statusLabel.getColor());
+            device.setIsOnline(DeviceConstants.isOnline(device.getStatus()));
+        }
+    }
+
     /**
      * 设置设备默认值
      */

+ 16 - 2
haha-service/src/main/java/com/haha/service/impl/ReplenishmentOrderServiceImpl.java

@@ -30,7 +30,10 @@ import java.time.LocalDate;
 import java.time.LocalDateTime;
 import java.time.format.DateTimeFormatter;
 import java.util.ArrayList;
+import java.util.HashMap;
 import java.util.List;
+import java.util.Map;
+import java.util.stream.Collectors;
 
 /**
  * 补货单服务实现
@@ -83,10 +86,21 @@ public class ReplenishmentOrderServiceImpl extends ServiceImpl<ReplenishmentOrde
         Page<ReplenishmentOrder> page = new Page<>(queryDTO.getPage(), queryDTO.getPageSize());
         IPage<ReplenishmentOrder> result = page(page, wrapper);
 
+        // 批量查询补货单明细数量,避免 N+1
+        List<Long> orderIds = result.getRecords().stream()
+                .map(ReplenishmentOrder::getId).collect(Collectors.toList());
+        Map<Long, Long> countMap = new HashMap<>();
+        if (!orderIds.isEmpty()) {
+            List<ReplenishmentOrderItem> allItems = orderItemMapper.selectList(
+                    new LambdaQueryWrapper<ReplenishmentOrderItem>()
+                            .in(ReplenishmentOrderItem::getOrderId, orderIds));
+            countMap = allItems.stream().collect(Collectors.groupingBy(
+                    ReplenishmentOrderItem::getOrderId, Collectors.counting()));
+        }
+
         for (ReplenishmentOrder order : result.getRecords()) {
             fillStatusLabel(order);
-            int count = orderItemMapper.selectByOrderId(order.getId()).size();
-            order.setItemCount(count);
+            order.setItemCount(countMap.getOrDefault(order.getId(), 0L).intValue());
         }
 
         return result;

+ 29 - 13
haha-service/src/main/java/com/haha/service/impl/TimedDiscountServiceImpl.java

@@ -328,6 +328,15 @@ public class TimedDiscountServiceImpl extends ServiceImpl<TimedDiscountActivityM
             List<String> productTypes = getProductTypes(activity);
             List<String> priorityTypes = getPriorityProductTypes(activity);
 
+            // 批量查询门店,避免 N+1
+            Map<Long, Shop> shopMap = new HashMap<>();
+            if (!shopIds.isEmpty()) {
+                List<Shop> shops = shopMapper.selectBatchIds(shopIds);
+                if (shops != null) {
+                    shopMap = shops.stream().collect(Collectors.toMap(Shop::getId, s -> s));
+                }
+            }
+
             int totalProducts = 0;
             int priorityProducts = 0;
             int totalStock = 0;
@@ -335,7 +344,7 @@ public class TimedDiscountServiceImpl extends ServiceImpl<TimedDiscountActivityM
             BigDecimal discountValue = BigDecimal.ZERO;
 
             for (Long shopId : shopIds) {
-                Shop shop = shopMapper.selectById(shopId);
+                Shop shop = shopMap.get(shopId);
                 String shopName = shop != null ? shop.getName() : "";
 
                 List<Device> devices = getDevicesByShop(shopId);
@@ -345,7 +354,7 @@ public class TimedDiscountServiceImpl extends ServiceImpl<TimedDiscountActivityM
                     for (Map<String, Object> inventory : inventoryList) {
                         Long productId = (Long) inventory.get("productId");
                         Integer stock = (Integer) inventory.get("stock");
-                        Product product = productMapper.selectById(productId);
+                        Product product = (Product) inventory.get("product");
 
                         if (product == null || product.getRetailPrice() == null) {
                             continue;
@@ -555,26 +564,33 @@ public class TimedDiscountServiceImpl extends ServiceImpl<TimedDiscountActivityM
         }
 
         List<DeviceInventory> inventoryList = deviceInventoryMapper.selectList(wrapper);
-        List<Map<String, Object>> result = new ArrayList<>();
+        if (inventoryList.isEmpty()) {
+            return new ArrayList<>();
+        }
+
+        // 批量查询所有关联商品,避免 N+1
+        List<Long> productIds = inventoryList.stream()
+                .map(DeviceInventory::getProductId).distinct().collect(Collectors.toList());
+        Map<Long, Product> productMap = new HashMap<>();
+        List<Product> products = productMapper.selectBatchIds(productIds);
+        if (products != null) {
+            productMap = products.stream().collect(Collectors.toMap(Product::getId, p -> p));
+        }
 
+        List<Map<String, Object>> result = new ArrayList<>();
         for (DeviceInventory inventory : inventoryList) {
-            Product product = productMapper.selectById(inventory.getProductId());
-            if (product == null) {
+            Product product = productMap.get(inventory.getProductId());
+            if (product == null) continue;
+            if (productTypes != null && !productTypes.isEmpty()
+                    && !productTypes.contains(product.getType())) {
                 continue;
             }
-
-            if (productTypes != null && !productTypes.isEmpty()) {
-                if (!productTypes.contains(product.getType())) {
-                    continue;
-                }
-            }
-
             Map<String, Object> map = new HashMap<>();
             map.put("productId", inventory.getProductId());
             map.put("stock", inventory.getStock());
+            map.put("product", product); // 顺便带出 product 供上层使用
             result.add(map);
         }
-
         return result;
     }
 

+ 36 - 10
haha-service/src/main/java/com/haha/service/impl/UserCouponServiceImpl.java

@@ -36,6 +36,7 @@ import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
 import java.util.concurrent.TimeUnit;
+import java.util.stream.Collectors;
 
 @Slf4j
 @Service
@@ -63,10 +64,10 @@ public class UserCouponServiceImpl extends ServiceImpl<UserCouponMapper, UserCou
         wrapper.orderByDesc(UserCoupon::getCreateTime);
 
         IPage<UserCoupon> result = this.page(pageParam, wrapper);
-        result.getRecords().forEach(this::fillCouponLabels);
+        fillCouponLabelsBatch(result.getRecords());
 
         log.debug("[优惠券服务] 查询结果 - 总数: {}, 当前页数量: {}", result.getTotal(), result.getRecords().size());
-        
+
         return result;
     }
 
@@ -347,24 +348,51 @@ public class UserCouponServiceImpl extends ServiceImpl<UserCouponMapper, UserCou
         return new ArrayList<>();
     }
 
-    private void fillCouponLabels(UserCoupon userCoupon) {
-        // 填充优惠券模板信息
-        CouponTemplate template = templateMapper.selectById(userCoupon.getTemplateId());
+    private void fillCouponLabelsBatch(List<UserCoupon> coupons) {
+        if (coupons == null || coupons.isEmpty()) return;
+
+        // 批量查询模板信息
+        List<Long> templateIds = coupons.stream()
+                .map(UserCoupon::getTemplateId)
+                .filter(id -> id != null)
+                .distinct()
+                .collect(Collectors.toList());
+        Map<Long, CouponTemplate> templateMap = new HashMap<>();
+        if (!templateIds.isEmpty()) {
+            List<CouponTemplate> templates = templateMapper.selectBatchIds(templateIds);
+            if (templates != null) {
+                templateMap = templates.stream()
+                        .collect(Collectors.toMap(CouponTemplate::getId, t -> t));
+            }
+        }
+
+        for (UserCoupon coupon : coupons) {
+            fillCouponLabelsFromMap(coupon, templateMap);
+        }
+    }
+
+    private void fillCouponLabelsFromMap(UserCoupon userCoupon, Map<Long, CouponTemplate> templateMap) {
+        CouponTemplate template = templateMap.get(userCoupon.getTemplateId());
         if (template != null) {
             userCoupon.setCouponName(template.getCouponName());
             userCoupon.setCouponType(template.getCouponType());
             userCoupon.setMinAmount(template.getMinAmount());
             userCoupon.setCouponDesc(template.getCouponDesc());
             userCoupon.setApplyScope(template.getApplyScope());
-            // 填充模板的discountValue用于前端显示
             userCoupon.setDiscountValue(template.getDiscountValue());
         }
-        // 填充状态标签
         com.haha.common.vo.StatusLabel label = CouponStatusEnum.getLabelByCode(userCoupon.getStatus());
         userCoupon.setStatusLabel(label.getLabel());
         userCoupon.setStatusColor(label.getColor());
     }
 
+    private void fillCouponLabels(UserCoupon userCoupon) {
+        CouponTemplate template = templateMapper.selectById(userCoupon.getTemplateId());
+        Map<Long, CouponTemplate> templateMap = new HashMap<>();
+        if (template != null) templateMap.put(template.getId(), template);
+        fillCouponLabelsFromMap(userCoupon, templateMap);
+    }
+
     @Override
     public IPage<UserCoupon> getUserCouponList(Long userId, String couponCode, Integer status, int page, int pageSize) {
         log.debug("[优惠券服务] 查询用户优惠券列表 - userId: {}, couponCode: {}, status: {}, page: {}, pageSize: {}",
@@ -379,9 +407,7 @@ public class UserCouponServiceImpl extends ServiceImpl<UserCouponMapper, UserCou
         wrapper.orderByDesc(UserCoupon::getCreateTime);
 
         IPage<UserCoupon> result = this.page(pageParam, wrapper);
-
-        // 填充优惠券模板信息和状态标签
-        result.getRecords().forEach(this::fillCouponLabels);
+        fillCouponLabelsBatch(result.getRecords());
 
         log.debug("[优惠券服务] 查询结果 - 总数: {}, 当前页数量: {}", result.getTotal(), result.getRecords().size());