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.util.StatisticEleAnalyticsUtil;
|
import com.ruoyi.http.util.StatisticEleAnalyticsUtil.DateBounds;
|
import com.ruoyi.http.vo.StatisticEleAnalyticsVo;
|
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.LocalDate;
|
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;
|
|
@Service
|
@Slf4j
|
@RequiredArgsConstructor
|
public class StatisticEleServiceImpl implements StatisticEleService {
|
|
private static final Set<String> STAT_DIMENSIONS = Set.of("day", "week", "month", "quarter", "year");
|
private static final List<String> DATA_DIMENSIONS = List.of("hour", "manual");
|
private static final DateTimeFormatter LOG_TIME_FMT = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
|
|
private static final DateTimeFormatter DAY_FMT = DateTimeFormatter.ofPattern("yyyyMMdd");
|
|
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<StatisticEleRecordVo> 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<StatisticEleRecordVo> hourRecords = queryHourRecords(startTime, endTime);
|
List<StatisticEleRecordVo> chartRecords = StatisticEleAggregateUtil.aggregateHourToBuckets(
|
hourRecords, StatisticEleAggregateUtil.HOUR_TO_HOUR);
|
return buildSummary(hourRecords, chartRecords, hourRecords);
|
}
|
if (!STAT_DIMENSIONS.contains(dimension)) {
|
throw new ServiceException("统计维度无效,支持 hour/day/week/month/quarter/year");
|
}
|
|
if ("day".equals(dimension)) {
|
return getDayDimensionSummary(startTime, endTime);
|
}
|
|
HourRange range = StatisticEleAggregateUtil.toHourQueryRange(dimension, startTime, endTime);
|
List<StatisticEleRecordVo> hourRecords = queryHourRecords(range.startTime(), range.endTime());
|
List<StatisticEleRecordVo> detailRecords = aggregateFromHour(dimension, startTime, endTime, true);
|
List<StatisticEleRecordVo> chartRecords = aggregateFromHour(dimension, startTime, endTime, false);
|
return buildSummary(detailRecords, chartRecords, hourRecords);
|
}
|
|
@Override
|
public StatisticEleAnalyticsVo getAnalytics(String dimension, String startTime, String endTime, String trendGranularity) {
|
if (!StringUtils.hasText(startTime) || !StringUtils.hasText(endTime)) {
|
throw new ServiceException("开始时间和结束时间不能为空");
|
}
|
StatisticEleSummaryVo summary = getSummary(dimension, startTime, endTime);
|
HourRange range = StatisticEleAggregateUtil.toHourQueryRange(
|
normalizeAnalyticsDimension(dimension), startTime, endTime);
|
List<StatisticEleRecordVo> hourRecords = queryHourRecords(range.startTime(), range.endTime());
|
List<StatisticEleRecordVo> hourlyMerged = StatisticEleAggregateUtil.aggregateHourToBuckets(
|
hourRecords, StatisticEleAggregateUtil.HOUR_TO_HOUR);
|
|
boolean singleDay = "day".equals(dimension) && startTime.equals(endTime);
|
String trend = StringUtils.hasText(trendGranularity)
|
? trendGranularity
|
: StatisticEleAnalyticsUtil.defaultTrendGranularity(dimension, singleDay);
|
|
StatisticEleAnalyticsVo analytics = copyToAnalytics(summary);
|
analytics.setLoadRate(StatisticEleAnalyticsUtil.calcLoadRate(
|
summary.getAvgConsumption(), summary.getMaxConsumption()));
|
analytics.setPeriodSplits(StatisticEleAnalyticsUtil.calcPeriodSplits(hourlyMerged));
|
analytics.setShiftSplits(StatisticEleAnalyticsUtil.calcShiftSplits(hourlyMerged));
|
analytics.setDayTypeSplits(StatisticEleAnalyticsUtil.calcDayTypeSplits(hourlyMerged));
|
analytics.setTrendGranularity(trend);
|
analytics.setTrendRecords(StatisticEleAggregateUtil.aggregateHourToBuckets(
|
hourRecords, StatisticEleAnalyticsUtil.trendBucketFn(trend)));
|
|
DateBounds bounds = StatisticEleAnalyticsUtil.resolveBounds(dimension, startTime, endTime);
|
Double chainTotal = queryTotalByDayBounds(StatisticEleAnalyticsUtil.shiftChain(bounds));
|
Double yoyTotal = queryTotalByDayBounds(StatisticEleAnalyticsUtil.shiftYoy(bounds));
|
analytics.setChainComparison(StatisticEleAnalyticsUtil.buildComparison(
|
"chain", "环比上期", summary.getTotalConsumption(), chainTotal));
|
analytics.setYoyComparison(StatisticEleAnalyticsUtil.buildComparison(
|
"yoy", "同比去年同期", summary.getTotalConsumption(), yoyTotal));
|
return analytics;
|
}
|
|
private String normalizeAnalyticsDimension(String dimension) {
|
return STAT_DIMENSIONS.contains(dimension) ? dimension : "day";
|
}
|
|
private Double queryTotalByDayBounds(DateBounds bounds) {
|
String start = bounds.start().format(DAY_FMT);
|
String end = bounds.end().format(DAY_FMT);
|
HourRange range = StatisticEleAggregateUtil.toHourQueryRange("day", start, end);
|
List<StatisticEleRecordVo> hourRecords = queryHourRecords(range.startTime(), range.endTime());
|
if (hourRecords.isEmpty()) {
|
return 0.0;
|
}
|
return StatisticEleAnalyticsUtil.calcCombinedMetrics(
|
hourRecords,
|
StatisticEleAggregateUtil.aggregateHourToBuckets(hourRecords, StatisticEleAggregateUtil.HOUR_TO_DAY)
|
).getTotalConsumption();
|
}
|
|
private StatisticEleAnalyticsVo copyToAnalytics(StatisticEleSummaryVo summary) {
|
StatisticEleAnalyticsVo analytics = new StatisticEleAnalyticsVo();
|
analytics.setTotalConsumption(summary.getTotalConsumption());
|
analytics.setAvgConsumption(summary.getAvgConsumption());
|
analytics.setMaxConsumption(summary.getMaxConsumption());
|
analytics.setMinConsumption(summary.getMinConsumption());
|
analytics.setRecordCount(summary.getRecordCount());
|
analytics.setChartRecords(summary.getChartRecords());
|
analytics.setRecords(summary.getRecords());
|
return analytics;
|
}
|
|
/**
|
* 天维度汇总:单日用小时级数据计算均值/极值并展示 24 小时趋势;多日按日桶对比。
|
*/
|
private StatisticEleSummaryVo getDayDimensionSummary(String startTime, String endTime) {
|
HourRange range = StatisticEleAggregateUtil.toHourQueryRange("day", startTime, endTime);
|
List<StatisticEleRecordVo> hourRecords = queryHourRecords(range.startTime(), range.endTime());
|
List<StatisticEleRecordVo> detailRecords = StatisticEleAggregateUtil.aggregateHourPerMeter(
|
hourRecords, StatisticEleAggregateUtil.HOUR_TO_DAY);
|
|
boolean singleDay = startTime.equals(endTime);
|
List<StatisticEleRecordVo> chartRecords = singleDay
|
? StatisticEleAggregateUtil.aggregateHourToBuckets(hourRecords, StatisticEleAggregateUtil.HOUR_TO_HOUR)
|
: StatisticEleAggregateUtil.aggregateHourToBuckets(hourRecords, StatisticEleAggregateUtil.HOUR_TO_DAY);
|
|
return buildSummary(detailRecords, chartRecords, hourRecords);
|
}
|
|
private StatisticEleSummaryVo buildSummary(
|
List<StatisticEleRecordVo> detailRecords,
|
List<StatisticEleRecordVo> chartRecords,
|
List<StatisticEleRecordVo> hourRecords) {
|
StatisticEleAggregateUtil.StatisticEleSummaryMetrics metrics =
|
StatisticEleAnalyticsUtil.calcCombinedMetrics(hourRecords, 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() {
|
String day = LocalDate.now().minusDays(1).format(DateTimeFormatter.ofPattern("yyyyMMdd"));
|
return getDayDimensionSummary(day, day);
|
}
|
|
@Override
|
public void exportRecords(String dimension, String startTime, String endTime, HttpServletResponse response) {
|
List<StatisticEleRecordVo> records;
|
if ("hour".equals(dimension)) {
|
records = queryHourRecords(startTime, endTime);
|
} else {
|
records = aggregateFromHour(dimension, startTime, endTime, true);
|
}
|
ExcelUtil<StatisticEleRecordVo> 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.<TqdianbiaoCollector>lambdaQuery().eq(TqdianbiaoCollector::getOnline, true))));
|
|
Map<String, Long> recordCountByDimension = new HashMap<>();
|
recordCountByDimension.put("hour", eleRecordMapper.selectCount(
|
Wrappers.<TqdianbiaoEleRecord>lambdaQuery().eq(TqdianbiaoEleRecord::getDimension, "hour")));
|
status.setRecordCountByDimension(recordCountByDimension);
|
|
Map<String, String> lastSyncTimeByType = new HashMap<>();
|
for (String syncType : List.of("collector", "meter", "hour")) {
|
TqdianbiaoSyncLog latest = syncLogMapper.selectOne(
|
Wrappers.<TqdianbiaoSyncLog>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<StatisticEleRecordVo> queryHourRecords(String startTime, String endTime) {
|
String normalizedStart = StatisticEleAggregateUtil.normalizeQueryStartTimeKey(startTime);
|
String normalizedEnd = StatisticEleAggregateUtil.normalizeQueryEndTimeKey(endTime);
|
List<StatisticEleRecordVo> records = eleRecordMapper.selectRecordList(DATA_DIMENSIONS, normalizedStart, normalizedEnd);
|
StatisticEleAggregateUtil.normalizeConsumptions(records);
|
return records;
|
}
|
|
private List<StatisticEleRecordVo> aggregateFromHour(
|
String dimension, String startTime, String endTime, boolean perMeter) {
|
HourRange range = StatisticEleAggregateUtil.toHourQueryRange(dimension, startTime, endTime);
|
List<StatisticEleRecordVo> hourRecords = queryHourRecords(range.startTime(), range.endTime());
|
Function<String, String> bucketFn = StatisticEleAggregateUtil.bucketFn(dimension);
|
if (perMeter) {
|
return StatisticEleAggregateUtil.aggregateHourPerMeter(hourRecords, bucketFn);
|
}
|
return StatisticEleAggregateUtil.aggregateHourToBuckets(hourRecords, bucketFn);
|
}
|
}
|