From 9f20477f45b8e15a3b456fb183a5aab4b42eb9a5 Mon Sep 17 00:00:00 2001
From: longz <longzhaoz@qq.com>
Date: 星期二, 04 八月 2026 15:53:06 +0800
Subject: [PATCH] 大屏数据缓存

---
 ruoyi-service/ruoyi-order/src/main/java/com/ruoyi/order/service/impl/TChargingOrderServiceImpl.java |  269 ++++++++++++++++++++++++++++++++++++++++++++++-------
 1 files changed, 231 insertions(+), 38 deletions(-)

diff --git a/ruoyi-service/ruoyi-order/src/main/java/com/ruoyi/order/service/impl/TChargingOrderServiceImpl.java b/ruoyi-service/ruoyi-order/src/main/java/com/ruoyi/order/service/impl/TChargingOrderServiceImpl.java
index 02c93a4..992e9c3 100644
--- a/ruoyi-service/ruoyi-order/src/main/java/com/ruoyi/order/service/impl/TChargingOrderServiceImpl.java
+++ b/ruoyi-service/ruoyi-order/src/main/java/com/ruoyi/order/service/impl/TChargingOrderServiceImpl.java
@@ -103,6 +103,7 @@
 import org.springframework.transaction.support.TransactionSynchronization;
 import org.springframework.transaction.support.TransactionSynchronizationManager;
 import org.springframework.transaction.support.TransactionTemplate;
+import org.springframework.util.DigestUtils;
 import org.springframework.util.StringUtils;
 
 import javax.annotation.Resource;
@@ -111,6 +112,7 @@
 import java.math.BigDecimal;
 import java.math.MathContext;
 import java.math.RoundingMode;
+import java.nio.charset.StandardCharsets;
 import java.text.ParseException;
 import java.text.SimpleDateFormat;
 import java.time.*;
@@ -144,6 +146,12 @@
     private static final int ENTERPRISE_INVOICE_BATCH_SIZE = 500;
     private static final int ENTERPRISE_ORDER_ID_BATCH_SIZE = 500;
     private static final int CHARGING_PERIOD_ACCOUNTING_BATCH_SIZE = 500;
