feat(ems): 新增能耗统计分析功能

- 新增环比分析、同比分析接口与实现
- 支持按设备、能源类型、区域(含子区域)过滤统计
- 实现日、周、月、年维度的数据汇总与对比计算
- 扩展 Mapper 层支持带设备列表的时间区间查询
- 新增多个 VO 类用于封装分析结果数据结构
- 完善 Controller 接口接收过滤参数并调用服务层
-优化区域递归查找逻辑以支持层级结构查询
- 提供工具方法处理空值与比率计算
- 添加新的 BO 类用于传递查询与范围参数
- 更新配置文件注释但未启用新路由规则
This commit is contained in:
JR
2025-09-30 18:46:57 +08:00
parent 29c0c7bb73
commit c927ce247b
14 changed files with 562 additions and 27 deletions

View File

@@ -2,11 +2,14 @@ package com.klp.ems.service;
import com.klp.ems.domain.vo.EmsEnergyConsumptionVo;
import com.klp.ems.domain.bo.EmsEnergyConsumptionBo;
import com.klp.ems.domain.bo.EmsEnergyConsumptionQueryBo;
import com.klp.common.core.page.TableDataInfo;
import com.klp.common.core.domain.PageQuery;
import com.klp.ems.domain.vo.EnergyChainRatioVo;
import com.klp.ems.domain.vo.SummaryDailyVo;
import com.klp.ems.domain.vo.SummaryMonthlyVo;
import com.klp.ems.domain.vo.YearOnYearVo;
import com.klp.ems.domain.vo.ChainAnalysisVo;
import java.util.Collection;
import java.util.List;
@@ -49,9 +52,25 @@ public interface IEmsEnergyConsumptionService {
*/
Boolean deleteWithValidByIds(Collection<Long> ids, Boolean isValid);
EnergyChainRatioVo getEnergyChainRatio();
List<SummaryDailyVo> getEnergySummaryDaily(String month);
List<SummaryMonthlyVo> getEnergySummaryMonthly(String year);
/**
* 过滤版:按能耗类型/设备/区域(含子区域)聚合
*/
EnergyChainRatioVo getEnergyChainRatioFiltered(EmsEnergyConsumptionQueryBo queryBo);
/**
* 近期能耗趋势过滤版month 按天、year 按月
*/
List<SummaryDailyVo> getEnergySummaryDailyFiltered(EmsEnergyConsumptionQueryBo queryBo);
List<SummaryMonthlyVo> getEnergySummaryMonthlyFiltered(EmsEnergyConsumptionQueryBo queryBo);
/** 同比分析:传入 year返回当年与上一年逐月汇总可过滤 */
YearOnYearVo getYearOnYear(EmsEnergyConsumptionQueryBo queryBo);
/** 环比分析periodType=day/week/month/year + dateKey可过滤 */
ChainAnalysisVo getChainAnalysis(EmsEnergyConsumptionQueryBo queryBo);
}

View File

