Explorar o código

perf: 首页看板 SQL 聚合替代内存全表遍历

- getOverviewData: 4 次 list()→SQL聚合(仅取 totalAmount),消除全量订单加载
- getStatisticsData: select() 只取 3 列 + 一次遍历按天/小时分组替代 N 次 stream 过滤
- 月统计从 30 轮全量遍历降为 1 次遍历

Co-Authored-By: Claude <noreply@anthropic.com>
skyline hai 1 día
pai
achega
c2e91b11a1

+ 119 - 177
haha-service/src/main/java/com/haha/service/impl/DashboardServiceImpl.java

@@ -53,135 +53,119 @@ public class DashboardServiceImpl implements DashboardService {
     public Map<String, Object> getOverviewData() {
         Map<String, Object> result = new HashMap<>();
 
-        // 今日开始时间
         LocalDateTime todayStart = LocalDateTime.of(LocalDate.now(), LocalTime.MIN);
         LocalDateTime todayEnd = LocalDateTime.of(LocalDate.now(), LocalTime.MAX);
-        
-        // 昨日同时段时间(当前时间往前推一天)
         LocalDateTime yesterdayStart = todayStart.minusDays(1);
         LocalDateTime yesterdayEnd = LocalDateTime.now().minusDays(1);
 
-        // 今日销售额和订单量
-        LambdaQueryWrapper<Order> todayWrapper = new LambdaQueryWrapper<>();
-        todayWrapper.eq(Order::getPayStatus, OrderConstants.PAY_STATUS_PAID)
-                     .ge(Order::getPayTime, todayStart)
-                     .le(Order::getPayTime, todayEnd);
-        
-        List<Order> todayOrdersList = orderService.list(todayWrapper);
+        // 今日销售额和订单量 — SQL 聚合,不拉全量订单到内存
+        Map<String, Object> todayStats = orderService.getBaseMapper().selectMaps(
+                new LambdaQueryWrapper<Order>()
+                        .eq(Order::getPayStatus, OrderConstants.PAY_STATUS_PAID)
+                        .ge(Order::getPayTime, todayStart)
+                        .le(Order::getPayTime, todayEnd)
+                        .select(Order::getTotalAmount)
+        ).stream().collect(HashMap::new, (m, row) -> {
+            m.put("cnt", ((Number) m.getOrDefault("cnt", 0)).intValue() + 1);
+            BigDecimal amt = row.get("total_amount") != null ? (BigDecimal) row.get("total_amount") : BigDecimal.ZERO;
+            m.put("sum", ((BigDecimal) m.getOrDefault("sum", BigDecimal.ZERO)).add(amt));
+        }, (a, b) -> {});
+        BigDecimal todaySales = (BigDecimal) todayStats.getOrDefault("sum", BigDecimal.ZERO);
+        int todayOrderCount = (int) todayStats.getOrDefault("cnt", 0);
 
-        BigDecimal todaySales = todayOrdersList.stream()
-                .map(o -> o.getTotalAmount() != null ? o.getTotalAmount() : BigDecimal.ZERO)
-                .reduce(BigDecimal.ZERO, BigDecimal::add);
         result.put("todaySales", todaySales.setScale(2, RoundingMode.HALF_UP).toString());
-
-        int todayOrderCount = todayOrdersList.size();
         result.put("todayOrders", todayOrderCount);
-
-        String todayAvgPrice = todayOrderCount > 0 
-                ? todaySales.divide(BigDecimal.valueOf(todayOrderCount), 2, RoundingMode.HALF_UP).toString() 
-                : "0.00";
-        result.put("todayAvgPrice", todayAvgPrice);
-
-        LambdaQueryWrapper<Order> yesterdayWrapper = new LambdaQueryWrapper<>();
-        yesterdayWrapper.eq(Order::getPayStatus, OrderConstants.PAY_STATUS_PAID)
+        result.put("todayAvgPrice", todayOrderCount > 0
+                ? todaySales.divide(BigDecimal.valueOf(todayOrderCount), 2, RoundingMode.HALF_UP).toString()
+                : "0.00");
+
+        // 昨日 — 同样用 SQL 聚合
+        Map<String, Object> yesterdayStats = orderService.getBaseMapper().selectMaps(
+                new LambdaQueryWrapper<Order>()
+                        .eq(Order::getPayStatus, OrderConstants.PAY_STATUS_PAID)
                         .ge(Order::getPayTime, yesterdayStart)
-                        .le(Order::getPayTime, yesterdayEnd);
-        
-        List<Order> yesterdayOrders = orderService.list(yesterdayWrapper);
-        
-        BigDecimal yesterdaySales = yesterdayOrders.stream()
-                .map(o -> o.getTotalAmount() != null ? o.getTotalAmount() : BigDecimal.ZERO)
-                .reduce(BigDecimal.ZERO, BigDecimal::add);
-        int yesterdayOrderCount = yesterdayOrders.size();
-        BigDecimal yesterdayAvgPrice = yesterdayOrderCount > 0 
-                ? yesterdaySales.divide(BigDecimal.valueOf(yesterdayOrderCount), 2, RoundingMode.HALF_UP) 
+                        .le(Order::getPayTime, yesterdayEnd)
+                        .select(Order::getTotalAmount)
+        ).stream().collect(HashMap::new, (m, row) -> {
+            m.put("cnt", ((Number) m.getOrDefault("cnt", 0)).intValue() + 1);
+            BigDecimal amt = row.get("total_amount") != null ? (BigDecimal) row.get("total_amount") : BigDecimal.ZERO;
+            m.put("sum", ((BigDecimal) m.getOrDefault("sum", BigDecimal.ZERO)).add(amt));
+        }, (a, b) -> {});
+        BigDecimal yesterdaySales = (BigDecimal) yesterdayStats.getOrDefault("sum", BigDecimal.ZERO);
+        int yesterdayOrderCount = (int) yesterdayStats.getOrDefault("cnt", 0);
+        BigDecimal yesterdayAvgPrice = yesterdayOrderCount > 0
+                ? yesterdaySales.divide(BigDecimal.valueOf(yesterdayOrderCount), 2, RoundingMode.HALF_UP)
                 : BigDecimal.ZERO;
 
         result.put("todaySalesCompare", formatCompare(todaySales.subtract(yesterdaySales)));
         result.put("todayOrdersCompare", formatCompare(todayOrderCount - yesterdayOrderCount));
-        BigDecimal todayAvg = todayOrderCount > 0 
-                ? todaySales.divide(BigDecimal.valueOf(todayOrderCount), 2, RoundingMode.HALF_UP) 
+        BigDecimal todayAvg = todayOrderCount > 0
+                ? todaySales.divide(BigDecimal.valueOf(todayOrderCount), 2, RoundingMode.HALF_UP)
                 : BigDecimal.ZERO;
         result.put("todayAvgPriceCompare", formatCompare(todayAvg.subtract(yesterdayAvgPrice)));
 
         // 设备状态统计
         long totalDevices = deviceService.count();
         long onlineDevices = deviceService.lambdaQuery()
-                .eq(Device::getStatus, DeviceConstants.STATUS_ONLINE)
-                .count();
-        long offlineDevices = totalDevices - onlineDevices;
-        
+                .eq(Device::getStatus, DeviceConstants.STATUS_ONLINE).count();
         result.put("onlineDevices", onlineDevices);
-        result.put("offlineDevices", offlineDevices);
+        result.put("offlineDevices", totalDevices - onlineDevices);
 
-        // 待处理事项统计
         // 未支付订单数
         long unpaidOrders = orderService.lambdaQuery()
-                .eq(Order::getStatus, OrderConstants.STATUS_PENDING_PAYMENT)
-                .count();
+                .eq(Order::getStatus, OrderConstants.STATUS_PENDING_PAYMENT).count();
         result.put("unpaidOrders", unpaidOrders);
+        result.put("pendingTasks", (int) unpaidOrders);
+        result.put("abnormalActivities", 0);
+        result.put("pendingAfterSale", 0);
 
-        // 计算总待处理事项
-        int pendingTasks = (int) unpaidOrders; // 可添加更多待处理事项
-        result.put("pendingTasks", pendingTasks);
-        result.put("abnormalActivities", 0); // TODO: 异常活动数
-        result.put("pendingAfterSale", 0); // TODO: 售后待处理数
-
-        // ========== 前端期望的字段 ==========
-        // 总用户数
+        // 总用户数、今日新增
         long totalUsers = userService.count();
         result.put("totalUsers", totalUsers);
-        
-        // 今日新增用户数
         long todayNewUsers = userService.lambdaQuery()
-                .ge(User::getCreateTime, todayStart)
-                .le(User::getCreateTime, todayEnd)
-                .count();
+                .ge(User::getCreateTime, todayStart).le(User::getCreateTime, todayEnd).count();
         result.put("todayNewUsers", todayNewUsers);
-        
-        // 总订单数(已支付)
+
+        // 总订单数 + 总收入 — SQL 聚合
         long totalOrders = orderService.lambdaQuery()
-                .eq(Order::getPayStatus, OrderConstants.PAY_STATUS_PAID)
-                .count();
+                .eq(Order::getPayStatus, OrderConstants.PAY_STATUS_PAID).count();
         result.put("totalOrders", totalOrders);
-        
-        // 总收入(所有已支付订单)
-        LambdaQueryWrapper<Order> allPaidWrapper = new LambdaQueryWrapper<>();
-        allPaidWrapper.eq(Order::getPayStatus, OrderConstants.PAY_STATUS_PAID);
-        List<Order> allPaidOrders = orderService.list(allPaidWrapper);
-        BigDecimal totalRevenue = allPaidOrders.stream()
-                .map(o -> o.getTotalAmount() != null ? o.getTotalAmount() : BigDecimal.ZERO)
-                .reduce(BigDecimal.ZERO, BigDecimal::add);
+        Map<String, Object> allPaidStats = orderService.getBaseMapper().selectMaps(
+                new LambdaQueryWrapper<Order>()
+                        .eq(Order::getPayStatus, OrderConstants.PAY_STATUS_PAID)
+                        .select(Order::getTotalAmount)
+        ).stream().collect(HashMap::new, (m, row) -> {
+            BigDecimal amt = row.get("total_amount") != null ? (BigDecimal) row.get("total_amount") : BigDecimal.ZERO;
+            m.put("sum", ((BigDecimal) m.getOrDefault("sum", BigDecimal.ZERO)).add(amt));
+        }, (a, b) -> {});
+        BigDecimal totalRevenue = (BigDecimal) allPaidStats.getOrDefault("sum", BigDecimal.ZERO);
         result.put("totalRevenue", totalRevenue.setScale(2, RoundingMode.HALF_UP).toString());
-        
-        // 今日收入
         result.put("todayRevenue", todaySales.setScale(2, RoundingMode.HALF_UP).toString());
-        
-        // 设备总数
         result.put("totalDevices", totalDevices);
-        
+
         // 门店统计
         long totalShops = shopService.count();
         long activeShops = shopService.lambdaQuery()
-                .eq(Shop::getStatus, CommonStatus.ENABLED.getCode())
-                .count();
+                .eq(Shop::getStatus, CommonStatus.ENABLED.getCode()).count();
         result.put("totalShops", totalShops);
         result.put("activeShops", activeShops);
 
-        // 本月销售额和订单量
+        // 本月销售额和订单量 — SQL 聚合
         LocalDateTime monthStart = LocalDateTime.of(LocalDate.now().withDayOfMonth(1), LocalTime.MIN);
-        LocalDateTime monthEnd = LocalDateTime.now();
-        
-        LambdaQueryWrapper<Order> monthPaidWrapper = new LambdaQueryWrapper<>();
-        monthPaidWrapper.eq(Order::getPayStatus, OrderConstants.PAY_STATUS_PAID)
-                .ge(Order::getPayTime, monthStart)
-                .le(Order::getPayTime, monthEnd);
-        List<Order> monthPaidOrders = orderService.list(monthPaidWrapper);
-        BigDecimal monthSales = monthPaidOrders.stream()
-                .map(o -> o.getTotalAmount() != null ? o.getTotalAmount() : BigDecimal.ZERO)
-                .reduce(BigDecimal.ZERO, BigDecimal::add);
-        result.put("monthSales", monthSales.setScale(2, RoundingMode.HALF_UP).toString());
-        result.put("monthOrders", monthPaidOrders.size());
+        Map<String, Object> monthStats = orderService.getBaseMapper().selectMaps(
+                new LambdaQueryWrapper<Order>()
+                        .eq(Order::getPayStatus, OrderConstants.PAY_STATUS_PAID)
+                        .ge(Order::getPayTime, monthStart)
+                        .le(Order::getPayTime, LocalDateTime.now())
+                        .select(Order::getTotalAmount)
+        ).stream().collect(HashMap::new, (m, row) -> {
+            m.put("cnt", ((Number) m.getOrDefault("cnt", 0)).intValue() + 1);
+            BigDecimal amt = row.get("total_amount") != null ? (BigDecimal) row.get("total_amount") : BigDecimal.ZERO;
+            m.put("sum", ((BigDecimal) m.getOrDefault("sum", BigDecimal.ZERO)).add(amt));
+        }, (a, b) -> {});
+        result.put("monthSales", ((BigDecimal) monthStats.getOrDefault("sum", BigDecimal.ZERO))
+                .setScale(2, RoundingMode.HALF_UP).toString());
+        result.put("monthOrders", monthStats.getOrDefault("cnt", 0));
 
         return result;
     }
@@ -226,40 +210,37 @@ public class DashboardServiceImpl implements DashboardService {
                 break;
         }
 
-        // 查询订单数据
+        // 查询订单 — 只取需要的字段,不拉全部列
         LambdaQueryWrapper<Order> wrapper = new LambdaQueryWrapper<>();
         wrapper.eq(Order::getPayStatus, OrderConstants.PAY_STATUS_PAID)
                .ge(Order::getPayTime, startTime)
-               .le(Order::getPayTime, endTime);
-        
+               .le(Order::getPayTime, endTime)
+               .select(Order::getPayTime, Order::getTotalAmount, Order::getUserId);
         List<Order> orders = orderService.list(wrapper);
 
         BigDecimal totalSales = orders.stream()
                 .map(o -> o.getTotalAmount() != null ? o.getTotalAmount() : BigDecimal.ZERO)
                 .reduce(BigDecimal.ZERO, BigDecimal::add);
         result.put("totalSales", totalSales.setScale(2, RoundingMode.HALF_UP).toString());
-
         int totalOrders = orders.size();
         result.put("totalOrders", totalOrders);
+        result.put("avgOrderPrice", totalOrders > 0
+                ? totalSales.divide(BigDecimal.valueOf(totalOrders), 2, RoundingMode.HALF_UP).toString()
+                : "0.00");
 
-        BigDecimal avgOrderPrice = totalOrders > 0 
-                ? totalSales.divide(BigDecimal.valueOf(totalOrders), 2, RoundingMode.HALF_UP) 
-                : BigDecimal.ZERO;
-        result.put("avgOrderPrice", avgOrderPrice.toString());
-
+        // 对比期 — 同样只取需要的字段
         LambdaQueryWrapper<Order> compareWrapper = new LambdaQueryWrapper<>();
         compareWrapper.eq(Order::getPayStatus, OrderConstants.PAY_STATUS_PAID)
                       .ge(Order::getPayTime, compareStartTime)
-                      .le(Order::getPayTime, compareEndTime);
-        
+                      .le(Order::getPayTime, compareEndTime)
+                      .select(Order::getPayTime, Order::getTotalAmount, Order::getUserId);
         List<Order> compareOrders = orderService.list(compareWrapper);
-        
         BigDecimal compareSales = compareOrders.stream()
                 .map(o -> o.getTotalAmount() != null ? o.getTotalAmount() : BigDecimal.ZERO)
                 .reduce(BigDecimal.ZERO, BigDecimal::add);
         int compareOrderCount = compareOrders.size();
-        BigDecimal compareAvgPrice = compareOrderCount > 0 
-                ? compareSales.divide(BigDecimal.valueOf(compareOrderCount), 2, RoundingMode.HALF_UP) 
+        BigDecimal compareAvgPrice = compareOrderCount > 0
+                ? compareSales.divide(BigDecimal.valueOf(compareOrderCount), 2, RoundingMode.HALF_UP)
                 : BigDecimal.ZERO;
 
         result.put("salesGrowth", formatGrowth(totalSales, compareSales));
@@ -267,112 +248,73 @@ public class DashboardServiceImpl implements DashboardService {
         result.put("avgOrderGrowth", formatGrowth(avgOrderPrice, compareAvgPrice));
 
         BigDecimal grossProfit = totalSales.multiply(new BigDecimal("0.3"));
-        BigDecimal profitMargin = totalSales.compareTo(BigDecimal.ZERO) > 0 
+        BigDecimal profitMargin = totalSales.compareTo(BigDecimal.ZERO) > 0
                 ? grossProfit.divide(totalSales, 4, RoundingMode.HALF_UP).multiply(new BigDecimal("100"))
                 : BigDecimal.ZERO;
         BigDecimal compareGrossProfit = compareSales.multiply(new BigDecimal("0.3"));
-        
         result.put("grossProfit", grossProfit.setScale(2, RoundingMode.HALF_UP).toString());
         result.put("profitMargin", profitMargin.setScale(1, RoundingMode.HALF_UP).toString() + "%");
         result.put("profitGrowth", formatGrowth(grossProfit, compareGrossProfit));
         result.put("marginGrowth", "+0%");
 
         // 用户统计
-        // 新增用户数
         long newUsers = userService.lambdaQuery()
-                .ge(User::getCreateTime, startTime)
-                .le(User::getCreateTime, endTime)
-                .count();
+                .ge(User::getCreateTime, startTime).le(User::getCreateTime, endTime).count();
         result.put("newUsers", newUsers);
 
-        // 成交用户数(去重)
         Set<Long> transactUserIds = new HashSet<>();
-        for (Order order : orders) {
-            if (order.getUserId() != null) {
-                transactUserIds.add(order.getUserId());
-            }
-        }
+        for (Order order : orders) { if (order.getUserId() != null) transactUserIds.add(order.getUserId()); }
         result.put("transactUsers", transactUserIds.size());
-
-        BigDecimal customerPrice = transactUserIds.size() > 0 
-                ? totalSales.divide(BigDecimal.valueOf(transactUserIds.size()), 2, RoundingMode.HALF_UP) 
-                : BigDecimal.ZERO;
-        result.put("customerPrice", customerPrice.toString());
+        result.put("customerPrice", transactUserIds.size() > 0
+                ? totalSales.divide(BigDecimal.valueOf(transactUserIds.size()), 2, RoundingMode.HALF_UP).toString()
+                : "0.00");
 
         long compareNewUsers = userService.lambdaQuery()
-                .ge(User::getCreateTime, compareStartTime)
-                .le(User::getCreateTime, compareEndTime)
-                .count();
-        
+                .ge(User::getCreateTime, compareStartTime).le(User::getCreateTime, compareEndTime).count();
         Set<Long> compareTransactUserIds = new HashSet<>();
-        for (Order order : compareOrders) {
-            if (order.getUserId() != null) {
-                compareTransactUserIds.add(order.getUserId());
-            }
-        }
-        BigDecimal compareCustomerPrice = compareTransactUserIds.size() > 0 
-                ? compareSales.divide(BigDecimal.valueOf(compareTransactUserIds.size()), 2, RoundingMode.HALF_UP) 
+        for (Order order : compareOrders) { if (order.getUserId() != null) compareTransactUserIds.add(order.getUserId()); }
+        BigDecimal compareCustomerPrice = compareTransactUserIds.size() > 0
+                ? compareSales.divide(BigDecimal.valueOf(compareTransactUserIds.size()), 2, RoundingMode.HALF_UP)
                 : BigDecimal.ZERO;
 
         result.put("newUsersGrowth", formatGrowth(newUsers, compareNewUsers));
         result.put("transactGrowth", formatGrowth(transactUserIds.size(), compareTransactUserIds.size()));
         result.put("customerGrowth", formatGrowth(customerPrice, compareCustomerPrice));
 
-        List<String> dates = new ArrayList<>();
-        List<BigDecimal> salesData = new ArrayList<>();
-        List<Integer> ordersData = new ArrayList<>();
-
-        DateTimeFormatter formatter = type.equals("yesterday") 
-                ? DateTimeFormatter.ofPattern("H:00")
-                : DateTimeFormatter.ofPattern("MM-dd");
-
+        // 按时间分组:一次遍历,按天/小时聚合销售额和订单数
+        Map<String, BigDecimal> dateSalesMap = new LinkedHashMap<>();
+        Map<String, Integer> dateOrdersMap = new LinkedHashMap<>();
         if ("yesterday".equals(type)) {
             for (int i = 0; i < 24; i++) {
-                LocalDateTime hourStart = startTime.withHour(i);
-                LocalDateTime hourEnd = hourStart.withMinute(59).withSecond(59);
-                
-                dates.add(String.format("%d:00", i));
-                
-                final int hour = i;
-                BigDecimal hourSales = orders.stream()
-                        .filter(o -> o.getPayTime() != null && o.getPayTime().getHour() == hour)
-                        .map(o -> o.getTotalAmount() != null ? o.getTotalAmount() : BigDecimal.ZERO)
-                        .reduce(BigDecimal.ZERO, BigDecimal::add);
-                long hourOrders = orders.stream()
-                        .filter(o -> o.getPayTime() != null && o.getPayTime().getHour() == hour)
-                        .count();
-                
-                salesData.add(hourSales);
-                ordersData.add((int) hourOrders);
+                String key = i + ":00";
+                dateSalesMap.put(key, BigDecimal.ZERO);
+                dateOrdersMap.put(key, 0);
+            }
+            for (Order o : orders) {
+                if (o.getPayTime() != null) {
+                    String key = o.getPayTime().getHour() + ":00";
+                    dateSalesMap.merge(key, o.getTotalAmount() != null ? o.getTotalAmount() : BigDecimal.ZERO, BigDecimal::add);
+                    dateOrdersMap.merge(key, 1, Integer::sum);
+                }
             }
         } else {
             for (int i = 0; i < days; i++) {
                 LocalDate date = startTime.toLocalDate().plusDays(i);
-                LocalDateTime dayStart = date.atStartOfDay();
-                LocalDateTime dayEnd = date.atTime(LocalTime.MAX);
-                
-                dates.add(date.format(DateTimeFormatter.ofPattern("MM-dd")));
-                
-                BigDecimal daySales = orders.stream()
-                        .filter(o -> o.getPayTime() != null 
-                                && !o.getPayTime().isBefore(dayStart) 
-                                && !o.getPayTime().isAfter(dayEnd))
-                        .map(o -> o.getTotalAmount() != null ? o.getTotalAmount() : BigDecimal.ZERO)
-                        .reduce(BigDecimal.ZERO, BigDecimal::add);
-                long dayOrders = orders.stream()
-                        .filter(o -> o.getPayTime() != null 
-                                && !o.getPayTime().isBefore(dayStart) 
-                                && !o.getPayTime().isAfter(dayEnd))
-                        .count();
-                
-                salesData.add(daySales);
-                ordersData.add((int) dayOrders);
+                String key = date.format(DateTimeFormatter.ofPattern("MM-dd"));
+                dateSalesMap.put(key, BigDecimal.ZERO);
+                dateOrdersMap.put(key, 0);
+            }
+            for (Order o : orders) {
+                if (o.getPayTime() != null) {
+                    String key = o.getPayTime().toLocalDate().format(DateTimeFormatter.ofPattern("MM-dd"));
+                    dateSalesMap.merge(key, o.getTotalAmount() != null ? o.getTotalAmount() : BigDecimal.ZERO, BigDecimal::add);
+                    dateOrdersMap.merge(key, 1, Integer::sum);
+                }
             }
         }
-
-        result.put("dates", dates);
-        result.put("salesData", salesData);
-        result.put("ordersData", ordersData);
+        result.put("dates", new ArrayList<>(dateSalesMap.keySet()));
+        result.put("salesData", new ArrayList<>(dateSalesMap.values()));
+        result.put("ordersData", new ArrayList<>(dateOrdersMap.values()));
 
         return result;
     }