|
|
@@ -1,6 +1,7 @@
|
|
|
package com.usky.ems.service.impl;
|
|
|
|
|
|
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
|
|
+import com.baomidou.mybatisplus.core.toolkit.Wrappers;
|
|
|
import com.usky.common.core.bean.ApiResult;
|
|
|
import com.usky.common.core.exception.BusinessException;
|
|
|
import com.usky.common.security.utils.SecurityUtils;
|
|
|
@@ -21,6 +22,7 @@ import javax.servlet.http.HttpServletResponse;
|
|
|
import java.io.IOException;
|
|
|
import java.io.OutputStream;
|
|
|
import java.math.BigDecimal;
|
|
|
+import java.math.RoundingMode;
|
|
|
import java.nio.charset.StandardCharsets;
|
|
|
import java.time.LocalDate;
|
|
|
import java.time.LocalDateTime;
|
|
|
@@ -54,6 +56,8 @@ public class EmsReportServiceImpl implements EmsReportService {
|
|
|
private EmsProductEnergyTypeMapper emsProductEnergyTypeMapper;
|
|
|
@Autowired
|
|
|
private BaseSpaceMapper baseSpaceMapper;
|
|
|
+ @Autowired
|
|
|
+ private EmsEnergyItemCodeMapper emsEnergyItemCodeMapper;
|
|
|
|
|
|
private String energyTypeName(Long energyTypeId) {
|
|
|
if (energyTypeId == null || energyTypeId < 1 || energyTypeId > 3) return "";
|
|
|
@@ -386,7 +390,7 @@ public class EmsReportServiceImpl implements EmsReportService {
|
|
|
} else {
|
|
|
// 生成空数据结构
|
|
|
timeData = generateEmptyTimeData(request.getTimeType(), startTime, endTime);
|
|
|
- total = "-";
|
|
|
+ total = "0";
|
|
|
}
|
|
|
|
|
|
EnergyReportResponse.ValueItem item = new EnergyReportResponse.ValueItem();
|
|
|
@@ -408,23 +412,6 @@ public class EmsReportServiceImpl implements EmsReportService {
|
|
|
return groupedValueList;
|
|
|
}
|
|
|
|
|
|
- /**
|
|
|
- * 查询设备能耗数据(模拟实现,实际应从TDengine或能耗表查询)
|
|
|
- */
|
|
|
- private Map<String, String> queryDeviceEnergyData(String deviceId, LocalDateTime startTime, LocalDateTime endTime, String timeType) {
|
|
|
- Map<String, String> data = new LinkedHashMap<>();
|
|
|
-
|
|
|
- // TODO: 实际应从数据库查询真实数据
|
|
|
- // 这里使用模拟数据
|
|
|
- List<String> timeLabels = generateTimeLabels(timeType, startTime, endTime);
|
|
|
- for (String label : timeLabels) {
|
|
|
- String key = "_" + label.replace("时", "").replace("日", "").replace("月", "");
|
|
|
- data.put(key, "-"); // 模拟无数据
|
|
|
- }
|
|
|
-
|
|
|
- return data;
|
|
|
- }
|
|
|
-
|
|
|
/**
|
|
|
* 生成空的时间数据结构(用于无数据时填充)
|
|
|
*/
|
|
|
@@ -434,25 +421,7 @@ public class EmsReportServiceImpl implements EmsReportService {
|
|
|
List<String> timeLabels = generateTimeLabels(timeType, startTime, endTime);
|
|
|
for (String label : timeLabels) {
|
|
|
String key = "_" + label.replace("时", "").replace("日", "").replace("月", "");
|
|
|
- data.put(key, "-"); // 空值填充
|
|
|
- }
|
|
|
-
|
|
|
- return data;
|
|
|
- }
|
|
|
-
|
|
|
- /**
|
|
|
- * 查询设备指定功能点的能耗数据(模拟实现,实际应从TDengine或能耗表查询)
|
|
|
- */
|
|
|
- private Map<String, String> queryDeviceEnergyDataWithFunc(String deviceId, String identifier,
|
|
|
- LocalDateTime startTime, LocalDateTime endTime, String timeType) {
|
|
|
- Map<String, String> data = new LinkedHashMap<>();
|
|
|
-
|
|
|
- // TODO: 实际应从数据库查询真实数据,并根据identifier区分不同功能点
|
|
|
- // 这里使用模拟数据
|
|
|
- List<String> timeLabels = generateTimeLabels(timeType, startTime, endTime);
|
|
|
- for (String label : timeLabels) {
|
|
|
- String key = "_" + label.replace("时", "").replace("日", "").replace("月", "");
|
|
|
- data.put(key, "-"); // 模拟无数据
|
|
|
+ data.put(key, "0"); // 空值填充
|
|
|
}
|
|
|
|
|
|
return data;
|
|
|
@@ -463,7 +432,7 @@ public class EmsReportServiceImpl implements EmsReportService {
|
|
|
*/
|
|
|
private String calculateTotal(Map<String, String> timeData) {
|
|
|
if (timeData == null || timeData.isEmpty()) {
|
|
|
- return "-";
|
|
|
+ return "0";
|
|
|
}
|
|
|
|
|
|
BigDecimal sum = BigDecimal.ZERO;
|
|
|
@@ -744,12 +713,7 @@ public class EmsReportServiceImpl implements EmsReportService {
|
|
|
if (timeData == null || timeData.isEmpty()) {
|
|
|
return 0;
|
|
|
}
|
|
|
-
|
|
|
- double sum = 0;
|
|
|
- for (Number value : timeData.values()) {
|
|
|
- sum += value.doubleValue();
|
|
|
- }
|
|
|
- return sum;
|
|
|
+ return sumUsageValues(timeData.values());
|
|
|
}
|
|
|
|
|
|
// ==================== 区域报表私有方法 ====================
|
|
|
@@ -765,10 +729,10 @@ public class EmsReportServiceImpl implements EmsReportService {
|
|
|
columns.add(createColumn("分项名称", "itemName", true));
|
|
|
columns.add(createColumn("合计", "total", true));
|
|
|
|
|
|
- // 动态时间列
|
|
|
+ // 动态时间列(prop 与前端约定:date/month 保留「时/日」后缀,year 去掉「月」)
|
|
|
List<String> timeLabels = generateTimeLabels(dateType, startTime, endTime);
|
|
|
for (String label : timeLabels) {
|
|
|
- columns.add(createColumn(label, "_" + label.replace("月", ""), false));
|
|
|
+ columns.add(createColumn(label, toSpaceReportColumnProp(label, dateType), false));
|
|
|
}
|
|
|
|
|
|
return columns;
|
|
|
@@ -781,16 +745,15 @@ public class EmsReportServiceImpl implements EmsReportService {
|
|
|
*/
|
|
|
private List<List<Map<String, Object>>> generateSpaceReportData(SpaceReportRequest request, LocalDateTime startTime, LocalDateTime endTime) {
|
|
|
List<List<Map<String, Object>>> groupedValueList = new ArrayList<>();
|
|
|
+ String timeType = request.getTimeType() != null ? request.getTimeType().toLowerCase() : "date";
|
|
|
+ List<String> timeLabels = generateTimeLabels(timeType, startTime, endTime);
|
|
|
|
|
|
for (SpaceReportRequest.SpaceItem space : request.getSpaces()) {
|
|
|
if (space == null || space.getId() == null) {
|
|
|
continue;
|
|
|
}
|
|
|
|
|
|
- // 查询区域分项能耗数据
|
|
|
- Map<String, Number> timeData = querySpaceItemEnergyData(request, startTime, endTime, request.getTimeType());
|
|
|
-
|
|
|
- // 为当前区域创建一个子列表
|
|
|
+ Map<String, Map<String, Number>> itemCodeTimeData = querySpaceItemEnergyData(space, request, startTime, endTime);
|
|
|
List<Map<String, Object>> spaceValueList = new ArrayList<>();
|
|
|
|
|
|
for (SpaceReportRequest.ItemCodeItem itemCode : request.getItemCodes()) {
|
|
|
@@ -798,26 +761,25 @@ public class EmsReportServiceImpl implements EmsReportService {
|
|
|
continue;
|
|
|
}
|
|
|
|
|
|
+ Map<String, Number> timeData = itemCodeTimeData.getOrDefault(
|
|
|
+ buildSpaceItemKey(itemCode),
|
|
|
+ createEmptySpaceTimeData(timeType, startTime, endTime));
|
|
|
+
|
|
|
Map<String, Object> row = new LinkedHashMap<>();
|
|
|
row.put("spaceName", space.getName());
|
|
|
row.put("itemName", itemCode.getName());
|
|
|
|
|
|
-
|
|
|
- // 计算合计
|
|
|
- Number total = calculateItemTotal(timeData);
|
|
|
- row.put("total", total);
|
|
|
-
|
|
|
- // 添加时间列数据
|
|
|
- int index = 1;
|
|
|
- for (Number value : timeData.values()) {
|
|
|
- row.put("_" + index, value);
|
|
|
- index++;
|
|
|
+ List<Number> periodValues = new ArrayList<>();
|
|
|
+ for (String label : timeLabels) {
|
|
|
+ Number value = timeData.getOrDefault(label, 0);
|
|
|
+ Number roundedValue = toUsageDecimal(value);
|
|
|
+ row.put(toSpaceReportValueKey(label), roundedValue.doubleValue());
|
|
|
+ periodValues.add(roundedValue);
|
|
|
}
|
|
|
-
|
|
|
+ row.put("total", sumUsageValues(periodValues));
|
|
|
spaceValueList.add(row);
|
|
|
}
|
|
|
|
|
|
- // 将当前区域的所有数据添加到外层列表
|
|
|
groupedValueList.add(spaceValueList);
|
|
|
}
|
|
|
|
|
|
@@ -825,233 +787,451 @@ public class EmsReportServiceImpl implements EmsReportService {
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
- * 查询区域分项能耗数据
|
|
|
+ * 查询区域分项能耗数据,按分项(code+name)-> 时间标签 -> 用量 返回
|
|
|
*/
|
|
|
- private Map<String, Number> querySpaceItemEnergyData(SpaceReportRequest request, LocalDateTime startTime, LocalDateTime endTime, String dateType) {
|
|
|
- Map<String, Number> data = new LinkedHashMap<>();
|
|
|
- List<Long> spaceIds = request.getSpaces().stream().map(SpaceReportRequest.SpaceItem::getId).collect(Collectors.toList());
|
|
|
- spaceIds = spaceIds(spaceIds);
|
|
|
- List<String> getGatewayUuids = getGatewayUuidsBySpaceIds(spaceIds).stream().map(BaseSpaceGateway::getGatewayUuid).collect(Collectors.toList());
|
|
|
- List<String> getDeviceUuids = getDeviceUuidsByGatewayIds(getGatewayUuids).stream().map(DmpDevice::getDeviceUuid).collect(Collectors.toList());
|
|
|
- Integer energyType = getEnergyTypeByDeviceUuid(getDeviceUuids.get(0));
|
|
|
+ private Map<String, Map<String, Number>> querySpaceItemEnergyData(SpaceReportRequest.SpaceItem space,
|
|
|
+ SpaceReportRequest request,
|
|
|
+ LocalDateTime startTime,
|
|
|
+ LocalDateTime endTime) {
|
|
|
+ String timeType = request.getTimeType() != null ? request.getTimeType().toLowerCase() : "date";
|
|
|
+ List<TimeSegmentBound> timeSegments = buildReportTimeSegments(timeType, startTime, endTime);
|
|
|
+
|
|
|
+ Map<String, Map<String, Number>> result = new LinkedHashMap<>();
|
|
|
+ List<SpaceReportRequest.ItemCodeItem> requestedItems = request.getItemCodes() == null
|
|
|
+ ? Collections.emptyList()
|
|
|
+ : request.getItemCodes().stream()
|
|
|
+ .filter(item -> item != null && StringUtils.hasText(item.getCode()))
|
|
|
+ .collect(Collectors.toList());
|
|
|
+
|
|
|
+ for (SpaceReportRequest.ItemCodeItem item : requestedItems) {
|
|
|
+ result.put(buildSpaceItemKey(item), createEmptySpaceTimeData(timeType, startTime, endTime));
|
|
|
+ }
|
|
|
+
|
|
|
+ if (space == null || space.getId() == null || requestedItems.isEmpty()) {
|
|
|
+ return result;
|
|
|
+ }
|
|
|
+
|
|
|
+ List<Long> spaceIds = spaceIds(Collections.singletonList(space.getId()));
|
|
|
+ List<String> gatewayUuids = getGatewayUuidsBySpaceIds(spaceIds).stream()
|
|
|
+ .map(BaseSpaceGateway::getGatewayUuid)
|
|
|
+ .collect(Collectors.toList());
|
|
|
+ List<String> spaceDeviceUuids = getDeviceUuidsByGatewayIds(gatewayUuids).stream()
|
|
|
+ .map(DmpDevice::getDeviceUuid)
|
|
|
+ .collect(Collectors.toList());
|
|
|
+
|
|
|
+ if (spaceDeviceUuids.isEmpty()) {
|
|
|
+ log.warn("区域下无设备,spaceId:{}", space.getId());
|
|
|
+ return result;
|
|
|
+ }
|
|
|
+
|
|
|
+ Integer energyType = getEnergyTypeByDeviceUuid(spaceDeviceUuids.get(0));
|
|
|
Long productId = getProductIdByEnergyType(energyType);
|
|
|
if (productId != 360L) {
|
|
|
throw new BusinessException("暂不支持该设备类型");
|
|
|
}
|
|
|
- // 按code将uuid分组
|
|
|
- Map<String, List<String>> itemCodeByDeviceUuid = getItemCodeByDeviceUuid(getDeviceUuids);
|
|
|
- // TODO:当前模拟数据都是电表设备,获取总有功功率
|
|
|
- List<String> metrics = new ArrayList<>();
|
|
|
- metrics.add("totalactiveenergyf");
|
|
|
+
|
|
|
+ Map<String, List<String>> itemCodeToDeviceUuids = getItemCodeByDeviceUuid(spaceDeviceUuids);
|
|
|
+
|
|
|
+ List<String> metrics = Collections.singletonList("totalactiveenergyf");
|
|
|
HistorysInnerRequestVO historyRequestVO = new HistorysInnerRequestVO();
|
|
|
- historyRequestVO.setDeviceuuid(getDeviceUuids);
|
|
|
+ historyRequestVO.setDeviceuuid(spaceDeviceUuids);
|
|
|
historyRequestVO.setStartTime(request.getStartTime());
|
|
|
historyRequestVO.setEndTime(request.getEndTime());
|
|
|
historyRequestVO.setMetrics(metrics);
|
|
|
log.info("历史数据接口请求:{}", historyRequestVO);
|
|
|
ApiResult<List<HistorysInnerResultVO>> listHistoryData = remoteTsdbProxyService.queryHistoryDeviceData(historyRequestVO);
|
|
|
- // 判断历史数据是否为空
|
|
|
- boolean hasData = !isHistoryDataEmpty(listHistoryData);
|
|
|
log.info("历史数据接口返回:{}", listHistoryData);
|
|
|
|
|
|
- // 如果有数据,解析TSDB返回的结果
|
|
|
- if (hasData) {
|
|
|
- // 1. 解析TSDB数据,构建设备UUID -> 指标 -> 时间点 -> 值的映射
|
|
|
- Map<String, Map<String, TreeMap<LocalDateTime, Double>>> deviceMetricTimeValueMap = new HashMap<>();
|
|
|
+ if (isHistoryDataEmpty(listHistoryData)) {
|
|
|
+ log.warn("历史数据为空,设备uuid:{}", spaceDeviceUuids);
|
|
|
+ return result;
|
|
|
+ }
|
|
|
|
|
|
- for (HistorysInnerResultVO result : listHistoryData.getData()) {
|
|
|
- String deviceUuid = result.getDeviceuuid();
|
|
|
- Map<String, TreeMap<LocalDateTime, Double>> metricTimeValueMap = deviceMetricTimeValueMap.computeIfAbsent(deviceUuid, k -> new HashMap<>());
|
|
|
+ Map<String, Map<String, TreeMap<LocalDateTime, Double>>> deviceMetricTimeValueMap =
|
|
|
+ parseDeviceMetricTimeValueMap(listHistoryData.getData());
|
|
|
|
|
|
- for (MetricVO metric : result.getMetrics()) {
|
|
|
- String metricName = metric.getMetric();
|
|
|
- TreeMap<LocalDateTime, Double> timeValueMap = metricTimeValueMap.computeIfAbsent(metricName, k -> new TreeMap<>());
|
|
|
+ for (SpaceReportRequest.ItemCodeItem itemCodeItem : requestedItems) {
|
|
|
+ List<String> itemDeviceUuids = resolveSpaceItemDeviceUuids(spaceDeviceUuids, itemCodeToDeviceUuids, itemCodeItem);
|
|
|
+ if (itemDeviceUuids.isEmpty()) {
|
|
|
+ log.warn("分项未匹配到区域设备,spaceId:{},itemCode:{},itemName:{}",
|
|
|
+ space.getId(), itemCodeItem.getCode(), itemCodeItem.getName());
|
|
|
+ continue;
|
|
|
+ }
|
|
|
|
|
|
- for (Map<String, Object> point : metric.getMetricItems()) {
|
|
|
- try {
|
|
|
- // 解析时间戳字符串为LocalDateTime
|
|
|
- String timestampStr = point.get("timestamp").toString();
|
|
|
- // 处理可能的时间格式,移除末尾的.0
|
|
|
- if (timestampStr.endsWith(".0")) {
|
|
|
- timestampStr = timestampStr.substring(0, timestampStr.length() - 2);
|
|
|
- }
|
|
|
- LocalDateTime timestamp = LocalDateTime.parse(timestampStr, DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"));
|
|
|
- Double value = Double.parseDouble(point.get("value").toString());
|
|
|
- timeValueMap.put(timestamp, value);
|
|
|
- } catch (Exception e) {
|
|
|
- log.error("解析数据失败,设备:{},指标:{},值:{}", deviceUuid, metricName, point.get("value"), e);
|
|
|
- }
|
|
|
- }
|
|
|
+ Map<String, Number> timeUsageMap = result.get(buildSpaceItemKey(itemCodeItem));
|
|
|
+ for (String deviceUuid : itemDeviceUuids) {
|
|
|
+ Map<String, TreeMap<LocalDateTime, Double>> metricTimeValueMap = deviceMetricTimeValueMap.get(deviceUuid);
|
|
|
+ if (metricTimeValueMap == null) {
|
|
|
+ continue;
|
|
|
+ }
|
|
|
+ TreeMap<LocalDateTime, Double> timeValueMap = metricTimeValueMap.get("totalactiveenergyf");
|
|
|
+ if (timeValueMap == null || timeValueMap.isEmpty()) {
|
|
|
+ continue;
|
|
|
+ }
|
|
|
+
|
|
|
+ for (TimeSegmentBound segment : timeSegments) {
|
|
|
+ double usage = calculateSegmentUsage(timeValueMap, segment.getStartInclusive(), segment.getEndExclusive());
|
|
|
+ timeUsageMap.put(segment.getLabel(), addUsage(timeUsageMap.get(segment.getLabel()), usage).doubleValue());
|
|
|
}
|
|
|
}
|
|
|
+ }
|
|
|
|
|
|
- // 2. 按itemCode分组计算用量
|
|
|
- Map<String, Map<String, Double>> itemCodeTimeUsageMap = new HashMap<>(); // itemCode -> 时间段 -> 用量
|
|
|
- Map<String, Double> itemCodeTotalUsageMap = new HashMap<>(); // itemCode -> 总用量
|
|
|
+ return result;
|
|
|
+ }
|
|
|
|
|
|
- // 初始化所有itemCode的总用量
|
|
|
- for (String itemCode : itemCodeByDeviceUuid.keySet()) {
|
|
|
- itemCodeTotalUsageMap.put(itemCode, 0.0);
|
|
|
- itemCodeTimeUsageMap.put(itemCode, new HashMap<>());
|
|
|
- }
|
|
|
+ private String buildSpaceItemKey(SpaceReportRequest.ItemCodeItem item) {
|
|
|
+ return item.getCode() + "::" + (item.getName() != null ? item.getName() : "");
|
|
|
+ }
|
|
|
|
|
|
- // 3. 生成时间标签分段
|
|
|
- List<String> timeLabels = generateTimeLabels(dateType, startTime, endTime);
|
|
|
-
|
|
|
- // 4. 按dateType生成时间分段
|
|
|
- Map<String, List<LocalDateTime>> timeSegments = new HashMap<>();
|
|
|
-
|
|
|
- if ("hour".equals(dateType)) {
|
|
|
- // 按小时分段
|
|
|
- LocalDateTime current = startTime.withMinute(0).withSecond(0).withNano(0);
|
|
|
- while (!current.isAfter(endTime)) {
|
|
|
- String hourLabel = current.getHour() + "时";
|
|
|
- LocalDateTime segmentStart = current;
|
|
|
- LocalDateTime segmentEnd = current.plusHours(1);
|
|
|
- timeSegments.put(hourLabel, Arrays.asList(segmentStart, segmentEnd));
|
|
|
- current = current.plusHours(1);
|
|
|
- }
|
|
|
- } else if ("day".equals(dateType)) {
|
|
|
- // 按天分段
|
|
|
- LocalDateTime current = startTime.with(LocalTime.MIN);
|
|
|
- while (!current.isAfter(endTime)) {
|
|
|
- String dateLabel = current.format(DateTimeFormatter.ofPattern("yyyy-MM-dd"));
|
|
|
- LocalDateTime segmentStart = current.with(LocalTime.MIN);
|
|
|
- LocalDateTime segmentEnd = current.with(LocalTime.MAX);
|
|
|
- timeSegments.put(dateLabel, Arrays.asList(segmentStart, segmentEnd));
|
|
|
- current = current.plusDays(1);
|
|
|
- }
|
|
|
+ /**
|
|
|
+ * 解析分项对应设备:优先按分项名称解析编码,再按 code 匹配,返回区域内全部关联设备
|
|
|
+ */
|
|
|
+ private List<String> resolveSpaceItemDeviceUuids(List<String> spaceDeviceUuids,
|
|
|
+ Map<String, List<String>> itemCodeToDeviceUuids,
|
|
|
+ SpaceReportRequest.ItemCodeItem itemCodeItem) {
|
|
|
+ Set<String> spaceDeviceSet = new HashSet<>(spaceDeviceUuids);
|
|
|
+ String requestCode = itemCodeItem.getCode().trim();
|
|
|
+
|
|
|
+ String resolvedCode = resolveEnergyItemDbCode(requestCode, itemCodeItem.getName());
|
|
|
+ if (StringUtils.hasText(resolvedCode)) {
|
|
|
+ List<String> matched = findDevicesByItemCode(resolvedCode, itemCodeToDeviceUuids, spaceDeviceSet);
|
|
|
+ if (!matched.isEmpty()) {
|
|
|
+ return matched;
|
|
|
}
|
|
|
+ }
|
|
|
|
|
|
- // 5. 计算每个itemCode在每个时间段的用量
|
|
|
- for (Map.Entry<String, List<String>> entry : itemCodeByDeviceUuid.entrySet()) {
|
|
|
- String itemCode = entry.getKey();
|
|
|
- List<String> deviceUuids = entry.getValue();
|
|
|
- Map<String, Double> timeUsageMap = itemCodeTimeUsageMap.get(itemCode);
|
|
|
-
|
|
|
- // 初始化时间段用量
|
|
|
- for (String timeLabel : timeSegments.keySet()) {
|
|
|
- timeUsageMap.putIfAbsent(timeLabel, 0.0);
|
|
|
- }
|
|
|
+ List<String> matched = findDevicesByItemCode(requestCode, itemCodeToDeviceUuids, spaceDeviceSet);
|
|
|
+ if (!matched.isEmpty()) {
|
|
|
+ return matched;
|
|
|
+ }
|
|
|
|
|
|
- // 遍历该itemCode下的所有设备
|
|
|
- for (String deviceUuid : deviceUuids) {
|
|
|
- if (!deviceMetricTimeValueMap.containsKey(deviceUuid)) {
|
|
|
- continue;
|
|
|
- }
|
|
|
+ return Collections.emptyList();
|
|
|
+ }
|
|
|
|
|
|
- Map<String, TreeMap<LocalDateTime, Double>> metricTimeValueMap = deviceMetricTimeValueMap.get(deviceUuid);
|
|
|
- if (!metricTimeValueMap.containsKey("totalactiveenergyf")) {
|
|
|
- continue;
|
|
|
- }
|
|
|
+ private List<String> findDevicesByItemCode(String itemCode,
|
|
|
+ Map<String, List<String>> itemCodeToDeviceUuids,
|
|
|
+ Set<String> spaceDeviceSet) {
|
|
|
+ List<String> matched = filterDevicesInSpace(itemCodeToDeviceUuids.get(itemCode), spaceDeviceSet);
|
|
|
+ if (!matched.isEmpty()) {
|
|
|
+ return matched;
|
|
|
+ }
|
|
|
+ return filterDevicesInSpace(
|
|
|
+ emsDeviceItemCodeMapper.selectDeviceUuidsByItemCode(SecurityUtils.getTenantId(), itemCode),
|
|
|
+ spaceDeviceSet);
|
|
|
+ }
|
|
|
|
|
|
- TreeMap<LocalDateTime, Double> timeValueMap = metricTimeValueMap.get("totalactiveenergyf");
|
|
|
- if (timeValueMap.isEmpty()) {
|
|
|
- continue;
|
|
|
- }
|
|
|
+ private List<String> filterDevicesInSpace(List<String> deviceUuids, Set<String> spaceDeviceSet) {
|
|
|
+ if (deviceUuids == null || deviceUuids.isEmpty()) {
|
|
|
+ return Collections.emptyList();
|
|
|
+ }
|
|
|
+ return deviceUuids.stream()
|
|
|
+ .filter(StringUtils::hasText)
|
|
|
+ .map(String::trim)
|
|
|
+ .filter(spaceDeviceSet::contains)
|
|
|
+ .distinct()
|
|
|
+ .collect(Collectors.toList());
|
|
|
+ }
|
|
|
|
|
|
- // 按时间段计算用量差值
|
|
|
- for (Map.Entry<String, List<LocalDateTime>> segmentEntry : timeSegments.entrySet()) {
|
|
|
- String timeLabel = segmentEntry.getKey();
|
|
|
- List<LocalDateTime> segment = segmentEntry.getValue();
|
|
|
- LocalDateTime segmentStart = segment.get(0);
|
|
|
- LocalDateTime segmentEnd = segment.get(1);
|
|
|
-
|
|
|
- // 获取时间段内的数据点(包含前后缓冲时间,确保能取到首尾数据)
|
|
|
- TreeMap<LocalDateTime, Double> segmentData = new TreeMap<>();
|
|
|
- for (Map.Entry<LocalDateTime, Double> entryData : timeValueMap.entrySet()) {
|
|
|
- LocalDateTime timestamp = entryData.getKey();
|
|
|
- // 考虑时间段前1小时和后1小时的数据,确保能取到边界数据
|
|
|
- if (!timestamp.isBefore(segmentStart.minusHours(1)) && !timestamp.isAfter(segmentEnd.plusHours(1))) {
|
|
|
- segmentData.put(timestamp, entryData.getValue());
|
|
|
- }
|
|
|
- }
|
|
|
+ /**
|
|
|
+ * 将请求中的 code/identifier/name 解析为 ems_energy_item_code 表中的 code
|
|
|
+ */
|
|
|
+ private String resolveEnergyItemDbCode(String requestCode, String itemName) {
|
|
|
+ if (StringUtils.hasText(itemName)) {
|
|
|
+ EmsEnergyItemCode byName = emsEnergyItemCodeMapper.selectOne(
|
|
|
+ Wrappers.lambdaQuery(EmsEnergyItemCode.class)
|
|
|
+ .eq(EmsEnergyItemCode::getName, itemName)
|
|
|
+ .last("LIMIT 1"));
|
|
|
+ if (byName != null) {
|
|
|
+ return byName.getCode();
|
|
|
+ }
|
|
|
+ }
|
|
|
|
|
|
- if (segmentData.size() < 2) {
|
|
|
- continue; // 数据点不足,无法计算差值
|
|
|
- }
|
|
|
+ if (StringUtils.hasText(requestCode)) {
|
|
|
+ EmsEnergyItemCode byCode = emsEnergyItemCodeMapper.selectOne(
|
|
|
+ Wrappers.lambdaQuery(EmsEnergyItemCode.class)
|
|
|
+ .eq(EmsEnergyItemCode::getCode, requestCode)
|
|
|
+ .last("LIMIT 1"));
|
|
|
+ if (byCode != null) {
|
|
|
+ return byCode.getCode();
|
|
|
+ }
|
|
|
|
|
|
- // 找到时间段内的第一个和最后一个有效数据点
|
|
|
- LocalDateTime firstTimestampInSegment = null;
|
|
|
- LocalDateTime lastTimestampInSegment = null;
|
|
|
+ EmsEnergyItemCode byIdentifier = emsEnergyItemCodeMapper.selectOne(
|
|
|
+ Wrappers.lambdaQuery(EmsEnergyItemCode.class)
|
|
|
+ .eq(EmsEnergyItemCode::getIdentifier, requestCode)
|
|
|
+ .last("LIMIT 1"));
|
|
|
+ if (byIdentifier != null) {
|
|
|
+ return byIdentifier.getCode();
|
|
|
+ }
|
|
|
+ }
|
|
|
+ return null;
|
|
|
+ }
|
|
|
|
|
|
- for (LocalDateTime timestamp : segmentData.keySet()) {
|
|
|
- if (!timestamp.isBefore(segmentStart)) {
|
|
|
- if (firstTimestampInSegment == null || timestamp.isBefore(firstTimestampInSegment)) {
|
|
|
- firstTimestampInSegment = timestamp;
|
|
|
- }
|
|
|
- }
|
|
|
- if (!timestamp.isAfter(segmentEnd)) {
|
|
|
- if (lastTimestampInSegment == null || timestamp.isAfter(lastTimestampInSegment)) {
|
|
|
- lastTimestampInSegment = timestamp;
|
|
|
- }
|
|
|
- }
|
|
|
- }
|
|
|
+ private TreeMap<LocalDateTime, Double> extractSegmentReadings(TreeMap<LocalDateTime, Double> source,
|
|
|
+ LocalDateTime startInclusive,
|
|
|
+ LocalDateTime endExclusive) {
|
|
|
+ TreeMap<LocalDateTime, Double> result = new TreeMap<>();
|
|
|
+ if (source == null || source.isEmpty() || !startInclusive.isBefore(endExclusive)) {
|
|
|
+ return result;
|
|
|
+ }
|
|
|
+ for (Map.Entry<LocalDateTime, Double> entry : source.entrySet()) {
|
|
|
+ LocalDateTime ts = entry.getKey();
|
|
|
+ if (!ts.isBefore(startInclusive) && ts.isBefore(endExclusive)) {
|
|
|
+ result.put(ts, entry.getValue());
|
|
|
+ }
|
|
|
+ }
|
|
|
+ return result;
|
|
|
+ }
|
|
|
|
|
|
- // 如果找不到有效的时间点,使用整个时间段的首尾数据点
|
|
|
- if (firstTimestampInSegment == null) {
|
|
|
- firstTimestampInSegment = segmentData.ceilingKey(segmentStart.minusHours(1));
|
|
|
- }
|
|
|
- if (lastTimestampInSegment == null) {
|
|
|
- lastTimestampInSegment = segmentData.floorKey(segmentEnd.plusHours(1));
|
|
|
- }
|
|
|
+ /**
|
|
|
+ * 解析 TSDB 历史数据为 设备 -> 指标 -> 时间 -> 值 结构
|
|
|
+ */
|
|
|
+ private Map<String, Map<String, TreeMap<LocalDateTime, Double>>> parseDeviceMetricTimeValueMap(
|
|
|
+ List<HistorysInnerResultVO> historyDataList) {
|
|
|
+ Map<String, Map<String, TreeMap<LocalDateTime, Double>>> deviceMetricTimeValueMap = new HashMap<>();
|
|
|
|
|
|
- if (firstTimestampInSegment == null || lastTimestampInSegment == null) {
|
|
|
- continue;
|
|
|
- }
|
|
|
+ if (historyDataList == null || historyDataList.isEmpty()) {
|
|
|
+ return deviceMetricTimeValueMap;
|
|
|
+ }
|
|
|
|
|
|
- Double firstValue = segmentData.get(firstTimestampInSegment);
|
|
|
- Double lastValue = segmentData.get(lastTimestampInSegment);
|
|
|
- Double usage = lastValue - firstValue;
|
|
|
+ for (HistorysInnerResultVO result : historyDataList) {
|
|
|
+ if (result == null || !StringUtils.hasText(result.getDeviceuuid())) {
|
|
|
+ continue;
|
|
|
+ }
|
|
|
+ String deviceUuid = result.getDeviceuuid();
|
|
|
+ Map<String, TreeMap<LocalDateTime, Double>> metricTimeValueMap =
|
|
|
+ deviceMetricTimeValueMap.computeIfAbsent(deviceUuid, k -> new HashMap<>());
|
|
|
|
|
|
- // 处理计数器重置的情况(用量为负数)
|
|
|
- if (usage < 0) {
|
|
|
- // 假设设备重置,取最后的值作为用量
|
|
|
- usage = lastValue;
|
|
|
+ if (result.getMetrics() == null) {
|
|
|
+ continue;
|
|
|
+ }
|
|
|
+ for (MetricVO metric : result.getMetrics()) {
|
|
|
+ if (metric == null || !StringUtils.hasText(metric.getMetric()) || metric.getMetricItems() == null) {
|
|
|
+ continue;
|
|
|
+ }
|
|
|
+ String metricName = metric.getMetric();
|
|
|
+ TreeMap<LocalDateTime, Double> timeValueMap =
|
|
|
+ metricTimeValueMap.computeIfAbsent(metricName, k -> new TreeMap<>());
|
|
|
+
|
|
|
+ for (Map<String, Object> point : metric.getMetricItems()) {
|
|
|
+ try {
|
|
|
+ String timestampStr = point.get("timestamp").toString();
|
|
|
+ if (timestampStr.endsWith(".0")) {
|
|
|
+ timestampStr = timestampStr.substring(0, timestampStr.length() - 2);
|
|
|
}
|
|
|
-
|
|
|
- // 累加到当前itemCode的用量
|
|
|
- timeUsageMap.put(timeLabel, timeUsageMap.get(timeLabel) + usage);
|
|
|
+ LocalDateTime timestamp = LocalDateTime.parse(timestampStr, DATE_TIME_FORMATTER);
|
|
|
+ Double value = Double.parseDouble(point.get("value").toString());
|
|
|
+ timeValueMap.put(timestamp, value);
|
|
|
+ } catch (Exception e) {
|
|
|
+ log.error("解析历史数据失败,设备:{},指标:{},值:{}",
|
|
|
+ deviceUuid, metricName, point.get("value"), e);
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
+ }
|
|
|
+ return deviceMetricTimeValueMap;
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 时间段用量 = 该时间段内最后一个读数 - 第一个读数(至少 2 条读数)
|
|
|
+ */
|
|
|
+ private double calculateSegmentUsage(TreeMap<LocalDateTime, Double> timeValueMap,
|
|
|
+ LocalDateTime startInclusive,
|
|
|
+ LocalDateTime endExclusive) {
|
|
|
+ if (timeValueMap == null || timeValueMap.isEmpty()) {
|
|
|
+ return 0;
|
|
|
+ }
|
|
|
+
|
|
|
+ TreeMap<LocalDateTime, Double> segmentData = extractSegmentReadings(timeValueMap, startInclusive, endExclusive);
|
|
|
+ if (segmentData.size() < 2) {
|
|
|
+ return 0;
|
|
|
+ }
|
|
|
+
|
|
|
+ double firstValue = segmentData.firstEntry().getValue();
|
|
|
+ double lastValue = segmentData.lastEntry().getValue();
|
|
|
+ return formatUsageValue(calculateReadingDelta(firstValue, lastValue));
|
|
|
+ }
|
|
|
+
|
|
|
+ private double calculateReadingDelta(double firstValue, double lastValue) {
|
|
|
+ double usage = lastValue - firstValue;
|
|
|
+ if (usage < 0) {
|
|
|
+ return lastValue;
|
|
|
+ }
|
|
|
+ return usage;
|
|
|
+ }
|
|
|
+
|
|
|
+ private double formatUsageValue(double usage) {
|
|
|
+ return toUsageDecimal(usage).doubleValue();
|
|
|
+ }
|
|
|
+
|
|
|
+ private static final int USAGE_SCALE = 2;
|
|
|
+
|
|
|
+ private BigDecimal toUsageDecimal(Number value) {
|
|
|
+ if (value == null) {
|
|
|
+ return BigDecimal.ZERO.setScale(USAGE_SCALE, RoundingMode.HALF_UP);
|
|
|
+ }
|
|
|
+ return BigDecimal.valueOf(value.doubleValue()).setScale(USAGE_SCALE, RoundingMode.HALF_UP);
|
|
|
+ }
|
|
|
+
|
|
|
+ private BigDecimal addUsage(Number a, Number b) {
|
|
|
+ return toUsageDecimal(a).add(toUsageDecimal(b)).setScale(USAGE_SCALE, RoundingMode.HALF_UP);
|
|
|
+ }
|
|
|
|
|
|
- // 6. 计算总量(所有时间段的用量之和)
|
|
|
- for (String itemCode : itemCodeByDeviceUuid.keySet()) {
|
|
|
- Map<String, Double> timeUsageMap = itemCodeTimeUsageMap.get(itemCode);
|
|
|
- Double totalUsage = timeUsageMap.values().stream().mapToDouble(Double::doubleValue).sum();
|
|
|
- itemCodeTotalUsageMap.put(itemCode, totalUsage);
|
|
|
+ private double sumUsageValues(Iterable<? extends Number> values) {
|
|
|
+ BigDecimal sum = BigDecimal.ZERO;
|
|
|
+ if (values != null) {
|
|
|
+ for (Number value : values) {
|
|
|
+ sum = sum.add(toUsageDecimal(value));
|
|
|
}
|
|
|
+ }
|
|
|
+ return sum.setScale(USAGE_SCALE, RoundingMode.HALF_UP).doubleValue();
|
|
|
+ }
|
|
|
|
|
|
- // 7. 将结果放入返回的data中
|
|
|
- // 这里假设我们返回第一个itemCode的数据,或者根据业务需求调整
|
|
|
- if (!itemCodeTimeUsageMap.isEmpty()) {
|
|
|
- String firstItemCode = itemCodeTimeUsageMap.keySet().iterator().next();
|
|
|
- Map<String, Double> timeUsageMap = itemCodeTimeUsageMap.get(firstItemCode);
|
|
|
+ private Map<String, Number> createEmptySpaceTimeData(String timeType, LocalDateTime startTime, LocalDateTime endTime) {
|
|
|
+ Map<String, Number> data = new LinkedHashMap<>();
|
|
|
+ for (String label : generateTimeLabels(timeType, startTime, endTime)) {
|
|
|
+ data.put(label, 0);
|
|
|
+ }
|
|
|
+ return data;
|
|
|
+ }
|
|
|
|
|
|
- // 按时间标签顺序放入结果
|
|
|
- for (String timeLabel : timeLabels) {
|
|
|
- if (timeUsageMap.containsKey(timeLabel)) {
|
|
|
- data.put(timeLabel, timeUsageMap.get(timeLabel));
|
|
|
- } else {
|
|
|
- data.put(timeLabel, 0);
|
|
|
- }
|
|
|
- }
|
|
|
+ /**
|
|
|
+ * 报表时间分段(左闭右开),与 columnList 标签一一对应
|
|
|
+ */
|
|
|
+ private static class TimeSegmentBound {
|
|
|
+ private final String label;
|
|
|
+ private final LocalDateTime startInclusive;
|
|
|
+ private final LocalDateTime endExclusive;
|
|
|
+
|
|
|
+ TimeSegmentBound(String label, LocalDateTime startInclusive, LocalDateTime endExclusive) {
|
|
|
+ this.label = label;
|
|
|
+ this.startInclusive = startInclusive;
|
|
|
+ this.endExclusive = endExclusive;
|
|
|
+ }
|
|
|
+
|
|
|
+ String getLabel() {
|
|
|
+ return label;
|
|
|
+ }
|
|
|
+
|
|
|
+ LocalDateTime getStartInclusive() {
|
|
|
+ return startInclusive;
|
|
|
+ }
|
|
|
+
|
|
|
+ LocalDateTime getEndExclusive() {
|
|
|
+ return endExclusive;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ private List<TimeSegmentBound> buildReportTimeSegments(String timeType, LocalDateTime startTime, LocalDateTime endTime) {
|
|
|
+ List<TimeSegmentBound> segments = new ArrayList<>();
|
|
|
+ if (startTime == null || endTime == null || !startTime.isBefore(endTime)) {
|
|
|
+ return segments;
|
|
|
+ }
|
|
|
+
|
|
|
+ String type = timeType != null ? timeType.toLowerCase() : "date";
|
|
|
+ switch (type) {
|
|
|
+ case "date":
|
|
|
+ buildHourSegments(segments, startTime, endTime);
|
|
|
+ break;
|
|
|
+ case "month":
|
|
|
+ buildDaySegments(segments, startTime, endTime);
|
|
|
+ break;
|
|
|
+ case "year":
|
|
|
+ buildMonthSegments(segments, startTime, endTime);
|
|
|
+ break;
|
|
|
+ case "custom":
|
|
|
+ break;
|
|
|
+ default:
|
|
|
+ buildHourSegments(segments, startTime, endTime);
|
|
|
+ break;
|
|
|
+ }
|
|
|
+ return segments;
|
|
|
+ }
|
|
|
|
|
|
- // 添加总量
|
|
|
- data.put("total", itemCodeTotalUsageMap.get(firstItemCode));
|
|
|
+ /** date:按小时分段 [HH:00, HH+1:00),与传参起止小时一致 */
|
|
|
+ private void buildHourSegments(List<TimeSegmentBound> segments, LocalDateTime startTime, LocalDateTime endTime) {
|
|
|
+ int startHour = startTime.getHour();
|
|
|
+ int endHour = endTime.getHour();
|
|
|
+ for (int hour = startHour; hour <= endHour; hour++) {
|
|
|
+ String label = hour + "时";
|
|
|
+ LocalDateTime hourStart = startTime.withHour(hour).withMinute(0).withSecond(0).withNano(0);
|
|
|
+ LocalDateTime hourEndExclusive = hourStart.plusHours(1);
|
|
|
+ LocalDateTime segmentStart = hourStart.isBefore(startTime) ? startTime : hourStart;
|
|
|
+ LocalDateTime segmentEndExclusive = hourEndExclusive.isAfter(endTime) ? endTime : hourEndExclusive;
|
|
|
+ if (segmentStart.isBefore(segmentEndExclusive)) {
|
|
|
+ segments.add(new TimeSegmentBound(label, segmentStart, segmentEndExclusive));
|
|
|
+ } else {
|
|
|
+ segments.add(new TimeSegmentBound(label, segmentStart, segmentStart.plusNanos(1)));
|
|
|
}
|
|
|
- } else {
|
|
|
- log.warn("历史数据为空,设备uuid:{}", getDeviceUuids);
|
|
|
- // 无数据时初始化所有时间标签为0
|
|
|
- List<String> timeLabels = generateTimeLabels(dateType, startTime, endTime);
|
|
|
- for (String label : timeLabels) {
|
|
|
- data.put(label, 0);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /** month:按自然日分段 [00:00, 次日00:00) */
|
|
|
+ private void buildDaySegments(List<TimeSegmentBound> segments, LocalDateTime startTime, LocalDateTime endTime) {
|
|
|
+ LocalDate current = startTime.toLocalDate();
|
|
|
+ LocalDate lastDate = resolveLastDateInRange(startTime, endTime);
|
|
|
+ while (!current.isAfter(lastDate)) {
|
|
|
+ String label = String.format("%02d日", current.getDayOfMonth());
|
|
|
+ LocalDateTime dayStart = current.atStartOfDay();
|
|
|
+ LocalDateTime dayEndExclusive = current.plusDays(1).atStartOfDay();
|
|
|
+ LocalDateTime segmentStart = dayStart.isBefore(startTime) ? startTime : dayStart;
|
|
|
+ LocalDateTime segmentEndExclusive = dayEndExclusive.isAfter(endTime) ? endTime : dayEndExclusive;
|
|
|
+ if (segmentStart.isBefore(segmentEndExclusive)) {
|
|
|
+ segments.add(new TimeSegmentBound(label, segmentStart, segmentEndExclusive));
|
|
|
}
|
|
|
- data.put("total", 0);
|
|
|
+ current = current.plusDays(1);
|
|
|
}
|
|
|
+ }
|
|
|
|
|
|
- return data;
|
|
|
+ /** year:按自然月分段 [月初00:00, 下月初00:00) */
|
|
|
+ private void buildMonthSegments(List<TimeSegmentBound> segments, LocalDateTime startTime, LocalDateTime endTime) {
|
|
|
+ int year = startTime.getYear();
|
|
|
+ int month = startTime.getMonthValue();
|
|
|
+ int endYear = endTime.getYear();
|
|
|
+ int endMonth = endTime.getMonthValue();
|
|
|
+
|
|
|
+ while (year < endYear || (year == endYear && month <= endMonth)) {
|
|
|
+ String label = month + "月";
|
|
|
+ LocalDateTime monthStart = LocalDateTime.of(year, month, 1, 0, 0, 0);
|
|
|
+ LocalDateTime monthEndExclusive = monthStart.plusMonths(1);
|
|
|
+ LocalDateTime segmentStart = monthStart.isBefore(startTime) ? startTime : monthStart;
|
|
|
+ LocalDateTime segmentEndExclusive = monthEndExclusive.isAfter(endTime) ? endTime : monthEndExclusive;
|
|
|
+ if (segmentStart.isBefore(segmentEndExclusive)) {
|
|
|
+ segments.add(new TimeSegmentBound(label, segmentStart, segmentEndExclusive));
|
|
|
+ }
|
|
|
+ month++;
|
|
|
+ if (month > 12) {
|
|
|
+ month = 1;
|
|
|
+ year++;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 结束时间为某日 00:00:00 时,表示不含该日,最后一天取前一日
|
|
|
+ */
|
|
|
+ private LocalDate resolveLastDateInRange(LocalDateTime startTime, LocalDateTime endTime) {
|
|
|
+ LocalDate endDate = endTime.toLocalDate();
|
|
|
+ if (endTime.toLocalTime().equals(LocalTime.MIDNIGHT) && endDate.isAfter(startTime.toLocalDate())) {
|
|
|
+ return endDate.minusDays(1);
|
|
|
+ }
|
|
|
+ return endDate;
|
|
|
+ }
|
|
|
+
|
|
|
+ private String toSpaceReportColumnProp(String label, String timeType) {
|
|
|
+ String type = timeType != null ? timeType.toLowerCase() : "date";
|
|
|
+ if ("year".equals(type)) {
|
|
|
+ return "_" + label.replace("月", "");
|
|
|
+ }
|
|
|
+ return "_" + label;
|
|
|
+ }
|
|
|
+
|
|
|
+ private String toSpaceReportValueKey(String label) {
|
|
|
+ String numPart = label.replace("时", "").replace("日", "").replace("月", "");
|
|
|
+ try {
|
|
|
+ return "_" + Integer.parseInt(numPart);
|
|
|
+ } catch (NumberFormatException e) {
|
|
|
+ return "_" + numPart;
|
|
|
+ }
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
@@ -1127,7 +1307,7 @@ public class EmsReportServiceImpl implements EmsReportService {
|
|
|
return 0;
|
|
|
}
|
|
|
|
|
|
- double sum = 0;
|
|
|
+ List<Number> rowTotals = new ArrayList<>();
|
|
|
for (List<Map<String, Object>> group : groupedValueList) {
|
|
|
if (group == null || group.isEmpty()) {
|
|
|
continue;
|
|
|
@@ -1135,11 +1315,11 @@ public class EmsReportServiceImpl implements EmsReportService {
|
|
|
for (Map<String, Object> row : group) {
|
|
|
Object total = row.get("total");
|
|
|
if (total instanceof Number) {
|
|
|
- sum += ((Number) total).doubleValue();
|
|
|
+ rowTotals.add((Number) total);
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
- return sum;
|
|
|
+ return sumUsageValues(rowTotals);
|
|
|
}
|
|
|
|
|
|
// ==================== 通用工具方法 ====================
|
|
|
@@ -1179,7 +1359,7 @@ public class EmsReportServiceImpl implements EmsReportService {
|
|
|
throw new BusinessException("按年查询时,起止时间必须是同一年");
|
|
|
}
|
|
|
break;
|
|
|
- case "customize":
|
|
|
+ case "custom":
|
|
|
// 自定义查询:不进行时间校验
|
|
|
break;
|
|
|
default:
|
|
|
@@ -1219,61 +1399,12 @@ public class EmsReportServiceImpl implements EmsReportService {
|
|
|
* - 按年(year)查询 → 返回月数据(1月-12月)
|
|
|
*/
|
|
|
private List<String> generateTimeLabels(String timeType, LocalDateTime startTime, LocalDateTime endTime) {
|
|
|
- List<String> labels = new ArrayList<>();
|
|
|
-
|
|
|
if (startTime == null || endTime == null) {
|
|
|
- return labels;
|
|
|
+ return new ArrayList<>();
|
|
|
}
|
|
|
-
|
|
|
- String type = timeType != null ? timeType.toLowerCase() : "date";
|
|
|
-
|
|
|
- switch (type) {
|
|
|
- case "date":
|
|
|
- // 按日查询:根据起止时间的小时来分段
|
|
|
- int startHour = startTime.getHour();
|
|
|
- int endHour = endTime.getHour();
|
|
|
- for (int i = startHour; i <= endHour; i++) {
|
|
|
- labels.add(i + "时");
|
|
|
- }
|
|
|
- break;
|
|
|
- case "month":
|
|
|
- // 按月查询:返回日数据 01日, 02日, ..., 31日
|
|
|
- LocalDate start = startTime.toLocalDate();
|
|
|
- LocalDate end = endTime.toLocalDate();
|
|
|
- int day = 1;
|
|
|
- while (!start.isAfter(end)) {
|
|
|
- labels.add(String.format("%02d日", day));
|
|
|
- start = start.plusDays(1);
|
|
|
- day++;
|
|
|
- }
|
|
|
- break;
|
|
|
- case "year":
|
|
|
- // 按年查询:返回月数据 1月, 2月, ..., 12月
|
|
|
- int startMonth = startTime.getMonthValue();
|
|
|
- int endMonth = endTime.getMonthValue();
|
|
|
- int year = startTime.getYear();
|
|
|
- int endYear = endTime.getYear();
|
|
|
-
|
|
|
- while (year < endYear || (year == endYear && startMonth <= endMonth)) {
|
|
|
- labels.add(startMonth + "月");
|
|
|
- startMonth++;
|
|
|
- if (startMonth > 12) {
|
|
|
- startMonth = 1;
|
|
|
- year++;
|
|
|
- }
|
|
|
- }
|
|
|
- break;
|
|
|
- case "custom":
|
|
|
- break;
|
|
|
- default:
|
|
|
- // 默认按日查询,返回小时数据
|
|
|
- for (int i = 0; i < 24; i++) {
|
|
|
- labels.add(i + "时");
|
|
|
- }
|
|
|
- break;
|
|
|
- }
|
|
|
-
|
|
|
- return labels;
|
|
|
+ return buildReportTimeSegments(timeType, startTime, endTime).stream()
|
|
|
+ .map(TimeSegmentBound::getLabel)
|
|
|
+ .collect(Collectors.toList());
|
|
|
}
|
|
|
|
|
|
/**
|