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