package com.bizmatics.service.util; import com.bizmatics.model.utils.TimeRangeParams; import com.bizmatics.model.utils.TimeRangeUtils; import com.github.benmanes.caffeine.cache.Cache; import com.github.benmanes.caffeine.cache.Caffeine; import com.bizmatics.persistence.mapper.HtAnalogDataMapper; import com.github.benmanes.caffeine.cache.stats.CacheStats; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import lombok.var; import org.springframework.scheduling.annotation.Scheduled; import org.springframework.stereotype.Component; import java.math.BigDecimal; import java.util.List; import java.util.Map; import java.util.concurrent.TimeUnit; import java.util.stream.Collectors; /** * * @author fyc * @email yuchuan.fu@chinausky.com * @date 2025/8/28 */ @Slf4j @Component @RequiredArgsConstructor public class AnalogCache { private final HtAnalogDataMapper htAnalogDataMapper; /** * 本地缓存:expireAfterWrite = 60 秒 */ private final Cache> cache = Caffeine.newBuilder() // 根据设备量自行调节 .maximumSize(5_000) .expireAfterAccess(2, TimeUnit.MINUTES) .expireAfterWrite(5, TimeUnit.MINUTES) // 命中率监控 .recordStats() .build(); /** * 对外唯一入口 */ public Map get(String deviceCode, TimeRangeUtils.TimeRanges ranges) { String key = AggCacheKey.of(deviceCode, ranges).signature(); // 如果 key 不存在,自动调用 loadFromDb return cache.get(key, k -> loadFromDb(deviceCode, ranges)); } /** * 回源:真正查数据库 */ private Map loadFromDb(String deviceCode, TimeRangeUtils.TimeRanges ranges) { TimeRangeParams params = TimeRangeParams.builder() .deviceCode(deviceCode) .today(ranges.getToday()) .monthStart(ranges.getMonthRange()[0]) .monthEnd(ranges.getMonthRange()[1]) .yearStart(ranges.getYearRange()[0]) .yearEnd(ranges.getYearRange()[1]) .yesterday(ranges.getYesterdayRange()[0]) .lastMonthStart(ranges.getLastMonthRange()[0]) .lastMonthEnd(ranges.getLastMonthRange()[1]) .lastYearStart(ranges.getLastYearRange()[0]) .lastYearEnd(ranges.getLastYearRange()[1]) .sameDayStart(ranges.getSameDayRange()[0]) .sameDayEnd(ranges.getSameDayRange()[1]) .sameMonthStart(ranges.getSameMonthRange()[0]) .sameMonthEnd(ranges.getSameMonthRange()[1]) .sameYearStart(ranges.getSameYearRange()[0]) .sameYearEnd(ranges.getSameYearRange()[1]) .lastYearSameDayStart(ranges.getLastYearSameDay()[0]) .lastYearSameDayEnd(ranges.getLastYearSameDay()[1]) .lastYearSameMonthStart(ranges.getLastYearSameMonth()[0]) .lastYearSameMonthEnd(ranges.getLastYearSameMonth()[1]) .build(); List> rows = htAnalogDataMapper.aggregateAll(params); return rows.stream() .collect(Collectors.toMap( r -> (String) r.get("type"), r -> new BigDecimal(r.get("value").toString()) )); } /* ---------------- 命中率日志(可选) ---------------- */ @Scheduled(fixedDelay = 30_000) public void logCacheStats() { CacheStats stats = cache.stats(); long size = cache.estimatedSize(); // 预先格式化数字 String hitRateFormatted = String.format("%.2f", stats.hitRate() * 100); String avgLoadTimeFormatted = String.format("%.2f", stats.averageLoadPenalty() / 1_000_000.0); log.info("缓存统计 - 大小: {}, 命中率: {}%, 加载次数: {}, 加载失败: {}, 平均加载时间: {}ms", size, hitRateFormatted, stats.loadCount(), stats.loadFailureCount(), avgLoadTimeFormatted); } }