|
@@ -7,6 +7,9 @@ import com.usky.common.security.utils.SecurityUtils;
|
|
|
import com.usky.demo.RemoteTsdbProxyService;
|
|
import com.usky.demo.RemoteTsdbProxyService;
|
|
|
import com.usky.demo.domain.EnergyItemSumQueryVO;
|
|
import com.usky.demo.domain.EnergyItemSumQueryVO;
|
|
|
import com.usky.demo.domain.EnergyItemSumResultVO;
|
|
import com.usky.demo.domain.EnergyItemSumResultVO;
|
|
|
|
|
+import com.usky.demo.domain.EnergyItemTrendPointVO;
|
|
|
|
|
+import com.usky.demo.domain.EnergyItemTrendQueryVO;
|
|
|
|
|
+import com.usky.demo.domain.EnergyItemTrendResultVO;
|
|
|
import com.usky.ems.domain.*;
|
|
import com.usky.ems.domain.*;
|
|
|
import com.usky.ems.enums.EnergyTypeEnum;
|
|
import com.usky.ems.enums.EnergyTypeEnum;
|
|
|
import com.usky.ems.mapper.DmpProductMapper;
|
|
import com.usky.ems.mapper.DmpProductMapper;
|
|
@@ -33,6 +36,8 @@ import java.time.LocalDate;
|
|
|
import java.time.LocalDateTime;
|
|
import java.time.LocalDateTime;
|
|
|
import java.time.LocalTime;
|
|
import java.time.LocalTime;
|
|
|
import java.time.format.DateTimeFormatter;
|
|
import java.time.format.DateTimeFormatter;
|
|
|
|
|
+import java.time.format.DateTimeFormatterBuilder;
|
|
|
|
|
+import java.time.temporal.ChronoField;
|
|
|
import java.time.temporal.TemporalAdjusters;
|
|
import java.time.temporal.TemporalAdjusters;
|
|
|
import java.util.ArrayList;
|
|
import java.util.ArrayList;
|
|
|
import java.util.Collections;
|
|
import java.util.Collections;
|
|
@@ -50,6 +55,13 @@ public class EmsOverviewServiceImpl implements EmsOverviewService {
|
|
|
|
|
|
|
|
private static final DateTimeFormatter TSDB_TIME_FORMAT =
|
|
private static final DateTimeFormatter TSDB_TIME_FORMAT =
|
|
|
DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss.SSS");
|
|
DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss.SSS");
|
|
|
|
|
+ /** 兼容 TDengine/JDBC 返回的可变小数秒,如 2026-06-03 00:00:00.0 */
|
|
|
|
|
+ private static final DateTimeFormatter TSDB_FLEXIBLE_TIME_FORMAT = new DateTimeFormatterBuilder()
|
|
|
|
|
+ .appendPattern("yyyy-MM-dd HH:mm:ss")
|
|
|
|
|
+ .optionalStart()
|
|
|
|
|
+ .appendFraction(ChronoField.NANO_OF_SECOND, 0, 9, true)
|
|
|
|
|
+ .optionalEnd()
|
|
|
|
|
+ .toFormatter();
|
|
|
private static final BigDecimal FALLBACK_CO2_FACTOR = new BigDecimal("2.4589");
|
|
private static final BigDecimal FALLBACK_CO2_FACTOR = new BigDecimal("2.4589");
|
|
|
private static final BigDecimal FALLBACK_COAL_FACTOR = new BigDecimal("0.70");
|
|
private static final BigDecimal FALLBACK_COAL_FACTOR = new BigDecimal("0.70");
|
|
|
private static final String FACTOR_NAME_COAL = "coal";
|
|
private static final String FACTOR_NAME_COAL = "coal";
|
|
@@ -406,78 +418,269 @@ public class EmsOverviewServiceImpl implements EmsOverviewService {
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
/**
|
|
|
- * 能耗用能趋势(模拟数据版本)
|
|
|
|
|
- * 参考原系统 queryEnergyTrend 逻辑,仅保留时间维度与同比的结构,数据采用固定规则模拟。
|
|
|
|
|
|
|
+ * 能耗用能趋势:按产品+时间范围查询 TSDB 得 time/value,再按原系统 getListResult 逻辑封装。
|
|
|
*/
|
|
*/
|
|
|
@Override
|
|
@Override
|
|
|
- public List<Map<String, Object>> queryEnergyTrend(Integer dateType, String itemCode, Long spaceId) {
|
|
|
|
|
- List<Map<String, Object>> list = new ArrayList<>();
|
|
|
|
|
-
|
|
|
|
|
- if (dateType == null || itemCode == null || itemCode.trim().isEmpty()) {
|
|
|
|
|
- return list;
|
|
|
|
|
|
|
+ public List<Map<String, Object>> queryEnergyTrend(Integer dateType, String identifier, Integer energyType) {
|
|
|
|
|
+ if (dateType == null || StringUtils.isBlank(identifier) || energyType == null) {
|
|
|
|
|
+ return new ArrayList<>();
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- // 当前期与去年同期的时间范围
|
|
|
|
|
- LocalDateTime endThisTime;
|
|
|
|
|
- LocalDateTime startThisTime;
|
|
|
|
|
- LocalDateTime endOldTime;
|
|
|
|
|
- LocalDateTime startOldTime;
|
|
|
|
|
|
|
+ List<String> productCodeList = dmpProductMapper.selectProductCodesByEnergyType(
|
|
|
|
|
+ SecurityUtils.getTenantId(), energyType);
|
|
|
|
|
+ if (productCodeList == null || productCodeList.isEmpty()) {
|
|
|
|
|
+ return new ArrayList<>();
|
|
|
|
|
+ }
|
|
|
|
|
|
|
|
- if (dateType == 1) {
|
|
|
|
|
- // 日:按小时
|
|
|
|
|
- endThisTime = LocalDateTime.of(LocalDate.now(), LocalTime.MAX);
|
|
|
|
|
- } else if (dateType == 2) {
|
|
|
|
|
- // 月:按天
|
|
|
|
|
- endThisTime = LocalDateTime.of(LocalDate.now().with(TemporalAdjusters.lastDayOfMonth()), LocalTime.MAX);
|
|
|
|
|
- } else if (dateType == 3) {
|
|
|
|
|
- // 年:按月
|
|
|
|
|
- endThisTime = LocalDateTime.of(LocalDate.now().with(TemporalAdjusters.lastDayOfYear()), LocalTime.MAX);
|
|
|
|
|
- } else {
|
|
|
|
|
- return list;
|
|
|
|
|
|
|
+ if (dateType == 1 || dateType == 2 || dateType == 3) {
|
|
|
|
|
+ return buildEnergyTrendResult(productCodeList, identifier, dateType);
|
|
|
}
|
|
}
|
|
|
|
|
+ return new ArrayList<>();
|
|
|
|
|
+ }
|
|
|
|
|
|
|
|
- startThisTime = getStartTime(dateType, endThisTime);
|
|
|
|
|
- endOldTime = endThisTime.minusYears(1);
|
|
|
|
|
- startOldTime = getStartTime(dateType, endOldTime);
|
|
|
|
|
|
|
+ /**
|
|
|
|
|
+ * 与原系统 queryEnergyTrend 一致:拉取当前期/去年同期 time-value 序列,构建 dateList 后 getListResult 封装。
|
|
|
|
|
+ */
|
|
|
|
|
+ private List<Map<String, Object>> buildEnergyTrendResult(List<String> productCodeList, String identifier,
|
|
|
|
|
+ Integer dateType) {
|
|
|
|
|
+ LocalDateTime endThisTime = getTrendEndTime(dateType);
|
|
|
|
|
+ LocalDateTime startThisTime = getStartTime(dateType, endThisTime);
|
|
|
|
|
+ LocalDateTime endOldTime = endThisTime.minusYears(1);
|
|
|
|
|
+ LocalDateTime startOldTime = getStartTime(dateType, endOldTime);
|
|
|
|
|
|
|
|
- // 按时间步长循环生成当前期与去年同期的模拟趋势数据
|
|
|
|
|
- LocalDateTime curTime = startThisTime;
|
|
|
|
|
- LocalDateTime oldTime = startOldTime;
|
|
|
|
|
- int index = 0;
|
|
|
|
|
|
|
+ String fieldIdentifier = identifier.toLowerCase();
|
|
|
|
|
+
|
|
|
|
|
+ List<TrendDataPoint> pointVOList = queryTrendDataPoints(productCodeList, fieldIdentifier,
|
|
|
|
|
+ startThisTime, endThisTime, dateType);
|
|
|
|
|
+ Map<Integer, TrendDataPoint> pointVOMap = toTrendPointMap(pointVOList, dateType);
|
|
|
|
|
+
|
|
|
|
|
+ List<TrendDataPoint> pointOldList = queryTrendDataPoints(productCodeList, fieldIdentifier,
|
|
|
|
|
+ startOldTime, endOldTime, dateType);
|
|
|
|
|
+ Map<Integer, TrendDataPoint> pointOldMap = toTrendPointMap(pointOldList, dateType);
|
|
|
|
|
+
|
|
|
|
|
+ List<String> dateList = buildTrendDateList(dateType, startThisTime, endThisTime);
|
|
|
|
|
+
|
|
|
|
|
+ return getTrendListResult(dateList, pointVOMap, pointOldMap, dateType);
|
|
|
|
|
+ }
|
|
|
|
|
|
|
|
- while (!curTime.isAfter(endThisTime)) {
|
|
|
|
|
- Map<String, Object> row = new HashMap<>();
|
|
|
|
|
- // 时间标签:当前期时间
|
|
|
|
|
- row.put("time", curTime.toString());
|
|
|
|
|
- // 当前期用能:示例 100 + index * 5
|
|
|
|
|
- BigDecimal currentValue = BigDecimal.valueOf(100 + index * 5L);
|
|
|
|
|
- // 去年同期用能:示例 80 + index * 5
|
|
|
|
|
- BigDecimal lastYearValue = BigDecimal.valueOf(80 + index * 5L);
|
|
|
|
|
- row.put("currentValue", currentValue);
|
|
|
|
|
- row.put("lastYearValue", lastYearValue);
|
|
|
|
|
- // 去年同期时间标签
|
|
|
|
|
- row.put("lastYearTime", oldTime.toString());
|
|
|
|
|
-
|
|
|
|
|
- list.add(row);
|
|
|
|
|
-
|
|
|
|
|
- // 根据时间类型推进时间
|
|
|
|
|
|
|
+ /**
|
|
|
|
|
+ * 按产品列表查询 TSDB,返回合并后的 time/value 序列(同时间点跨产品累加)。
|
|
|
|
|
+ */
|
|
|
|
|
+ private List<TrendDataPoint> queryTrendDataPoints(List<String> productCodeList, String fieldIdentifier,
|
|
|
|
|
+ LocalDateTime startTime, LocalDateTime endTime,
|
|
|
|
|
+ Integer dateType) {
|
|
|
|
|
+ Map<String, BigDecimal> merged = new HashMap<>();
|
|
|
|
|
+ String start = formatTsdbTime(startTime);
|
|
|
|
|
+ String end = formatTsdbTime(endTime);
|
|
|
|
|
+ for (String productCode : productCodeList) {
|
|
|
|
|
+ if (StringUtils.isBlank(productCode)) {
|
|
|
|
|
+ continue;
|
|
|
|
|
+ }
|
|
|
|
|
+ EnergyItemTrendQueryVO queryVO = new EnergyItemTrendQueryVO();
|
|
|
|
|
+ queryVO.setIdentifier(fieldIdentifier);
|
|
|
|
|
+ queryVO.setSuperTable("super_" + productCode.trim());
|
|
|
|
|
+ queryVO.setStartTime(start);
|
|
|
|
|
+ queryVO.setEndTime(end);
|
|
|
|
|
+ queryVO.setDateType(dateType);
|
|
|
|
|
+ EnergyItemTrendResultVO apiResult = remoteTsdbProxyService.sumEnergyItemTrend(queryVO);
|
|
|
|
|
+ if (apiResult == null || apiResult.getPoints() == null) {
|
|
|
|
|
+ continue;
|
|
|
|
|
+ }
|
|
|
|
|
+ for (EnergyItemTrendPointVO point : apiResult.getPoints()) {
|
|
|
|
|
+ if (point == null || StringUtils.isBlank(point.getTime())) {
|
|
|
|
|
+ continue;
|
|
|
|
|
+ }
|
|
|
|
|
+ BigDecimal value = point.getValue() != null ? point.getValue() : BigDecimal.ZERO;
|
|
|
|
|
+ merged.merge(point.getTime(), value, BigDecimal::add);
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ List<TrendDataPoint> result = new ArrayList<>();
|
|
|
|
|
+ for (Map.Entry<String, BigDecimal> entry : merged.entrySet()) {
|
|
|
|
|
+ TrendDataPoint dataPoint = new TrendDataPoint();
|
|
|
|
|
+ dataPoint.setTime(entry.getKey());
|
|
|
|
|
+ dataPoint.setValue(entry.getValue());
|
|
|
|
|
+ result.add(dataPoint);
|
|
|
|
|
+ }
|
|
|
|
|
+ return result;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ /** 将 time/value 列表转为以时间刻度为 key 的 Map(与原系统 pointVOMap 一致) */
|
|
|
|
|
+ private Map<Integer, TrendDataPoint> toTrendPointMap(List<TrendDataPoint> points, Integer dateType) {
|
|
|
|
|
+ Map<Integer, TrendDataPoint> map = new HashMap<>();
|
|
|
|
|
+ for (TrendDataPoint point : points) {
|
|
|
|
|
+ LocalDateTime parsed = parseTrendTimestamp(point.getTime());
|
|
|
|
|
+ if (parsed == null) {
|
|
|
|
|
+ continue;
|
|
|
|
|
+ }
|
|
|
|
|
+ int timeKey = getTrendTimeKey(dateType, parsed);
|
|
|
|
|
+ map.merge(timeKey, point, (existing, incoming) -> {
|
|
|
|
|
+ TrendDataPoint merged = new TrendDataPoint();
|
|
|
|
|
+ merged.setTime(existing.getTime());
|
|
|
|
|
+ merged.setValue(existing.getValue().add(incoming.getValue()));
|
|
|
|
|
+ return merged;
|
|
|
|
|
+ });
|
|
|
|
|
+ }
|
|
|
|
|
+ return map;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ /** 构建时间轴 dateList(与原系统一致) */
|
|
|
|
|
+ private List<String> buildTrendDateList(Integer dateType, LocalDateTime startTime, LocalDateTime endTime) {
|
|
|
|
|
+ List<String> dateList = new ArrayList<>();
|
|
|
|
|
+ LocalDateTime time = startTime;
|
|
|
|
|
+ while (true) {
|
|
|
if (dateType == 1) {
|
|
if (dateType == 1) {
|
|
|
- curTime = curTime.plusHours(1);
|
|
|
|
|
- oldTime = oldTime.plusHours(1);
|
|
|
|
|
|
|
+ if (time.getHour() > endTime.getHour()) {
|
|
|
|
|
+ break;
|
|
|
|
|
+ }
|
|
|
} else if (dateType == 2) {
|
|
} else if (dateType == 2) {
|
|
|
- curTime = curTime.plusDays(1);
|
|
|
|
|
- oldTime = oldTime.plusDays(1);
|
|
|
|
|
|
|
+ if (time.getDayOfMonth() > endTime.getDayOfMonth()) {
|
|
|
|
|
+ break;
|
|
|
|
|
+ }
|
|
|
|
|
+ } else if (dateType == 3) {
|
|
|
|
|
+ if (time.getMonthValue() > endTime.getMonthValue()) {
|
|
|
|
|
+ break;
|
|
|
|
|
+ }
|
|
|
} else {
|
|
} else {
|
|
|
- curTime = curTime.plusMonths(1);
|
|
|
|
|
- oldTime = oldTime.plusMonths(1);
|
|
|
|
|
|
|
+ break;
|
|
|
|
|
+ }
|
|
|
|
|
+ dateList.add(formatTsdbTime(time));
|
|
|
|
|
+ if (dateType == 1) {
|
|
|
|
|
+ time = time.plusHours(1);
|
|
|
|
|
+ } else if (dateType == 2) {
|
|
|
|
|
+ time = time.plusDays(1);
|
|
|
|
|
+ } else {
|
|
|
|
|
+ time = time.plusMonths(1);
|
|
|
|
|
+ }
|
|
|
|
|
+ if (time.isAfter(endTime)) {
|
|
|
|
|
+ break;
|
|
|
}
|
|
}
|
|
|
-
|
|
|
|
|
- index++;
|
|
|
|
|
}
|
|
}
|
|
|
|
|
+ return dateList;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ /**
|
|
|
|
|
+ * 与原系统 getListResult 一致:遍历 dateList,匹配 thisValue/oldValue/time/unit。
|
|
|
|
|
+ */
|
|
|
|
|
+ private List<Map<String, Object>> getTrendListResult(List<String> dateList,
|
|
|
|
|
+ Map<Integer, TrendDataPoint> pointVOMap,
|
|
|
|
|
+ Map<Integer, TrendDataPoint> pointOldMap,
|
|
|
|
|
+ Integer dateType) {
|
|
|
|
|
+ List<Map<String, Object>> list = new ArrayList<>();
|
|
|
|
|
|
|
|
|
|
+ for (String date : dateList) {
|
|
|
|
|
+ Map<String, Object> map = new HashMap<>();
|
|
|
|
|
+ int time = parseTrendTimeKey(dateType, date);
|
|
|
|
|
+
|
|
|
|
|
+ TrendDataPoint pointVO = pointVOMap.get(time);
|
|
|
|
|
+ if (pointVO == null) {
|
|
|
|
|
+ map.put("thisValue", "-");
|
|
|
|
|
+ } else {
|
|
|
|
|
+ map.put("thisValue", pointVO.getValue());
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ TrendDataPoint pointOldVO = pointOldMap.get(time);
|
|
|
|
|
+ if (pointOldVO == null) {
|
|
|
|
|
+ map.put("oldValue", "-");
|
|
|
|
|
+ } else {
|
|
|
|
|
+ map.put("oldValue", pointOldVO.getValue());
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ map.put("time", time);
|
|
|
|
|
+ list.add(map);
|
|
|
|
|
+ }
|
|
|
return list;
|
|
return list;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
+ private EmsEnergyItemCode resolveEnergyItemCode(String identifier) {
|
|
|
|
|
+ EmsEnergyItemCode itemCode = emsEnergyItemCodeMapper.selectOne(
|
|
|
|
|
+ Wrappers.lambdaQuery(EmsEnergyItemCode.class).eq(EmsEnergyItemCode::getCode, identifier));
|
|
|
|
|
+ if (itemCode == null) {
|
|
|
|
|
+ itemCode = emsEnergyItemCodeMapper.selectOne(
|
|
|
|
|
+ Wrappers.lambdaQuery(EmsEnergyItemCode.class).eq(EmsEnergyItemCode::getIdentifier, identifier));
|
|
|
|
|
+ }
|
|
|
|
|
+ return itemCode;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ /** 趋势时序点(对应原系统 DataPointVO 的 time/value) */
|
|
|
|
|
+ private static class TrendDataPoint {
|
|
|
|
|
+ private String time;
|
|
|
|
|
+ private BigDecimal value;
|
|
|
|
|
+
|
|
|
|
|
+ public String getTime() {
|
|
|
|
|
+ return time;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ public void setTime(String time) {
|
|
|
|
|
+ this.time = time;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ public BigDecimal getValue() {
|
|
|
|
|
+ return value;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ public void setValue(BigDecimal value) {
|
|
|
|
|
+ this.value = value;
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ private int parseTrendTimeKey(Integer dateType, String date) {
|
|
|
|
|
+ LocalDateTime time = parseTrendTimestamp(date);
|
|
|
|
|
+ return time != null ? getTrendTimeKey(dateType, time) : 0;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ private LocalDateTime parseTrendTimestamp(String ts) {
|
|
|
|
|
+ if (StringUtils.isBlank(ts)) {
|
|
|
|
|
+ return null;
|
|
|
|
|
+ }
|
|
|
|
|
+ String normalized = ts.trim();
|
|
|
|
|
+ DateTimeFormatter[] formatters = {
|
|
|
|
|
+ TSDB_FLEXIBLE_TIME_FORMAT,
|
|
|
|
|
+ TSDB_TIME_FORMAT,
|
|
|
|
|
+ DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"),
|
|
|
|
|
+ DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ss")
|
|
|
|
|
+ };
|
|
|
|
|
+ for (DateTimeFormatter formatter : formatters) {
|
|
|
|
|
+ try {
|
|
|
|
|
+ return LocalDateTime.parse(normalized, formatter);
|
|
|
|
|
+ } catch (Exception ignored) {
|
|
|
|
|
+ // try next formatter
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ int dotIndex = normalized.indexOf('.');
|
|
|
|
|
+ if (dotIndex > 0) {
|
|
|
|
|
+ try {
|
|
|
|
|
+ return LocalDateTime.parse(normalized.substring(0, dotIndex),
|
|
|
|
|
+ DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"));
|
|
|
|
|
+ } catch (Exception ignored) {
|
|
|
|
|
+ // fall through
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ return null;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ private LocalDateTime getTrendEndTime(Integer dateType) {
|
|
|
|
|
+ if (dateType == 1) {
|
|
|
|
|
+ return LocalDateTime.of(LocalDate.now(), LocalTime.MAX);
|
|
|
|
|
+ }
|
|
|
|
|
+ if (dateType == 2) {
|
|
|
|
|
+ return LocalDateTime.of(LocalDate.now().with(TemporalAdjusters.lastDayOfMonth()), LocalTime.MAX);
|
|
|
|
|
+ }
|
|
|
|
|
+ if (dateType == 3) {
|
|
|
|
|
+ return LocalDateTime.of(LocalDate.now().with(TemporalAdjusters.lastDayOfYear()), LocalTime.MAX);
|
|
|
|
|
+ }
|
|
|
|
|
+ return null;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ private int getTrendTimeKey(Integer dateType, LocalDateTime time) {
|
|
|
|
|
+ if (dateType == 1) {
|
|
|
|
|
+ return time.getHour();
|
|
|
|
|
+ }
|
|
|
|
|
+ if (dateType == 2) {
|
|
|
|
|
+ return time.getDayOfMonth();
|
|
|
|
|
+ }
|
|
|
|
|
+ return time.getMonthValue();
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
/**
|
|
/**
|
|
|
* 计算指定时间类型的开始时间
|
|
* 计算指定时间类型的开始时间
|
|
|
* dateType: 1-日(当天 00:00:00)、2-月(当月第一天 00:00:00)、3-年(当年第一天 00:00:00)
|
|
* dateType: 1-日(当天 00:00:00)、2-月(当月第一天 00:00:00)、3-年(当年第一天 00:00:00)
|