package com.ruoyi.http.service.impl; import com.baomidou.mybatisplus.core.toolkit.Wrappers; import com.ruoyi.common.exception.ServiceException; import com.ruoyi.common.utils.http.HttpUtils; import com.ruoyi.common.utils.poi.ExcelUtil; import com.ruoyi.http.config.TqdianbiaoConfig; import com.ruoyi.http.mapper.TqdianbiaoCollectorMapper; import com.ruoyi.http.mapper.TqdianbiaoEleRecordMapper; import com.ruoyi.http.mapper.TqdianbiaoMeterMapper; import com.ruoyi.http.mapper.TqdianbiaoSyncLogMapper; import com.ruoyi.http.pojo.TqdianbiaoCollector; import com.ruoyi.http.pojo.TqdianbiaoEleRecord; import com.ruoyi.http.pojo.TqdianbiaoSyncLog; import com.ruoyi.http.service.StatisticEleService; import com.ruoyi.http.util.StatisticEleAggregateUtil; import com.ruoyi.http.util.StatisticEleAggregateUtil.HourRange; import com.ruoyi.http.vo.StatisticEleRecordVo; import com.ruoyi.http.vo.StatisticEleSummaryVo; import com.ruoyi.http.vo.StatisticEleSyncStatusVo; import jakarta.servlet.http.HttpServletResponse; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Service; import org.springframework.util.StringUtils; import java.time.format.DateTimeFormatter; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Set; import java.util.function.Function; import java.util.stream.Collectors; @Service @Slf4j @RequiredArgsConstructor public class StatisticEleServiceImpl implements StatisticEleService { private static final Set STAT_DIMENSIONS = Set.of("day", "month", "quarter", "year"); private static final List DATA_DIMENSIONS = List.of("hour", "manual"); private static final DateTimeFormatter LOG_TIME_FMT = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"); private final TqdianbiaoConfig config; private final TqdianbiaoEleRecordMapper eleRecordMapper; private final TqdianbiaoMeterMapper meterMapper; private final TqdianbiaoCollectorMapper collectorMapper; private final TqdianbiaoSyncLogMapper syncLogMapper; @Override public String fetchRawData(String dimension, String startTime, String endTime) { if (!"hour".equals(dimension)) { throw new ServiceException("仅支持拉取小时原始数据"); } String url = config.getBaseUrl() + "/Api/StatisticEle/hour"; String param = String.format( "auth=%s&start_time=%s&end_time=%s&ignore_radio=%d", config.getAuth(), startTime, endTime, config.getIgnoreRadio() ); log.warn("调用远程电表接口(调试): {}?{}", url, param); return HttpUtils.sendGet(url, param); } @Override public List listRecords(String dimension, String startTime, String endTime, Integer ignoreRadio) { if ("hour".equals(dimension) || "collection".equals(dimension)) { return queryHourRecords(startTime, endTime); } return aggregateFromHour(dimension, startTime, endTime, true); } @Override public StatisticEleSummaryVo getSummary(String dimension, String startTime, String endTime) { if (!StringUtils.hasText(startTime) || !StringUtils.hasText(endTime)) { throw new ServiceException("开始时间和结束时间不能为空"); } if ("hour".equals(dimension)) { List detailRecords = queryHourRecords(startTime, endTime); List chartRecords = StatisticEleAggregateUtil.aggregateHourToBuckets( detailRecords, StatisticEleAggregateUtil.HOUR_TO_HOUR); return buildSummary(detailRecords, chartRecords); } if (!STAT_DIMENSIONS.contains(dimension)) { throw new ServiceException("统计维度无效,支持 hour/day/month/quarter/year"); } List detailRecords = aggregateFromHour(dimension, startTime, endTime, true); List chartRecords = aggregateFromHour(dimension, startTime, endTime, false); return buildSummary(detailRecords, chartRecords); } private StatisticEleSummaryVo buildSummary( List detailRecords, List chartRecords) { StatisticEleAggregateUtil.StatisticEleSummaryMetrics metrics = StatisticEleAggregateUtil.calcMetrics(chartRecords); StatisticEleSummaryVo summary = new StatisticEleSummaryVo(); summary.setRecords(detailRecords); summary.setChartRecords(chartRecords); summary.setRecordCount(metrics.getRecordCount()); summary.setTotalConsumption(metrics.getTotalConsumption()); summary.setAvgConsumption(metrics.getAvgConsumption()); summary.setMaxConsumption(metrics.getMaxConsumption()); summary.setMinConsumption(metrics.getMinConsumption()); return summary; } @Override public StatisticEleSummaryVo getYesterdaySummary() { HourRange range = StatisticEleAggregateUtil.yesterdayHourRange(); List records = queryHourRecords(range.startTime(), range.endTime()); List chartRecords = StatisticEleAggregateUtil.aggregateHourToBuckets( records, StatisticEleAggregateUtil.HOUR_TO_HOUR); StatisticEleSummaryVo summary = buildSummary(records, chartRecords); summary.setTotalConsumption(round(StatisticEleAggregateUtil.sumRecordsTotal(records))); return summary; } private static double round(double value) { return Math.round(value * 100.0) / 100.0; } @Override public void exportRecords(String dimension, String startTime, String endTime, HttpServletResponse response) { List records; if ("hour".equals(dimension)) { records = queryHourRecords(startTime, endTime); } else { records = aggregateFromHour(dimension, startTime, endTime, true); } ExcelUtil util = new ExcelUtil<>(StatisticEleRecordVo.class); util.exportExcel(response, records, "能耗统计数据"); } @Override public StatisticEleSyncStatusVo getSyncStatus() { StatisticEleSyncStatusVo status = new StatisticEleSyncStatusVo(); status.setMeterCount(Math.toIntExact(meterMapper.selectCount(null))); status.setCollectorCount(Math.toIntExact(collectorMapper.selectCount(null))); status.setOnlineCollectorCount(Math.toIntExact(collectorMapper.selectCount( Wrappers.lambdaQuery().eq(TqdianbiaoCollector::getOnline, true)))); Map recordCountByDimension = new HashMap<>(); recordCountByDimension.put("hour", eleRecordMapper.selectCount( Wrappers.lambdaQuery().eq(TqdianbiaoEleRecord::getDimension, "hour"))); status.setRecordCountByDimension(recordCountByDimension); Map lastSyncTimeByType = new HashMap<>(); for (String syncType : List.of("collector", "meter", "hour")) { TqdianbiaoSyncLog latest = syncLogMapper.selectOne( Wrappers.lambdaQuery() .eq(TqdianbiaoSyncLog::getSyncType, syncType) .eq(TqdianbiaoSyncLog::getStatus, "success") .orderByDesc(TqdianbiaoSyncLog::getCreateTime) .last("LIMIT 1")); if (latest != null && latest.getCreateTime() != null) { lastSyncTimeByType.put(syncType, latest.getCreateTime().format(LOG_TIME_FMT)); } } status.setLastSyncTimeByType(lastSyncTimeByType); return status; } private List queryHourRecords(String startTime, String endTime) { String normalizedStart = StatisticEleAggregateUtil.normalizeQueryStartTimeKey(startTime); String normalizedEnd = StatisticEleAggregateUtil.normalizeQueryEndTimeKey(endTime); return eleRecordMapper.selectRecordList(DATA_DIMENSIONS, normalizedStart, normalizedEnd); } private List aggregateFromHour( String dimension, String startTime, String endTime, boolean perMeter) { HourRange range = StatisticEleAggregateUtil.toHourQueryRange(dimension, startTime, endTime); List hourRecords = queryHourRecords(range.startTime(), range.endTime()); Function bucketFn = StatisticEleAggregateUtil.bucketFn(dimension); if (perMeter) { return StatisticEleAggregateUtil.aggregateHourPerMeter(hourRecords, bucketFn); } return StatisticEleAggregateUtil.aggregateHourToBuckets(hourRecords, bucketFn); } }