@@ -6,24 +6,30 @@ import com.klp.common.core.domain.PageQuery;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.klp.ems.domain.EmsLocation;
import com.klp.ems.domain.bo.EmsEnergyConsumptionQueryBo;
import com.klp.ems.domain.bo.MeterFilterBo;
import com.klp.ems.domain.bo.TimeRangeWithMetersBo;
import com.klp.ems.domain.vo.EnergyChainRatioVo;
import com.klp.ems.domain.vo.SummaryDailyVo;
import com.klp.ems.domain.vo.SummaryMonthlyVo;
import com.klp.ems.domain.vo.YearOnYearVo;
import com.klp.ems.domain.vo.ChainAnalysisVo;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import com.klp.ems.domain.bo.EmsEnergyConsumptionBo;
import com.klp.ems.domain.vo.EmsEnergyConsumptionVo;
import com.klp.ems.domain.EmsEnergyConsumption;
import com.klp.ems.mapper.EmsEnergyConsumptionMapper;
import com.klp.ems.mapper.EmsMeterMapper;
import com.klp.ems.mapper.EmsLocationMapper;
import com.klp.ems.service.IEmsEnergyConsumptionService;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.List;
import java.util.Map;
import java.util.Collection;
import java.time.temporal.WeekFields;
import java.util.*;
/**
* 能耗记录Service业务层处理
@@ -36,6 +42,8 @@ import java.util.Collection;
public class EmsEnergyConsumptionServiceImpl implements IEmsEnergyConsumptionService {
private final EmsEnergyConsumptionMapper baseMapper;
private final EmsMeterMapper meterMapper;
private final EmsLocationMapper locationMapper;
/**
* 查询能耗记录
@@ -119,16 +127,127 @@ public class EmsEnergyConsumptionServiceImpl implements IEmsEnergyConsumptionSer
return baseMapper.deleteBatchIds(ids) > 0;
}
private Double nz(Double v) {
return v == null ? 0D : v;
}
private Double calcRate(Double current, Double base) {
if (base == null || base == 0D) {
return null;
}
return (current - base) / base * 100D;
}
@Override
public EnergyChainRatioVo getEnergyChainRatio() {
EnergyChainRatioVo energyChainRatioVo = new EnergyChainRatioVo();
public EnergyChainRatioVo getEnergyChainRatioFiltered(EmsEnergyConsumptionQueryBo queryBo) {
// 解析区域(含子区域)-> 设备ID集合
// 优先 meterId其次 energyTypeId/locationIds
List<Long> meterIds;
if (queryBo.getMeterId() != null) {
meterIds = Collections.singletonList(queryBo.getMeterId());
} else {
List<Long> locationIds = null;
if (queryBo.getLocationId() != null) {
locationIds = collectSubLocationIds(queryBo.getLocationId());
}
MeterFilterBo filter = new MeterFilterBo();
filter.setEnergyTypeId(queryBo.getEnergyTypeId());
filter.setLocationIds(locationIds);
filter.setMeterId(null);
meterIds = meterMapper.selectMeterIds(filter);
}
if (meterIds == null || meterIds.isEmpty()) {
// 没有设备则全为0
EnergyChainRatioVo vo = new EnergyChainRatioVo();
vo.setTodayUsage(0D);
vo.setYesterdayUsage(0D);
vo.setThisMonthUsage(0D);
vo.setLastMonthUsage(0D);
vo.setCurrentYearUsage(0D);
vo.setLastYearUsage(0D);
vo.setDailyChainRate(null);
vo.setMonthlyChainRate(null);
vo.setYearOnYearRate(null);
return vo;
}
LocalDateTime now = LocalDateTime.now();
LocalDateTime startOfToday = now.toLocalDate().atStartOfDay();
LocalDateTime endOfNow = now;
LocalDateTime startOfYesterday = startOfToday.minusDays(1);
LocalDateTime endOfYesterdaySameTime = startOfYesterday.plusHours(now.getHour()).plusMinutes(now.getMinute()).plusSeconds(now.getSecond());
LocalDateTime startOfThisMonth = now.withDayOfMonth(1).toLocalDate().atStartOfDay();
LocalDateTime endOfThisMonthSameTime = startOfThisMonth.plusDays(now.getDayOfMonth() - 1)
.plusHours(now.getHour()).plusMinutes(now.getMinute()).plusSeconds(now.getSecond());
LocalDateTime startOfLastMonth = startOfThisMonth.minusMonths(1);
LocalDateTime endOfLastMonthSameTime = startOfLastMonth.plusDays(now.getDayOfMonth() - 1)
.plusHours(now.getHour()).plusMinutes(now.getMinute()).plusSeconds(now.getSecond());
LocalDateTime startOfThisYear = now.withDayOfYear(1).toLocalDate().atStartOfDay();
LocalDateTime endOfThisYearSameTime = startOfThisYear.plusDays(now.getDayOfYear() - 1)
.plusHours(now.getHour()).plusMinutes(now.getMinute()).plusSeconds(now.getSecond());
LocalDateTime startOfLastYear = startOfThisYear.minusYears(1);
LocalDateTime endOfLastYearSameTime = startOfLastYear.plusDays(now.getDayOfYear() - 1)
.plusHours(now.getHour()).plusMinutes(now.getMinute()).plusSeconds(now.getSecond());
DateTimeFormatter fmt = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
TimeRangeWithMetersBo range = new TimeRangeWithMetersBo();
range.setMeterIds(meterIds);
range.setStart(fmt.format(startOfToday));
range.setEnd(fmt.format(endOfNow));
Double today = baseMapper.sumConsumptionBetweenWithMeters(range);
range.setStart(fmt.format(startOfYesterday));
range.setEnd(fmt.format(endOfYesterdaySameTime));
Double yesterday = baseMapper.sumConsumptionBetweenWithMeters(range);
range.setStart(fmt.format(startOfThisMonth));
range.setEnd(fmt.format(endOfThisMonthSameTime));
Double thisMonth = baseMapper.sumConsumptionBetweenWithMeters(range);
range.setStart(fmt.format(startOfLastMonth));
range.setEnd(fmt.format(endOfLastMonthSameTime));
Double lastMonth = baseMapper.sumConsumptionBetweenWithMeters(range);
range.setStart(fmt.format(startOfThisYear));
range.setEnd(fmt.format(endOfThisYearSameTime));
Double currentYear = baseMapper.sumConsumptionBetweenWithMeters(range);
range.setStart(fmt.format(startOfLastYear));
range.setEnd(fmt.format(endOfLastYearSameTime));
Double lastYear = baseMapper.sumConsumptionBetweenWithMeters(range);
EnergyChainRatioVo vo = new EnergyChainRatioVo();
vo.setTodayUsage(nz(today));
vo.setYesterdayUsage(nz(yesterday));
vo.setThisMonthUsage(nz(thisMonth));
vo.setLastMonthUsage(nz(lastMonth));
vo.setCurrentYearUsage(nz(currentYear));
vo.setLastYearUsage(nz(lastYear));
vo.setDailyChainRate(calcRate(vo.getTodayUsage(), vo.getYesterdayUsage()));
vo.setMonthlyChainRate(calcRate(vo.getThisMonthUsage(), vo.getLastMonthUsage()));
vo.setYearOnYearRate(calcRate(vo.getCurrentYearUsage(), vo.getLastYearUsage()));
return vo;
}
return null;
private List<Long> collectSubLocationIds(Long rootId) {
// 简易递归:使用 MP 查询所有,再在内存递归(数据量大时建议写 SQL/CTE
List<EmsLocation> all = locationMapper.selectList(null);
Map<Long, List<Long>> tree = new HashMap<>();
for (EmsLocation loc : all) {
Long pid = loc.getParentId();
if (pid == null) pid = 0L;
tree.computeIfAbsent(pid, k -> new ArrayList<>()).add(loc.getLocationId());
}
List<Long> result = new ArrayList<>();
Deque<Long> stack = new ArrayDeque<>();
stack.push(rootId);
result.add(rootId);
while (!stack.isEmpty()) {
Long id = stack.pop();
List<Long> children = tree.get(id);
if (children != null) {
for (Long c : children) {
result.add(c);
stack.push(c);
}
}
}
return result;
}
@Override
@@ -144,4 +263,196 @@ public class EmsEnergyConsumptionServiceImpl implements IEmsEnergyConsumptionSer
public List<SummaryMonthlyVo> getEnergySummaryMonthly(String year) {
return baseMapper.selectMonthlySummary(year);
}
@Override
public List<SummaryDailyVo> getEnergySummaryDailyFiltered(EmsEnergyConsumptionQueryBo queryBo) {
String month = queryBo.getMonth();
LocalDate startDate = LocalDate.parse(month + "-01", DateTimeFormatter.ofPattern("yyyy-MM-dd"));
LocalDate endDate = startDate.withDayOfMonth(startDate.lengthOfMonth());
List<Long> meterIds = resolveMeters(queryBo.getEnergyTypeId(), queryBo.getMeterId(), queryBo.getLocationId());
TimeRangeWithMetersBo range = new TimeRangeWithMetersBo();
range.setStart(month + "-01");
range.setEnd(endDate.toString());
range.setMeterIds(meterIds);
return baseMapper.selectDailySummaryWithMeters(range);
}
@Override
public List<SummaryMonthlyVo> getEnergySummaryMonthlyFiltered(EmsEnergyConsumptionQueryBo queryBo) {
List<Long> meterIds = resolveMeters(queryBo.getEnergyTypeId(), queryBo.getMeterId(), queryBo.getLocationId());
TimeRangeWithMetersBo range = new TimeRangeWithMetersBo();
range.setStart(queryBo.getYear());
range.setEnd(null);
range.setMeterIds(meterIds);
return baseMapper.selectMonthlySummaryWithMeters(range);
}
@Override
public YearOnYearVo getYearOnYear(EmsEnergyConsumptionQueryBo queryBo) {
String year = queryBo.getYear();
List<Long> meterIds = resolveMeters(queryBo.getEnergyTypeId(), queryBo.getMeterId(), queryBo.getLocationId());
// 构造当年和上一年月份列表
List<Double> current = new ArrayList<>();
List<Double> previous = new ArrayList<>();
for (int m = 1; m <= 12; m++) {
String ym = String.format("%s-%02d", year, m);
// 汇总该月
LocalDate first = LocalDate.parse(ym + "-01", DateTimeFormatter.ofPattern("yyyy-MM-dd"));
LocalDate last = first.withDayOfMonth(first.lengthOfMonth());
TimeRangeWithMetersBo r1 = new TimeRangeWithMetersBo();
r1.setMeterIds(meterIds);
r1.setStart(first.toString() + " 00:00:00"); r1.setEnd(last.toString() + " 23:59:59");
Double sumThis = baseMapper.sumConsumptionBetweenWithMeters(r1);
current.add(nz(sumThis));
String lastYear = String.valueOf(Integer.parseInt(year) - 1);
String ymPrev = String.format("%s-%02d", lastYear, m);
LocalDate firstPrev = LocalDate.parse(ymPrev + "-01", DateTimeFormatter.ofPattern("yyyy-MM-dd"));
LocalDate lastPrev = firstPrev.withDayOfMonth(firstPrev.lengthOfMonth());
TimeRangeWithMetersBo r0 = new TimeRangeWithMetersBo();
r0.setMeterIds(meterIds);
r0.setStart(firstPrev.toString() + " 00:00:00"); r0.setEnd(lastPrev.toString() + " 23:59:59");
Double sumPrev = baseMapper.sumConsumptionBetweenWithMeters(r0);
previous.add(nz(sumPrev));
}
YearOnYearVo vo = new YearOnYearVo();
vo.setYear(year);
vo.setLastYear(String.valueOf(Integer.parseInt(year) - 1));
vo.setCurrentYearMonthly(current);
vo.setLastYearMonthly(previous);
return vo;
}
@Override
public ChainAnalysisVo getChainAnalysis(EmsEnergyConsumptionQueryBo queryBo) {
List<Long> meterIds = resolveMeters(queryBo.getEnergyTypeId(), queryBo.getMeterId(), queryBo.getLocationId());
ChainAnalysisVo vo = new ChainAnalysisVo();
vo.setPeriodType(queryBo.getPeriodType());
DateTimeFormatter dt = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
switch (queryBo.getPeriodType()) {
case "day": {
// dateKey = yyyy-MM-dd按小时
LocalDate day = LocalDate.parse(queryBo.getDateKey(), DateTimeFormatter.ofPattern("yyyy-MM-dd"));
LocalDate prevDay = day.minusDays(1);
List<String> labels = new ArrayList<>();
List<Double> curVals = new ArrayList<>();
List<Double> prevVals = new ArrayList<>();
for (int h = 0; h < 24; h++) {
labels.add(String.format("%02d:00", h));
String s1 = day + String.format(" %02d:00:00", h);
String e1 = day + String.format(" %02d:59:59", h);
String s0 = prevDay + String.format(" %02d:00:00", h);
String e0 = prevDay + String.format(" %02d:59:59", h);
TimeRangeWithMetersBo r = new TimeRangeWithMetersBo();
r.setMeterIds(meterIds);
r.setStart(s1); r.setEnd(e1);
curVals.add(nz(baseMapper.sumConsumptionBetweenWithMeters(r)));
r.setStart(s0); r.setEnd(e0);
prevVals.add(nz(baseMapper.sumConsumptionBetweenWithMeters(r)));
}
vo.setCurrentLabels(labels);
vo.setPreviousLabels(labels);
vo.setCurrentValues(curVals);
vo.setPreviousValues(prevVals);
break;
}
case "week": {
// dateKey = 年内周数1-53按天
int year = LocalDate.now().getYear();
int week = Integer.parseInt(queryBo.getDateKey());
WeekFields wf = WeekFields.ISO;
LocalDate firstDayOfWeek = LocalDate.of(year, 1, 4).with(wf.weekOfWeekBasedYear(), week).with(wf.dayOfWeek(), 1);
LocalDate prevWeekFirst = firstDayOfWeek.minusWeeks(1);
List<String> labels = new ArrayList<>();
List<Double> curVals = new ArrayList<>();
List<Double> prevVals = new ArrayList<>();
for (int i = 0; i < 7; i++) {
LocalDate d1 = firstDayOfWeek.plusDays(i);
LocalDate d0 = prevWeekFirst.plusDays(i);
labels.add(d1.getDayOfWeek().name());
TimeRangeWithMetersBo r = new TimeRangeWithMetersBo();
r.setMeterIds(meterIds);
r.setStart(d1.toString() + " 00:00:00"); r.setEnd(d1.toString() + " 23:59:59");
curVals.add(nz(baseMapper.sumConsumptionBetweenWithMeters(r)));
r.setStart(d0.toString() + " 00:00:00"); r.setEnd(d0.toString() + " 23:59:59");
prevVals.add(nz(baseMapper.sumConsumptionBetweenWithMeters(r)));
}
vo.setCurrentLabels(labels);
vo.setPreviousLabels(labels);
vo.setCurrentValues(curVals);
vo.setPreviousValues(prevVals);
break;
}
case "month": {
// dateKey = 月份1-12按天
int year = LocalDate.now().getYear();
int m = Integer.parseInt(queryBo.getDateKey());
LocalDate first = LocalDate.of(year, m, 1);
LocalDate prevFirst = first.minusMonths(1);
int days = first.lengthOfMonth();
List<String> labels = new ArrayList<>();
List<Double> curVals = new ArrayList<>();
List<Double> prevVals = new ArrayList<>();
for (int i = 1; i <= days; i++) {
LocalDate d1 = first.withDayOfMonth(i);
LocalDate d0 = prevFirst.withDayOfMonth(Math.min(i, prevFirst.lengthOfMonth()));
labels.add(String.valueOf(i));
TimeRangeWithMetersBo r = new TimeRangeWithMetersBo();
r.setMeterIds(meterIds);
r.setStart(d1.toString() + " 00:00:00"); r.setEnd(d1.toString() + " 23:59:59");
curVals.add(nz(baseMapper.sumConsumptionBetweenWithMeters(r)));
r.setStart(d0.toString() + " 00:00:00"); r.setEnd(d0.toString() + " 23:59:59");
prevVals.add(nz(baseMapper.sumConsumptionBetweenWithMeters(r)));
}
vo.setCurrentLabels(labels);
vo.setPreviousLabels(labels);
vo.setCurrentValues(curVals);
vo.setPreviousValues(prevVals);
break;
}
case "year": {
// dateKey = 年份yyyy按月
int year = Integer.parseInt(queryBo.getDateKey());
List<String> labels = new ArrayList<>();
List<Double> curVals = new ArrayList<>();
List<Double> prevVals = new ArrayList<>();
for (int m = 1; m <= 12; m++) {
LocalDate first = LocalDate.of(year, m, 1);
LocalDate last = first.withDayOfMonth(first.lengthOfMonth());
LocalDate firstPrev = first.minusYears(1);
LocalDate lastPrev = firstPrev.withDayOfMonth(firstPrev.lengthOfMonth());
labels.add(String.format("%02d", m));
TimeRangeWithMetersBo r = new TimeRangeWithMetersBo();
r.setMeterIds(meterIds);
r.setStart(first.toString() + " 00:00:00"); r.setEnd(last.toString() + " 23:59:59");
curVals.add(nz(baseMapper.sumConsumptionBetweenWithMeters(r)));
r.setStart(firstPrev.toString() + " 00:00:00"); r.setEnd(lastPrev.toString() + " 23:59:59");
prevVals.add(nz(baseMapper.sumConsumptionBetweenWithMeters(r)));
}
vo.setCurrentLabels(labels);
vo.setPreviousLabels(labels);
vo.setCurrentValues(curVals);
vo.setPreviousValues(prevVals);
break;
}
}
return vo;
}
private List<Long> resolveMeters(Long energyTypeId, Long meterId, Long locationId) {
// 优先 meterId
if (meterId != null) {
return Collections.singletonList(meterId);
}
List<Long> locationIds = null;
if (locationId != null) {
locationIds = collectSubLocationIds(locationId);
}
MeterFilterBo filter = new MeterFilterBo();
filter.setEnergyTypeId(energyTypeId);
filter.setLocationIds(locationIds);
filter.setMeterId(null);
return meterMapper.selectMeterIds(filter);
}
}