From 8546d1e67e7fe8b091e269f6f4b8104216c6f1dd Mon Sep 17 00:00:00 2001 From: liutao <790864623@qq.com> Date: Thu, 14 May 2026 15:31:29 +0800 Subject: [PATCH] =?UTF-8?q?=E8=83=BD=E6=BA=90=E6=A6=82=E8=A7=88=E6=8E=A5?= =?UTF-8?q?=E5=8F=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../energydevice/EnergyDeviceController.java | 9 +- .../energydevice/EnergyDeviceService.java | 3 +- .../energydevice/EnergyDeviceServiceImpl.java | 379 +++++++++++++++++- 3 files changed, 383 insertions(+), 8 deletions(-) diff --git a/yudao-module-mes/yudao-module-mes-biz/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/energydevice/EnergyDeviceController.java b/yudao-module-mes/yudao-module-mes-biz/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/energydevice/EnergyDeviceController.java index fdba623c3..bfa4beebd 100644 --- a/yudao-module-mes/yudao-module-mes-biz/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/energydevice/EnergyDeviceController.java +++ b/yudao-module-mes/yudao-module-mes-biz/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/energydevice/EnergyDeviceController.java @@ -104,6 +104,13 @@ public class EnergyDeviceController { return success(energyDeviceService.queryDataRecords(deviceConsumptionReqVO)); } + @GetMapping("/queryOverviewData") + @Operation(summary = "查询能源总览数据") + @PreAuthorize("@ss.hasPermission('mes:energy-device:queryRecords')") + public CommonResult queryOverviewData(@Valid EnergyOverviewReqVO reqVO) { + return success(energyDeviceService.queryOverviewData(reqVO)); + } + @GetMapping("/record-export-excel") @Operation(summary = "导出数据记录 Excel") @PreAuthorize("@ss.hasPermission('mes:energy-device:exportRecords')") @@ -186,4 +193,4 @@ public class EnergyDeviceController { } -} \ No newline at end of file +} diff --git a/yudao-module-mes/yudao-module-mes-biz/src/main/java/cn/iocoder/yudao/module/mes/service/energydevice/EnergyDeviceService.java b/yudao-module-mes/yudao-module-mes-biz/src/main/java/cn/iocoder/yudao/module/mes/service/energydevice/EnergyDeviceService.java index a9ffc2d07..706d48d98 100644 --- a/yudao-module-mes/yudao-module-mes-biz/src/main/java/cn/iocoder/yudao/module/mes/service/energydevice/EnergyDeviceService.java +++ b/yudao-module-mes/yudao-module-mes-biz/src/main/java/cn/iocoder/yudao/module/mes/service/energydevice/EnergyDeviceService.java @@ -108,8 +108,9 @@ public interface EnergyDeviceService { List queryDataRecords(EnergyDeviceConsumptionReqVO deviceConsumptionReqVO); + EnergyOverviewRespVO queryOverviewData(EnergyOverviewReqVO reqVO); List lastEnergyStatistics(Long deviceTypeId,Long orgId); List latestSevenDaysStatistics(Long deviceTypeId, Long orgId); -} \ No newline at end of file +} diff --git a/yudao-module-mes/yudao-module-mes-biz/src/main/java/cn/iocoder/yudao/module/mes/service/energydevice/EnergyDeviceServiceImpl.java b/yudao-module-mes/yudao-module-mes-biz/src/main/java/cn/iocoder/yudao/module/mes/service/energydevice/EnergyDeviceServiceImpl.java index ecdaa0042..34a763374 100644 --- a/yudao-module-mes/yudao-module-mes-biz/src/main/java/cn/iocoder/yudao/module/mes/service/energydevice/EnergyDeviceServiceImpl.java +++ b/yudao-module-mes/yudao-module-mes-biz/src/main/java/cn/iocoder/yudao/module/mes/service/energydevice/EnergyDeviceServiceImpl.java @@ -630,6 +630,39 @@ public class EnergyDeviceServiceImpl implements EnergyDeviceService { return result; } + @Override + public EnergyOverviewRespVO queryOverviewData(EnergyOverviewReqVO reqVO) { + EnergyOverviewRespVO respVO = new EnergyOverviewRespVO(); + + List devices = queryDevices(null, reqVO.getOrgId(), reqVO.getEnergyTypeId(), null); + if (devices.isEmpty()) { + respVO.setMetrics(buildOverviewMetrics(Collections.emptyList(), Collections.emptyList(), reqVO)); + respVO.setTrendChart(buildEmptyTrendChart(reqVO)); + respVO.setRegionChart(buildRegionChart(Collections.emptyList())); + respVO.setRankList(Collections.emptyList()); + respVO.setTotal(0L); + respVO.setDetailList(Collections.emptyList()); + return respVO; + } + + EnergyDeviceConsumptionReqVO consumptionReqVO = new EnergyDeviceConsumptionReqVO(); + consumptionReqVO.setOrgId(reqVO.getOrgId()); + consumptionReqVO.setStartTime(reqVO.getStartTime()); + consumptionReqVO.setEndTime(reqVO.getEndTime()); + consumptionReqVO.setIds(devices.stream().map(item -> String.valueOf(item.getId())).collect(Collectors.joining(","))); + + List records = queryDataRecords(consumptionReqVO); + records.sort(Comparator.comparing((EnergyDeviceRespVO item) -> parseEnergyValue(item.getEnergyConsumption())).reversed()); + + respVO.setMetrics(buildOverviewMetrics(records, devices, reqVO)); + respVO.setTrendChart(buildTrendChart(devices, reqVO)); + respVO.setRegionChart(buildRegionChart(records)); + respVO.setRankList(buildRankList(records)); + respVO.setTotal((long) records.size()); + respVO.setDetailList(paginateDetails(records, devices, reqVO)); + return respVO; + } + private Map buildCacheFromRow( List devices, Map> rowMap, @@ -719,19 +752,353 @@ public class EnergyDeviceServiceImpl implements EnergyDeviceService { /** 查询设备列表 */ private List queryDevices(EnergyDeviceConsumptionReqVO req) { + return queryDevices(req.getName(), req.getOrgId(), null, req.getIds()); + } + + private List queryDevices(String name, Long orgId, Long deviceTypeId, String ids) { return energyDeviceMapper.selectList( Wrappers.lambdaQuery() - .in(StringUtils.isNotBlank(req.getIds()), + .in(StringUtils.isNotBlank(ids), EnergyDeviceDO::getId, - StringUtils.isNotBlank(req.getIds()) - ? Arrays.asList(req.getIds().split(",")) + StringUtils.isNotBlank(ids) + ? Arrays.asList(ids.split(",")) : null) - .like(StringUtils.isNotBlank(req.getName()), EnergyDeviceDO::getName, req.getName()) - .eq(req.getOrgId() != null, EnergyDeviceDO::getOrgId, req.getOrgId()) + .like(StringUtils.isNotBlank(name), EnergyDeviceDO::getName, name) + .eq(orgId != null, EnergyDeviceDO::getOrgId, orgId) + .eq(deviceTypeId != null, EnergyDeviceDO::getDeviceTypeId, deviceTypeId) .orderByDesc(EnergyDeviceDO::getCreateTime) ); } + private List buildOverviewMetrics(List records, + List devices, + EnergyOverviewReqVO reqVO) { + double totalValue = records.stream().mapToDouble(item -> parseEnergyValue(item.getEnergyConsumption())).sum(); + EnergyDeviceRespVO topRecord = records.stream().findFirst().orElse(null); + + Map regionTotals = new LinkedHashMap<>(); + for (EnergyDeviceRespVO record : records) { + regionTotals.merge(StringUtils.defaultIfBlank(record.getOrgName(), "-"), + parseEnergyValue(record.getEnergyConsumption()), Double::sum); + } + Map.Entry maxRegion = regionTotals.entrySet().stream() + .max(Map.Entry.comparingByValue()) + .orElse(null); + + List metrics = new ArrayList<>(); + metrics.add(buildMetric("total", "总用量", formatDouble(totalValue), "", "设备数", String.valueOf(devices.size()), null, null)); + metrics.add(buildMetric("deviceCount", "统计设备", String.valueOf(records.size()), "台", "区域数", String.valueOf(regionTotals.size()), null, null)); + metrics.add(buildMetric("topDevice", "最高能耗设备", + topRecord != null ? topRecord.getName() : "-", + "", + "用量", + topRecord != null ? formatDouble(parseEnergyValue(topRecord.getEnergyConsumption())) : "0", + null, + null)); + metrics.add(buildMetric("topRegion", "最高能耗区域", + maxRegion != null ? maxRegion.getKey() : "-", + "", + "用量", + maxRegion != null ? formatDouble(maxRegion.getValue()) : "0", + null, + null)); + metrics.add(buildMetric("range", "统计时段", + buildRangeLabel(reqVO), + "", + "开始", + StringUtils.defaultIfBlank(reqVO.getStartTime(), "-"), + null, + null)); + return metrics; + } + + private EnergyOverviewRespVO.MetricItem buildMetric(String key, String label, String value, String unit, + String subLabel, String subValue, String change, Boolean down) { + EnergyOverviewRespVO.MetricItem item = new EnergyOverviewRespVO.MetricItem(); + item.setKey(key); + item.setLabel(label); + item.setValue(value); + item.setUnit(unit); + item.setSubLabel(subLabel); + item.setSubValue(subValue); + item.setChange(change); + item.setDown(down); + return item; + } + + private EnergyOverviewRespVO.TrendChart buildTrendChart(List devices, EnergyOverviewReqVO reqVO) { + EnergyOverviewRespVO.TrendChart chart = new EnergyOverviewRespVO.TrendChart(); + chart.setUnit(""); + + if (StringUtils.isBlank(reqVO.getStartTime()) || StringUtils.isBlank(reqVO.getEndTime())) { + chart.setXAxis(Collections.emptyList()); + chart.setData(Collections.emptyList()); + return chart; + } + + try { + LocalDateTime start = LocalDateTime.parse(reqVO.getStartTime(), DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")); + LocalDateTime end = LocalDateTime.parse(reqVO.getEndTime(), DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")); + Map> deviceRulesMap = buildDeviceRulesMap(devices); + Set sourceDeviceIds = collectRuleDeviceIds(deviceRulesMap); + Map pointInfoMap = batchGetPointInfoFromLocalDB(new ArrayList<>(collectPointIds(deviceRulesMap))); + + if (sourceDeviceIds.isEmpty()) { + chart.setXAxis(Collections.emptyList()); + chart.setData(Collections.emptyList()); + return chart; + } + + boolean sameDay = start.toLocalDate().equals(end.toLocalDate()) && !start.equals(end); + if (sameDay) { + return buildHourTrendChart(chart, deviceRulesMap, sourceDeviceIds, pointInfoMap, start, end); + } + return buildDayTrendChart(chart, deviceRulesMap, sourceDeviceIds, pointInfoMap, start.toLocalDate(), end.toLocalDate()); + } catch (Exception e) { + log.warn("构建能源总览趋势数据失败, req={}", reqVO, e); + chart.setXAxis(Collections.emptyList()); + chart.setData(Collections.emptyList()); + return chart; + } + } + + private EnergyOverviewRespVO.TrendChart buildHourTrendChart(EnergyOverviewRespVO.TrendChart chart, + Map> deviceRulesMap, + Set sourceDeviceIds, + Map pointInfoMap, + LocalDateTime start, + LocalDateTime end) { + DateTimeFormatter hourKeyFormatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH"); + DateTimeFormatter displayFormatter = DateTimeFormatter.ofPattern("HH:00"); + List> allRows = tDengineService.newQueryLastDataByHourBatch(sourceDeviceIds, start, end); + Map> hourCacheMap = buildTimeCacheByHour(allRows, pointInfoMap, deviceRulesMap); + + List xAxis = new ArrayList<>(); + List data = new ArrayList<>(); + LocalDateTime cursor = start.withMinute(0).withSecond(0); + LocalDateTime limit = end.withMinute(0).withSecond(0); + while (!cursor.isAfter(limit)) { + String hourKey = cursor.format(hourKeyFormatter); + double total = sumGroupedEnergy(deviceRulesMap, hourCacheMap.getOrDefault(hourKey, Collections.emptyMap())); + xAxis.add(cursor.format(displayFormatter)); + data.add(formatDouble(total)); + cursor = cursor.plusHours(1); + } + chart.setXAxis(xAxis); + chart.setData(data); + return chart; + } + + private EnergyOverviewRespVO.TrendChart buildDayTrendChart(EnergyOverviewRespVO.TrendChart chart, + Map> deviceRulesMap, + Set sourceDeviceIds, + Map pointInfoMap, + LocalDate startDate, + LocalDate endDate) { + int days = (int) (endDate.toEpochDay() - startDate.toEpochDay()) + 1; + List> allRows = tDengineService.queryLastDataByDayBatch(sourceDeviceIds, startDate, days); + Map> dayCacheMap = buildTimeCacheByDay(allRows, pointInfoMap, deviceRulesMap); + + List xAxis = new ArrayList<>(); + List data = new ArrayList<>(); + LocalDate cursor = startDate; + while (!cursor.isAfter(endDate)) { + String dayKey = cursor.toString(); + double total = sumGroupedEnergy(deviceRulesMap, dayCacheMap.getOrDefault(dayKey, Collections.emptyMap())); + xAxis.add(dayKey); + data.add(formatDouble(total)); + cursor = cursor.plusDays(1); + } + chart.setXAxis(xAxis); + chart.setData(data); + return chart; + } + + private Map> buildTimeCacheByHour(List> allRows, + Map pointInfoMap, + Map> deviceRulesMap) { + Map> cacheMap = new LinkedHashMap<>(); + DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH"); + for (Map row : allRows) { + LocalDateTime ts = (LocalDateTime) row.get("timestamp"); + Long deviceId = (Long) row.get("deviceId"); + if (ts == null || deviceId == null) { + continue; + } + String key = ts.format(formatter); + cacheMap.computeIfAbsent(key, item -> new HashMap<>()).put(deviceId, + buildTimePointCache(deviceId, ts, row, pointInfoMap, deviceRulesMap)); + } + return cacheMap; + } + + private Map> buildTimeCacheByDay(List> allRows, + Map pointInfoMap, + Map> deviceRulesMap) { + Map> cacheMap = new LinkedHashMap<>(); + for (Map row : allRows) { + String dayKey = (String) row.get("day"); + LocalDateTime ts = (LocalDateTime) row.get("timestamp"); + Long deviceId = (Long) row.get("deviceId"); + if (StringUtils.isBlank(dayKey) || ts == null || deviceId == null) { + continue; + } + cacheMap.computeIfAbsent(dayKey, item -> new HashMap<>()).put(deviceId, + buildTimePointCache(deviceId, ts, row, pointInfoMap, deviceRulesMap)); + } + return cacheMap; + } + + private TimePointCache buildTimePointCache(Long deviceId, + LocalDateTime ts, + Map row, + Map pointInfoMap, + Map> deviceRulesMap) { + TimePointCache cache = new TimePointCache(); + cache.setTimestamp(ts); + Map pointValueMap = new HashMap<>(); + List rules = deviceRulesMap.values().stream() + .flatMap(List::stream) + .filter(item -> deviceId.equals(item.getDeviceId())) + .collect(Collectors.toList()); + for (OperationRulesVO rule : rules) { + DeviceContactModelDO pointInfo = pointInfoMap.get(rule.getPointId()); + if (pointInfo == null || StringUtils.isBlank(pointInfo.getAttributeCode())) { + continue; + } + Object value = row.get(pointInfo.getAttributeCode().toLowerCase()); + if (value == null) { + continue; + } + try { + pointValueMap.put(rule.getPointId(), new BigDecimal(value.toString()).setScale(2, RoundingMode.HALF_UP).doubleValue()); + } catch (Exception e) { + log.warn("解析趋势点位值失败, deviceId={}, pointId={}, value={}", deviceId, rule.getPointId(), value, e); + } + } + cache.setPointValueMap(pointValueMap); + return cache; + } + + private double sumGroupedEnergy(Map> deviceRulesMap, Map cacheMap) { + double total = 0D; + for (Map.Entry> entry : deviceRulesMap.entrySet()) { + total += calculateByRules(entry.getKey(), entry.getValue(), cacheMap); + } + return total; + } + + private EnergyOverviewRespVO.TrendChart buildEmptyTrendChart(EnergyOverviewReqVO reqVO) { + EnergyOverviewRespVO.TrendChart chart = new EnergyOverviewRespVO.TrendChart(); + chart.setUnit(""); + chart.setXAxis(Collections.emptyList()); + chart.setData(Collections.emptyList()); + return chart; + } + + private EnergyOverviewRespVO.RegionChart buildRegionChart(List records) { + EnergyOverviewRespVO.RegionChart chart = new EnergyOverviewRespVO.RegionChart(); + chart.setUnit(""); + Map regionMap = new LinkedHashMap<>(); + double total = 0D; + for (EnergyDeviceRespVO record : records) { + double value = parseEnergyValue(record.getEnergyConsumption()); + total += value; + regionMap.merge(StringUtils.defaultIfBlank(record.getOrgName(), "-"), value, Double::sum); + } + List items = new ArrayList<>(); + for (Map.Entry entry : regionMap.entrySet()) { + EnergyOverviewRespVO.RegionItem item = new EnergyOverviewRespVO.RegionItem(); + item.setName(entry.getKey()); + item.setValue(formatDouble(entry.getValue())); + item.setPercent(total <= 0 ? "0" : formatDouble(entry.getValue() * 100 / total)); + items.add(item); + } + items.sort(Comparator.comparing((EnergyOverviewRespVO.RegionItem item) -> parseEnergyValue(item.getValue())).reversed()); + chart.setTotalValue(formatDouble(total)); + chart.setItems(items); + return chart; + } + + private List buildRankList(List records) { + return records.stream().limit(5).map(item -> { + EnergyOverviewRespVO.RankItem rankItem = new EnergyOverviewRespVO.RankItem(); + rankItem.setId(item.getId()); + rankItem.setName(item.getName()); + rankItem.setRegion(item.getOrgName()); + rankItem.setValue(item.getEnergyConsumption()); + return rankItem; + }).collect(Collectors.toList()); + } + + private List paginateDetails(List records, + List devices, + EnergyOverviewReqVO reqVO) { + Map deviceMap = devices.stream().collect(Collectors.toMap(EnergyDeviceDO::getId, Function.identity(), (a, b) -> a)); + int pageNo = reqVO.getPageNo() == null || reqVO.getPageNo() < 1 ? 1 : reqVO.getPageNo(); + int pageSize = reqVO.getPageSize() == null || reqVO.getPageSize() < 1 ? 10 : reqVO.getPageSize(); + int fromIndex = Math.min((pageNo - 1) * pageSize, records.size()); + int toIndex = Math.min(fromIndex + pageSize, records.size()); + List details = new ArrayList<>(); + for (EnergyDeviceRespVO item : records.subList(fromIndex, toIndex)) { + EnergyOverviewRespVO.DetailItem detailItem = new EnergyOverviewRespVO.DetailItem(); + detailItem.setId(item.getId()); + detailItem.setName(item.getName()); + detailItem.setEnergyType(Optional.ofNullable(deviceMap.get(item.getId())).map(EnergyDeviceDO::getDeviceTypeName).orElse(item.getDeviceTypeName())); + detailItem.setRegion(item.getOrgName()); + detailItem.setValue(item.getEnergyConsumption()); + detailItem.setStartTime(reqVO.getStartTime()); + detailItem.setEndTime(reqVO.getEndTime()); + details.add(detailItem); + } + return details; + } + + private Map> buildDeviceRulesMap(List devices) { + Map> result = new LinkedHashMap<>(); + for (EnergyDeviceDO device : devices) { + List rules = parseRules(device); + if (!rules.isEmpty()) { + result.put(device.getId(), rules); + } + } + return result; + } + + private Set collectRuleDeviceIds(Map> deviceRulesMap) { + return deviceRulesMap.values().stream() + .flatMap(List::stream) + .map(OperationRulesVO::getDeviceId) + .filter(Objects::nonNull) + .collect(Collectors.toSet()); + } + + private Set collectPointIds(Map> deviceRulesMap) { + return deviceRulesMap.values().stream() + .flatMap(List::stream) + .map(OperationRulesVO::getPointId) + .filter(Objects::nonNull) + .collect(Collectors.toSet()); + } + + private String buildRangeLabel(EnergyOverviewReqVO reqVO) { + if (StringUtils.isBlank(reqVO.getStartTime()) || StringUtils.isBlank(reqVO.getEndTime())) { + return "-"; + } + return reqVO.getStartTime() + " ~ " + reqVO.getEndTime(); + } + + private double parseEnergyValue(String value) { + if (StringUtils.isBlank(value)) { + return 0D; + } + try { + return Double.parseDouble(value.replace(",", "")); + } catch (Exception e) { + return 0D; + } + } + /** 解析设备规则 */ private List parseRules(EnergyDeviceDO device) { if (device == null || StringUtils.isBlank(device.getRules())) return Collections.emptyList(); @@ -969,4 +1336,4 @@ public class EnergyDeviceServiceImpl implements EnergyDeviceService { } -} \ No newline at end of file +}