+    private static final String LARGE_SCREEN_CACHE_PREFIX = "large:screen:";
+    private static final long LARGE_SCREEN_CACHE_SECONDS = 60L;
+    private static final LocalDate LARGE_SCREEN_STAT_DATA_START = LocalDate.of(2020, 1, 1);
+    private static final DateTimeFormatter LARGE_SCREEN_MONTH_FMT = DateTimeFormatter.ofPattern("yyyy-MM");
+    private static final DateTimeFormatter LARGE_SCREEN_DAY_FMT = DateTimeFormatter.ofPattern("yyyy-MM-dd");
+    private static final DateTimeFormatter LARGE_SCREEN_SQL_FMT = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
 
     @Override
     public ReservationRecommendationVO getReservationRecommendation(Integer siteId) {
@@ -6693,58 +6701,243 @@
 
     @Override
     public Map<String, Object> getLargeScreenSummary(List<Integer> siteIds) {
-        return this.baseMapper.getLargeScreenSummary(siteIds);
+        String cacheKey = largeScreenCacheKey("summary", siteIds);
+        Map<String, Object> cached = redisService.getCacheObject(cacheKey);
+        if (cached != null) {
+            return cached;
+        }
+        Map<String, Object> result = new HashMap<>();
+        try {
+            Map<String, Object> summary = this.baseMapper.summaryFromStat(siteIds);
+            if (summary != null) {
+                result.putAll(summary);
+            }
+            Long registered = this.baseMapper.registeredFromStat();
+            result.put("registeredUserCount", registered == null ? 0L : registered);
+        } catch (Exception e) {
+            log.warn("大屏累计指标读取预聚合表失败, siteIds={}", siteIds, e);
+        }
+        Arrays.asList("validOrderCount", "chargingUserCount", "registeredUserCount",
+                "totalChargingCapacity", "electricityFee", "serviceFee", "totalIncome")
+                .forEach(key -> result.putIfAbsent(key, 0));
+        redisService.setCacheObject(cacheKey, result, LARGE_SCREEN_CACHE_SECONDS, TimeUnit.SECONDS);
+        return result;
     }
 
     @Override
     public List<SixChargingDto> getLargeScreenIncomeTrend(List<Integer> siteIds, LocalDate sixBefore) {
-        String siteIdsCsv = toSiteIdsCsv(siteIds);
-        List<SixChargingDto> result = new ArrayList<>();
-        LocalDate currentMonth = LocalDate.now().withDayOfMonth(1);
-        LocalDate month = sixBefore.withDayOfMonth(1);
-        while (!month.isAfter(currentMonth)) {
-            LocalDate nextMonth = month.plusMonths(1);
-            SixChargingDto income = this.baseMapper.getLargeScreenCurrentMonthIncome(
-                    siteIdsCsv, month.toString(), nextMonth.toString());
-            if (income == null) {
-                income = new SixChargingDto();
-            }
-            income.setMonth(String.format("%02d", month.getMonthValue()));
-            if (income.getElectrovalence() == null) {
-                income.setElectrovalence(BigDecimal.ZERO);
-            }
-            if (income.getServiceCharge() == null) {
-                income.setServiceCharge(BigDecimal.ZERO);
-            }
-            result.add(income);
-            month = nextMonth;
+        String cacheKey = largeScreenCacheKey("incomeTrend", siteIds);
+        List<SixChargingDto> cached = redisService.getCacheObject(cacheKey);
+        if (cached != null) {
+            return cached;
         }
+        LocalDate currentMonth = LocalDate.now().withDayOfMonth(1);
+        List<SixChargingDto> result = new ArrayList<>();
+        try {
+            String sixBeforeMonth = sixBefore.withDayOfMonth(1).format(LARGE_SCREEN_MONTH_FMT);
+            List<SixChargingDto> incomeList = this.baseMapper.incomeTrendFromStat(siteIds, sixBeforeMonth);
+            Map<String, SixChargingDto> incomeByMonth = new HashMap<>();
+            if (incomeList != null) {
+                for (SixChargingDto income : incomeList) {
+                    if (income != null && income.getMonth() != null) {
+                        incomeByMonth.put(income.getMonth(), income);
+                    }
+                }
+            }
+            LocalDate month = sixBefore.withDayOfMonth(1);
+            while (!month.isAfter(currentMonth)) {
+                SixChargingDto income = incomeByMonth.get(month.format(LARGE_SCREEN_MONTH_FMT));
+                if (income == null) {
+                    income = new SixChargingDto();
+                }
+                income.setMonth(String.format("%02d", month.getMonthValue()));
+                if (income.getElectrovalence() == null) {
+                    income.setElectrovalence(BigDecimal.ZERO);
+                }
+                if (income.getServiceCharge() == null) {
+                    income.setServiceCharge(BigDecimal.ZERO);
+                }
+                result.add(income);
+                month = month.plusMonths(1);
+            }
+        } catch (Exception e) {
+            log.warn("大屏收入趋势读取预聚合表失败, siteIds={}, sixBefore={}", siteIds, sixBefore, e);
+            LocalDate month = sixBefore.withDayOfMonth(1);
+            while (!month.isAfter(currentMonth)) {
+                SixChargingDto income = new SixChargingDto();
+                income.setMonth(String.format("%02d", month.getMonthValue()));
+                income.setElectrovalence(BigDecimal.ZERO);
+                income.setServiceCharge(BigDecimal.ZERO);
+                result.add(income);
+                month = month.plusMonths(1);
+            }
+        }
+        redisService.setCacheObject(cacheKey, result, LARGE_SCREEN_CACHE_SECONDS, TimeUnit.SECONDS);
         return result;
     }
 
     @Override
     public List<LargeChargingPowerVo> getLargeScreenChargingPower(List<Integer> siteIds, LocalDateTime startTime, LocalDateTime endTime) {
-        DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
-        String siteIdsCsv = toSiteIdsCsv(siteIds);
-        List<LargeChargingPowerVo> result = new ArrayList<>();
-        LocalDateTime dayStart = startTime;
-        while (dayStart.isBefore(endTime)) {
-            LocalDateTime dayEnd = dayStart.plusDays(1);
-            Map<String, Object> aggregate = this.baseMapper.getLargeScreenChargingPower(
-                    siteIdsCsv, dayStart.format(formatter), dayEnd.format(formatter));
-            BigDecimal totalPower = decimalValue(aggregate, "total_power");
-            BigDecimal orderCount = decimalValue(aggregate, "order_count");
-            BigDecimal power = orderCount.signum() == 0 ? BigDecimal.ZERO
-                    : totalPower.divide(orderCount, 0, RoundingMode.HALF_UP);
-            LargeChargingPowerVo item = new LargeChargingPowerVo();
-            item.setTime(dayStart.toLocalDate().toString());
-            item.setPower(power == null ? BigDecimal.ZERO : power);
-            result.add(item);
-            dayStart = dayEnd;
+        String cacheKey = largeScreenCacheKey("power", siteIds);
+        List<LargeChargingPowerVo> cached = redisService.getCacheObject(cacheKey);
+        if (cached != null) {
+            return cached;
         }
+        List<LargeChargingPowerVo> result = new ArrayList<>();
+        try {
+            String dayStart = startTime.toLocalDate().format(LARGE_SCREEN_DAY_FMT);
+            String dayEnd = endTime.toLocalDate().format(LARGE_SCREEN_DAY_FMT);
+            List<Map<String, Object>> dayAggs = this.baseMapper.powerFromStat(siteIds, dayStart, dayEnd);
+            if (dayAggs != null) {
+                for (Map<String, Object> agg : dayAggs) {
+                    BigDecimal totalPower = decimalValue(agg, "total_power");
+                    BigDecimal orderCount = decimalValue(agg, "order_count");
+                    BigDecimal power = orderCount.signum() == 0 ? BigDecimal.ZERO
+                            : totalPower.divide(orderCount, 0, RoundingMode.HALF_UP);
+                    LargeChargingPowerVo item = new LargeChargingPowerVo();
+                    Object day = agg.get("day");
+                    item.setTime(day == null ? null : String.valueOf(day));
+                    item.setPower(power);
+                    result.add(item);
+                }
+            }
+        } catch (Exception e) {
+            log.warn("大屏功率曲线读取预聚合表失败, siteIds={}", siteIds, e);
+        }
+        redisService.setCacheObject(cacheKey, result, LARGE_SCREEN_CACHE_SECONDS, TimeUnit.SECONDS);
         return result;
     }
 
+    @Override
+    public void rebuildLargeScreenStat() {
+        log.info("大屏预聚合表全量重建开始");
+        long start = System.currentTimeMillis();
+        // 1. 月度切片全量(数据起始月 ~ 当前月), 每次切片一次查询, 避免单条大查询超时
+        LocalDate month = LARGE_SCREEN_STAT_DATA_START.withDayOfMonth(1);
+        LocalDate currentMonth = LocalDate.now().withDayOfMonth(1);
+        while (!month.isAfter(currentMonth)) {
+            writeMonthSlice(month, month.plusMonths(1));
+            month = month.plusMonths(1);
+        }
+        // 2. 近90天日切片
+        LocalDate day = LocalDate.now().minusDays(89);
+        LocalDate today = LocalDate.now();
+        while (!day.isAfter(today)) {
+            writeDaySlice(day);
+            day = day.plusDays(1);
+        }
+        // 3. 全局注册用户数
+        writeGlobal();
+        log.info("大屏预聚合表全量重建完成, 耗时 {}ms", System.currentTimeMillis() - start);
+    }
+
+    @Override
+    public void refreshLargeScreenStat() {
+        long start = System.currentTimeMillis();
+        LocalDate currentMonth = LocalDate.now().withDayOfMonth(1);
+        writeMonthSlice(currentMonth, currentMonth.plusMonths(1));
+        writeDaySlice(LocalDate.now());
+        writeGlobal();
+        log.info("大屏预聚合表刷新完成, 耗时 {}ms", System.currentTimeMillis() - start);
+    }
+
+    /**
+     * 写入一个月度切片聚合(M 行)
+     */
+    private void writeMonthSlice(LocalDate monthStart, LocalDate monthEnd) {
+        List<Map<String, Object>> rows = this.baseMapper.monthSliceAgg(
+                monthStart.atStartOfDay().format(LARGE_SCREEN_SQL_FMT),
+                monthEnd.atStartOfDay().format(LARGE_SCREEN_SQL_FMT));
+        if (rows == null || rows.isEmpty()) {
+            return;
+        }
+        List<Map<String, Object>> statRows = new ArrayList<>();
+        for (Map<String, Object> row : rows) {
+            if (row == null || row.get("site_id") == null) {
+                continue;
+            }
+            BigDecimal electricFee = decimalValue(row, "electric_fee");
+            BigDecimal serviceFee = decimalValue(row, "service_fee");
+            Map<String, Object> stat = new HashMap<>();
+            stat.put("statType", "M");
+            stat.put("period", monthStart.format(LARGE_SCREEN_MONTH_FMT));
+            stat.put("siteId", row.get("site_id"));
+            stat.put("orderCount", decimalValue(row, "order_count"));
+            stat.put("userCount", decimalValue(row, "user_count"));
+            stat.put("capacity", decimalValue(row, "capacity"));
+            stat.put("electricFee", electricFee);
+            stat.put("serviceFee", serviceFee);
+            stat.put("income", electricFee.add(serviceFee));
+            stat.put("totalPower", BigDecimal.ZERO);
+            stat.put("registeredUserCount", 0L);
+            statRows.add(stat);
+        }
+        if (!statRows.isEmpty()) {
+            this.baseMapper.replaceSiteStatBatch(statRows);
+        }
+    }
+
+    /**
+     * 写入一个日切片聚合(D 行)
+     */
+    private void writeDaySlice(LocalDate day) {
+        List<Map<String, Object>> rows = this.baseMapper.daySliceAgg(
+                day.atStartOfDay().format(LARGE_SCREEN_SQL_FMT),
+                day.plusDays(1).atStartOfDay().format(LARGE_SCREEN_SQL_FMT));
+        if (rows == null || rows.isEmpty()) {
+            return;
+        }
+        List<Map<String, Object>> statRows = new ArrayList<>();
+        for (Map<String, Object> row : rows) {
+            if (row == null || row.get("site_id") == null) {
+                continue;
+            }
+            Map<String, Object> stat = new HashMap<>();
+            stat.put("statType", "D");
+            stat.put("period", day.format(LARGE_SCREEN_DAY_FMT));
+            stat.put("siteId", row.get("site_id"));
+            stat.put("orderCount", decimalValue(row, "order_count"));
+            stat.put("userCount", BigDecimal.ZERO);
+            stat.put("capacity", BigDecimal.ZERO);
+            stat.put("electricFee", BigDecimal.ZERO);
+            stat.put("serviceFee", BigDecimal.ZERO);
+            stat.put("income", BigDecimal.ZERO);
+            stat.put("totalPower", decimalValue(row, "total_power"));
+            stat.put("registeredUserCount", 0L);
+            statRows.add(stat);
+        }
+        if (!statRows.isEmpty()) {
+            this.baseMapper.replaceSiteStatBatch(statRows);
+        }
+    }
+
+    /**
+     * 写入全局注册用户数(G 行)
+     */
+    private void writeGlobal() {
+        Long registered = this.baseMapper.globalRegisteredUsers();
+        Map<String, Object> stat = new HashMap<>();
+        stat.put("statType", "G");
+        stat.put("period", "ALL");
+        stat.put("siteId", 0);
+        stat.put("orderCount", 0L);
+        stat.put("userCount", 0L);
+        stat.put("capacity", BigDecimal.ZERO);
+        stat.put("electricFee", BigDecimal.ZERO);
+        stat.put("serviceFee", BigDecimal.ZERO);
+        stat.put("income", BigDecimal.ZERO);
+        stat.put("totalPower", BigDecimal.ZERO);
+        stat.put("registeredUserCount", registered == null ? 0L : registered);
+        this.baseMapper.replaceSiteStatBatch(Collections.singletonList(stat));
+    }
+
+    private String largeScreenCacheKey(String endpoint, List<Integer> siteIds) {
+        String idPart = siteIds == null ? "all" : DigestUtils.md5DigestAsHex(
+                siteIds.stream().sorted().map(String::valueOf).collect(Collectors.joining(","))
+                        .getBytes(StandardCharsets.UTF_8));
+        return LARGE_SCREEN_CACHE_PREFIX + endpoint + ":" + idPart;
+    }
+
     private BigDecimal decimalValue(Map<String, Object> values, String key) {
         if (values == null) {
             return BigDecimal.ZERO;

--
Gitblit v1.7.1