diff --git a/ems-system/src/main/java/com/xzzn/ems/domain/vo/DevicePointDataList.java b/ems-system/src/main/java/com/xzzn/ems/domain/vo/DevicePointDataList.java index ac2f356..3d9d2f0 100644 --- a/ems-system/src/main/java/com/xzzn/ems/domain/vo/DevicePointDataList.java +++ b/ems-system/src/main/java/com/xzzn/ems/domain/vo/DevicePointDataList.java @@ -40,6 +40,12 @@ public class DevicePointDataList this.minDate = minDate; } + public DevicePointDataList(String deviceId, String parentDeviceId, List pointValueList) { + this.deviceId = deviceId; + this.parentDeviceId = parentDeviceId; + this.pointValueList = pointValueList; + } + public DevicePointDataList() { } diff --git a/ems-system/src/main/java/com/xzzn/ems/domain/vo/GeneralQueryDataVo.java b/ems-system/src/main/java/com/xzzn/ems/domain/vo/GeneralQueryDataVo.java index 81d67ff..0484094 100644 --- a/ems-system/src/main/java/com/xzzn/ems/domain/vo/GeneralQueryDataVo.java +++ b/ems-system/src/main/java/com/xzzn/ems/domain/vo/GeneralQueryDataVo.java @@ -14,6 +14,33 @@ public class GeneralQueryDataVo { private Object pointValue; private String parentDeviceId; + //箱线图数据 + /** 最小值(Minimum) */ + private BigDecimal min; + /** 第一四分位数(Q1) */ + private BigDecimal q1; + /** 中位数(Median/Q2) */ + private BigDecimal median; + /** 第三四分位数(Q3) */ + private BigDecimal q3; + /** 最大值(Maximum) */ + private BigDecimal max; + + public GeneralQueryDataVo() { + } + + public GeneralQueryDataVo(String siteId, String deviceId, String valueDate, String parentDeviceId, BigDecimal min, BigDecimal q1, BigDecimal median, BigDecimal q3, BigDecimal max) { + this.siteId = siteId; + this.deviceId = deviceId; + this.valueDate = valueDate; + this.parentDeviceId = parentDeviceId; + this.min = min; + this.q1 = q1; + this.median = median; + this.q3 = q3; + this.max = max; + } + public String getSiteId() { return siteId; } @@ -53,4 +80,44 @@ public class GeneralQueryDataVo { public void setParentDeviceId(String parentDeviceId) { this.parentDeviceId = parentDeviceId; } + + public BigDecimal getMin() { + return min; + } + + public void setMin(BigDecimal min) { + this.min = min; + } + + public BigDecimal getQ1() { + return q1; + } + + public void setQ1(BigDecimal q1) { + this.q1 = q1; + } + + public BigDecimal getMedian() { + return median; + } + + public void setMedian(BigDecimal median) { + this.median = median; + } + + public BigDecimal getQ3() { + return q3; + } + + public void setQ3(BigDecimal q3) { + this.q3 = q3; + } + + public BigDecimal getMax() { + return max; + } + + public void setMax(BigDecimal max) { + this.max = max; + } } diff --git a/ems-system/src/main/java/com/xzzn/ems/mapper/EmsPointMatchMapper.java b/ems-system/src/main/java/com/xzzn/ems/mapper/EmsPointMatchMapper.java index b799df3..c604e11 100644 --- a/ems-system/src/main/java/com/xzzn/ems/mapper/EmsPointMatchMapper.java +++ b/ems-system/src/main/java/com/xzzn/ems/mapper/EmsPointMatchMapper.java @@ -101,6 +101,15 @@ public interface EmsPointMatchMapper @Param("params") Map> params, @Param("clusterDeviceId")String clusterDeviceId); + // 根据条件查询箱线图数据-按天-单体电池特殊处理 + public List getBatteryPointDataForBoxPlot(@Param("siteIds")List siteIds, + @Param("tableName")String tableName, + @Param("tableField")String tableField, + @Param("startDate")Date startDate, + @Param("endDate")Date endDate, + @Param("params") Map> params, + @Param("clusterDeviceId")String clusterDeviceId); + // 根据条件查询数据-按分钟-其他设备 public List getCommonPointDataByMinutes(@Param("siteIds")List siteIds, @Param("tableName")String tableName, @@ -122,6 +131,13 @@ public interface EmsPointMatchMapper @Param("startDate")Date startDate, @Param("endDate")Date endDate, @Param("deviceId")String deviceId); + // 根据条件查询箱线图数据-按天-其他设备 + public List getCommonPointDataForBoxPlot(@Param("siteIds")List siteIds, + @Param("tableName")String tableName, + @Param("tableField")String tableField, + @Param("startDate")Date startDate, + @Param("endDate")Date endDate, + @Param("deviceId")String deviceId); // 单个站点单个设备点位查询-除了电池簇其他设备使用 public List getSingleSiteDevicePoints(@Param("siteId")String siteId, diff --git a/ems-system/src/main/java/com/xzzn/ems/service/impl/GeneralQueryServiceImpl.java b/ems-system/src/main/java/com/xzzn/ems/service/impl/GeneralQueryServiceImpl.java index 6d99ba0..93c89da 100644 --- a/ems-system/src/main/java/com/xzzn/ems/service/impl/GeneralQueryServiceImpl.java +++ b/ems-system/src/main/java/com/xzzn/ems/service/impl/GeneralQueryServiceImpl.java @@ -1,5 +1,6 @@ package com.xzzn.ems.service.impl; +import com.sun.org.glassfish.external.statistics.Stats; import com.xzzn.common.enums.DeviceCategory; import com.xzzn.common.utils.DateUtils; import com.xzzn.ems.domain.EmsPointMatch; @@ -20,6 +21,8 @@ import java.time.temporal.TemporalAdjusters; import java.util.*; import java.util.stream.Collectors; +import javax.lang.model.util.ElementScanner6; + /** * 综合查询 服务层实现 * @@ -142,11 +145,18 @@ public class GeneralQueryServiceImpl implements IGeneralQueryService dataVoList = emsPointMatchMapper.getCommonPointDataByHours(querySiteIds,tableName,tableField,startDate,endDate,deviceId); } else if (dataUnit == 3) { // 天:yyyy-MM-dd 00:00:00 endDate = DateUtils.adjustToEndOfDay(request.getEndDate()); - dataVoList = emsPointMatchMapper.getCommonPointDataByDays(querySiteIds,tableName,tableField,startDate,endDate,deviceId); + dataVoList = emsPointMatchMapper.getCommonPointDataForBoxPlot(querySiteIds,tableName,tableField,startDate,endDate,deviceId); + } + + if (dataUnit == 3) { + // 箱线图数据特殊处理 + result = dealCommonWithBoxPlotData(dataVoList, dataType); + } else { + // 曲线图数据特殊处理 + // 数据转换+计算曲线数据最大最小平均和差值 + result = convertCommonToResultList(dataVoList, dataType); } - // 数据转换+计算曲线数据最大最小平均和差值 - result = convertCommonToResultList(dataVoList, dataType); return result; } @@ -175,13 +185,113 @@ public class GeneralQueryServiceImpl implements IGeneralQueryService } else if (dataUnit == 3) { // 天 endDate = DateUtils.adjustToEndOfDay(request.getEndDate()); tableName = "ems_battery_data_day"; - dataVoList = emsPointMatchMapper.getBatteryPointDataByDays(querySiteIds,tableName,tableField,startDate,endDate,siteDeviceMap,clusterDeviceId); + dataVoList = emsPointMatchMapper.getBatteryPointDataForBoxPlot(querySiteIds,tableName,tableField,startDate,endDate,siteDeviceMap,clusterDeviceId); } // 数据转换 - result = convertBatteryToResultList(dataVoList,dataType); + if (dataUnit == 3) { + // 箱线图数据特殊处理 + result = dealBatteryWithBoxPlotData(dataVoList, dataType); + } else { + // 曲线图数据特殊处理 + // 数据转换+计算曲线数据最大最小平均和差值 + result = convertBatteryToResultList(dataVoList, dataType); + } return result; } + private List dealCommonWithBoxPlotData(List dataVoList, Long dataType) { + // 数据转换: 先按siteId分组,再按deviceId分组 + return dataVoList.stream() + // 第一层分组:按siteId分组,得到 + .collect(Collectors.groupingBy(GeneralQueryDataVo::getSiteId)) + .entrySet().stream() + .map(siteEntry -> { + String siteId = siteEntry.getKey(); + List siteAllData = siteEntry.getValue(); + + // 第二层分组:在当前站点下,按deviceId分组,得到 + List deviceList = siteAllData.stream() + .collect(Collectors.groupingBy(GeneralQueryDataVo::getDeviceId)) + .entrySet().stream() + .map(deviceEntry -> { + String deviceId = deviceEntry.getKey(); + List deviceDataList = deviceEntry.getValue(); + + // 第三层分组:在当前设备下,按时间分组,得到 <时间, 该时间段数据> + List dataList = deviceDataList.stream().sorted(Comparator.comparing(GeneralQueryDataVo::getValueDate)) + .collect(Collectors.groupingBy( + GeneralQueryDataVo::getValueDate, + TreeMap::new, + Collectors.toList())) + .entrySet().stream().map(timeEntry -> { + String valueDate = timeEntry.getKey(); + // 计算当前设备数据的统计值(最小值,第一四分位数,中位数,第三四分位数, 最大值) + List values = timeEntry.getValue().stream().map(data -> new BigDecimal(String.valueOf(data.getPointValue()))).collect(Collectors.toList()); + BoxPlotData data = calculateBoxPlotData(values); + return new GeneralQueryDataVo(siteId, deviceId, valueDate, null, data.min, data.q1, data.median, data.q3, data.max); + }).collect(Collectors.toList()); + + return new DevicePointDataList(deviceId, null, dataList); + }) + .collect(Collectors.toList()); + + return new GeneralQueryResponse(siteId, deviceList, dataType); + }) + .collect(Collectors.toList()); + } + + private List dealBatteryWithBoxPlotData(List dataVoList, Long dataType) { + // 先按siteId分组 + return dataVoList.stream() + .collect(Collectors.groupingBy(GeneralQueryDataVo::getSiteId)) + .entrySet().stream() + .map(siteEntry -> { + String siteId = siteEntry.getKey(); + List siteData = siteEntry.getValue(); + + // 2. 按(deviceId + parentDeviceId)组合分组,生成deviceList + List deviceList = siteData.stream() + // 分组键:deviceId + parentDeviceId(确保唯一设备) + .collect(Collectors.groupingBy(data -> + data.getDeviceId() + "_" + data.getParentDeviceId() + )) + .entrySet().stream() + .map(deviceEntry -> { + // 解析分组键,获取deviceId和parentDeviceId + String[] keyParts = deviceEntry.getKey().split("_"); + String deviceId = keyParts[0]; + String parentDeviceId = keyParts[1]; + + // 3. 第三层分组:在当前设备下,按时间分组,得到 <时间, 该时间段数据> + List deviceDataList = deviceEntry.getValue().stream() + .collect(Collectors.groupingBy( + GeneralQueryDataVo::getValueDate, + TreeMap::new, + Collectors.toList())) + .entrySet().stream().map(timeEntry -> { + String valueDate = timeEntry.getKey(); + + // 计算当前设备数据的统计值(最小值,第一四分位数,中位数,第三四分位数, 最大值) + List values = timeEntry.getValue().stream().map(data -> new BigDecimal(String.valueOf(data.getPointValue()))).collect(Collectors.toList()); + BoxPlotData data = calculateBoxPlotData(values); + return new GeneralQueryDataVo(siteId, deviceId, valueDate, parentDeviceId, data.min, data.q1, data.median, data.q3, data.max); + }).collect(Collectors.toList()); + + // 4. 构建DeviceItem + return new DevicePointDataList(deviceId, parentDeviceId, deviceDataList); + })// 关键排序步骤:先按deviceId升序,再按parentDeviceId升序 + .sorted( + Comparator.comparing(DevicePointDataList::getDeviceId) // 第一排序键:deviceId + .thenComparing(DevicePointDataList::getParentDeviceId) // 第二排序键:parentDeviceId + ) + .collect(Collectors.toList()); + + // 5. 构建SiteData + return new GeneralQueryResponse(siteId, deviceList, dataType); + }) + .collect(Collectors.toList()); + } + private List convertBatteryToResultList(List dataVoList, Long dataType) { // 先按siteId分组 return dataVoList.stream() @@ -492,6 +602,49 @@ public class GeneralQueryServiceImpl implements IGeneralQueryService return minutes; } + public BoxPlotData calculateBoxPlotData(List values) { + if (values == null || values.isEmpty()) { + return null; + } + + // 排序 + Collections.sort(values); + + // 计算五个统计量 + BigDecimal min = values.get(0); + BigDecimal max = values.get(values.size() - 1); + BigDecimal median = calculateMedian(values); + BigDecimal q1 = calculateQuartile(values, new BigDecimal("0.25")); + BigDecimal q3 = calculateQuartile(values, new BigDecimal("0.75")); + + return new BoxPlotData(min, q1, median, q3, max); + } + + private BigDecimal calculateMedian(List values) { + int size = values.size(); + if (size % 2 == 0) { + return (values.get(size/2 - 1).add(values.get(size/2))).divide(new BigDecimal(2)); + } else { + return values.get(size/2); + } + } + + private BigDecimal calculateQuartile(List values, BigDecimal quartile) { + int size = values.size(); + BigDecimal pos = new BigDecimal(size - 1).multiply(quartile); + int lowerIndex = (int) Math.floor(pos.doubleValue()); + int upperIndex = (int) Math.ceil(pos.doubleValue()); + + if (lowerIndex == upperIndex) { + return values.get(lowerIndex); + } + + BigDecimal lowerValue = values.get(lowerIndex); + BigDecimal upperValue = values.get(upperIndex); +// return lowerValue + (upperValue - lowerValue) * (pos - lowerIndex); + return lowerValue.add(upperValue.subtract(lowerValue)).multiply((pos.subtract(new BigDecimal(lowerIndex)))); + } + /** * 内部辅助类:存储统计结果 */ @@ -527,4 +680,31 @@ public class GeneralQueryServiceImpl implements IGeneralQueryService } } + /** + * 内部辅助类:存储箱线图统计结果 + */ + private static class BoxPlotData { + /** 最小值(Minimum) */ + private final BigDecimal min; + + /** 第一四分位数(Q1) */ + private final BigDecimal q1; + + /** 中位数(Median/Q2) */ + private final BigDecimal median; + + /** 第三四分位数(Q3) */ + private final BigDecimal q3; + + /** 最大值(Maximum) */ + private final BigDecimal max; + + public BoxPlotData(BigDecimal min, BigDecimal q1, BigDecimal median, BigDecimal q3, BigDecimal max) { + this.min = min; + this.q1 = q1; + this.median = median; + this.q3 = q3; + this.max = max; + } + } } diff --git a/ems-system/src/main/resources/mapper/ems/EmsPointMatchMapper.xml b/ems-system/src/main/resources/mapper/ems/EmsPointMatchMapper.xml index 29f9581..3219fa7 100644 --- a/ems-system/src/main/resources/mapper/ems/EmsPointMatchMapper.xml +++ b/ems-system/src/main/resources/mapper/ems/EmsPointMatchMapper.xml @@ -273,6 +273,30 @@ ORDER BY t.site_id, t.device_id, t.cluster_device_id, valueDate ASC + + + +