Bläddra i källkod

Merge branch 'vpp-zyj' into feature/service-vpp-20260701

james 1 dag sedan
förälder
incheckning
312094dcb8

+ 14 - 0
service-vpp/service-vpp-biz/src/main/java/com/usky/vpp/constant/VppTsdbConstants.java

@@ -31,5 +31,19 @@ public final class VppTsdbConstants {
             "downCapacityKw"
     );
 
+    /** 资源看板实时指标 */
+    public static final List<String> BOARD_REALTIME_METRICS = Arrays.asList(
+            "device_status",
+            "p",
+            "ua",
+            "ia"
+    );
+
+    /** 日发电量(光伏) */
+    public static final String METRIC_EDAY = "eday";
+
+    /** 正向总有功电能(用电累计) */
+    public static final String METRIC_EPP = "epp";
+
     public static final String TIME_FORMAT = "yyyy-MM-dd HH:mm:ss";
 }

+ 10 - 10
service-vpp/service-vpp-biz/src/main/java/com/usky/vpp/controller/web/ResourceController.java

@@ -35,7 +35,7 @@ public class ResourceController {
     private VppResourceOverviewService vppResourceOverviewService;
 
     /**
-     * 资源看板 - 实时运行数据列表(模拟数据)
+     * 资源看板 - 实时运行数据列表
      * <p>展示所有资源点的实时功率、电压、电流、储能SOC、充电桩充电功率;
      * 支持按 resourceType(资源类型)、province/city/district(区域)、status(normal/abnormal)筛选;
      * abnormal=true 的资源需前端红色高亮。</p>
@@ -66,21 +66,21 @@ public class ResourceController {
 
     /**
      * 资源概览 - 今日电能概况
-     * <p>总发电量、总用电量、绿电消纳比例(模拟数据)。</p>
+     * <p>总发电量、总用电量、绿电消纳比例(TSDB 今日 eday/epp 汇总)。</p>
      */
     @GetMapping("/overview/energy-today")
     public ApiResult<EnergyTodayVO> resourceOverviewEnergyToday() {
         return ApiResult.success(vppResourceOverviewService.getEnergyToday());
     }
 
-    /**
-     * 资源概览 - 实时告警统计
-     * <p>按紧急/重要/一般统计告警数量,含跳转告警列表路径(模拟数据)。</p>
-     */
-    @GetMapping("/overview/alarm-stats")
-    public ApiResult<List<AlarmLevelStatVO>> resourceOverviewAlarmStats() {
-        return ApiResult.success(vppResourceOverviewService.getAlarmStats());
-    }
+//    /**
+//     * 资源概览 - 实时告警统计
+//     * <p>按紧急/重要/一般统计告警数量,含跳转告警列表路径(模拟数据)。</p>
+//     */
+//    @GetMapping("/overview/alarm-stats")
+//    public ApiResult<List<AlarmLevelStatVO>> resourceOverviewAlarmStats() {
+//        return ApiResult.success(vppResourceOverviewService.getAlarmStats());
+//    }
 
     /**
      * 资源点管理 - 分页列表

+ 86 - 13
service-vpp/service-vpp-biz/src/main/java/com/usky/vpp/service/impl/VppResourceBoardServiceImpl.java

@@ -4,20 +4,23 @@ import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
 import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
 import com.usky.common.core.bean.CommonPage;
 import com.usky.common.core.exception.BusinessException;
+import com.usky.common.security.utils.SecurityUtils;
 import com.usky.vpp.domain.VppDevice;
 import com.usky.vpp.domain.VppResourcePoint;
 import com.usky.vpp.domain.VppSite;
 import com.usky.vpp.mapper.VppDeviceMapper;
 import com.usky.vpp.mapper.VppResourcePointMapper;
 import com.usky.vpp.mapper.VppSiteMapper;
+import com.usky.vpp.constant.VppTsdbConstants;
 import com.usky.vpp.service.VppResourceBoardService;
+import com.usky.vpp.service.VppTsdbQueryService;
 import com.usky.vpp.service.vo.ResourceBoardDetailVO;
 import com.usky.vpp.service.vo.ResourceBoardItemVO;
 import com.usky.vpp.util.VppAuditHelper;
-import com.usky.vpp.util.VppPageHelper;
 import com.usky.vpp.util.VppResourceMockHelper;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.stereotype.Service;
+import org.springframework.util.CollectionUtils;
 import org.springframework.util.StringUtils;
 
 import java.math.BigDecimal;
@@ -34,6 +37,8 @@ public class VppResourceBoardServiceImpl implements VppResourceBoardService {
     private VppSiteMapper siteMapper;
     @Autowired
     private VppDeviceMapper deviceMapper;
+    @Autowired
+    private VppTsdbQueryService vppTsdbQueryService;
 
     @Override
     public CommonPage<ResourceBoardItemVO> pageBoard(Map<String, Object> params) {
@@ -58,8 +63,9 @@ public class VppResourceBoardServiceImpl implements VppResourceBoardService {
         }
         VppSite site = siteMapper.selectById(resource.getSiteId());
         VppDevice device = loadDeviceForResource(resource);
+        Map<String, BigDecimal> tsdbMetrics = loadTsdbMetrics(device);
         ResourceBoardDetailVO detail = new ResourceBoardDetailVO();
-        fillBoardItem(detail, resource, site, device);
+        fillBoardItem(detail, resource, site, device, tsdbMetrics);
         detail.setDeviceId(resource.getDeviceId());
         detail.setDeviceName(device != null ? device.getDeviceName() : null);
         detail.setDeviceCode(device != null ? device.getDeviceCode() : null);
@@ -73,7 +79,7 @@ public class VppResourceBoardServiceImpl implements VppResourceBoardService {
             detail.setContactPhone(site.getContactPhone());
         }
         if (Boolean.TRUE.equals(detail.getAbnormal())) {
-            detail.setAbnormalReason(VppResourceMockHelper.mockAbnormalReason(resource, device));
+            detail.setAbnormalReason(resolveAbnormalReason(tsdbMetrics));
         }
         detail.setDataTime(LocalDateTime.now());
         return detail;
@@ -82,6 +88,7 @@ public class VppResourceBoardServiceImpl implements VppResourceBoardService {
     private List<VppResourcePoint> loadFilteredResources(Map<String, Object> params) {
         LambdaQueryWrapper<VppResourcePoint> wrapper = new LambdaQueryWrapper<VppResourcePoint>()
                 .eq(VppResourcePoint::getDeleteFlag, VppAuditHelper.NOT_DELETED)
+                .eq(VppResourcePoint::getTenantId, SecurityUtils.getTenantId())
                 .orderByDesc(VppResourcePoint::getCreateTime);
         if (params != null) {
             if (params.get("resourceType") != null) {
@@ -138,11 +145,14 @@ public class VppResourceBoardServiceImpl implements VppResourceBoardService {
                 .filter(d -> !VppAuditHelper.isDeleted(d.getDeleteFlag()))
                 .collect(Collectors.toMap(VppDevice::getId, d -> d, (a, b) -> a));
 
+        Map<String, Map<String, BigDecimal>> tsdbMetricsMap = loadTsdbMetricsMap(deviceMap.values());
+
         List<ResourceBoardItemVO> items = new ArrayList<>();
         for (VppResourcePoint resource : resources) {
             VppDevice device = resolveDeviceForResource(resource, deviceMap);
+            Map<String, BigDecimal> tsdbMetrics = resolveTsdbMetrics(device, tsdbMetricsMap);
             ResourceBoardItemVO item = new ResourceBoardItemVO();
-            fillBoardItem(item, resource, siteMap.get(resource.getSiteId()), device);
+            fillBoardItem(item, resource, siteMap.get(resource.getSiteId()), device, tsdbMetrics);
             items.add(item);
         }
         return items;
@@ -168,7 +178,8 @@ public class VppResourceBoardServiceImpl implements VppResourceBoardService {
         return device != null && siteId != null && siteId.equals(device.getSiteId());
     }
 
-    private void fillBoardItem(ResourceBoardItemVO item, VppResourcePoint resource, VppSite site, VppDevice device) {
+    private void fillBoardItem(ResourceBoardItemVO item, VppResourcePoint resource, VppSite site, VppDevice device,
+                               Map<String, BigDecimal> tsdbMetrics) {
         item.setResourceId(resource.getId());
         item.setResourceCode(resource.getResourceCode());
         item.setResourceName(resource.getResourceName());
@@ -182,18 +193,80 @@ public class VppResourceBoardServiceImpl implements VppResourceBoardService {
             item.setCity(site.getCity());
             item.setDistrict(site.getDistrict());
         }
-        boolean abnormal = VppResourceMockHelper.mockAbnormal(resource, device);
-        item.setAbnormal(abnormal);
-        item.setRunStatusLabel(VppResourceMockHelper.runStatusLabel(device, abnormal));
-        BigDecimal power = VppResourceMockHelper.mockRealtimePowerKw(resource, device);
-        BigDecimal voltage = VppResourceMockHelper.mockVoltageV(resource);
-        item.setRealtimePowerKw(power);
-        item.setVoltageV(voltage);
-        item.setCurrentA(VppResourceMockHelper.mockCurrentA(resource, power, voltage));
+        applyTsdbMetrics(item, tsdbMetrics);
         item.setSocPercent(VppResourceMockHelper.mockSocPercent(resource));
         item.setEvcsChargePowerKw(VppResourceMockHelper.mockEvcsChargePowerKw(resource));
     }
 
+    private void applyTsdbMetrics(ResourceBoardItemVO item, Map<String, BigDecimal> tsdbMetrics) {
+        BigDecimal deviceStatus = getMetric(tsdbMetrics, "device_status");
+        boolean abnormal = deviceStatus != null && deviceStatus.compareTo(BigDecimal.ZERO) == 0;
+        item.setAbnormal(abnormal);
+        item.setRunStatusLabel(resolveRunStatusLabel(deviceStatus));
+        item.setRealtimePowerKw(getMetric(tsdbMetrics, "p"));
+        item.setVoltageV(getMetric(tsdbMetrics, "ua"));
+        item.setCurrentA(getMetric(tsdbMetrics, "ia"));
+    }
+
+    private Map<String, Map<String, BigDecimal>> loadTsdbMetricsMap(Collection<VppDevice> devices) {
+        List<String> deviceUuids = devices.stream()
+                .map(VppDevice::getDeviceUuid)
+                .filter(StringUtils::hasText)
+                .distinct()
+                .collect(Collectors.toList());
+        if (deviceUuids.isEmpty()) {
+            return Collections.emptyMap();
+        }
+        return vppTsdbQueryService.queryLatestMetrics(deviceUuids, VppTsdbConstants.BOARD_REALTIME_METRICS);
+    }
+
+    private Map<String, BigDecimal> loadTsdbMetrics(VppDevice device) {
+        if (device == null || !StringUtils.hasText(device.getDeviceUuid())) {
+            return Collections.emptyMap();
+        }
+        Map<String, Map<String, BigDecimal>> metricsMap = vppTsdbQueryService.queryLatestMetrics(
+                Collections.singletonList(device.getDeviceUuid()),
+                VppTsdbConstants.BOARD_REALTIME_METRICS);
+        return metricsMap.getOrDefault(device.getDeviceUuid(), Collections.emptyMap());
+    }
+
+    private Map<String, BigDecimal> resolveTsdbMetrics(VppDevice device, Map<String, Map<String, BigDecimal>> tsdbMetricsMap) {
+        if (device == null || !StringUtils.hasText(device.getDeviceUuid())) {
+            return Collections.emptyMap();
+        }
+        Map<String, BigDecimal> metrics = tsdbMetricsMap.get(device.getDeviceUuid());
+        return metrics != null ? metrics : Collections.emptyMap();
+    }
+
+    private BigDecimal getMetric(Map<String, BigDecimal> tsdbMetrics, String metric) {
+        if (CollectionUtils.isEmpty(tsdbMetrics)) {
+            return null;
+        }
+        return tsdbMetrics.get(metric);
+    }
+
+    private String resolveRunStatusLabel(BigDecimal deviceStatus) {
+        if (deviceStatus == null) {
+            return "未知";
+        }
+        int status = deviceStatus.intValue();
+        if (status == 0) {
+            return "离线";
+        }
+        if (status == 1) {
+            return "在线";
+        }
+        return "未知";
+    }
+
+    private String resolveAbnormalReason(Map<String, BigDecimal> tsdbMetrics) {
+        BigDecimal deviceStatus = getMetric(tsdbMetrics, "device_status");
+        if (deviceStatus != null && deviceStatus.compareTo(BigDecimal.ZERO) == 0) {
+            return "设备通信离线";
+        }
+        return "设备运行异常";
+    }
+
     private List<ResourceBoardItemVO> filterByStatus(List<ResourceBoardItemVO> items, Map<String, Object> params) {
         if (params == null || params.get("status") == null) {
             return items;

+ 116 - 11
service-vpp/service-vpp-biz/src/main/java/com/usky/vpp/service/impl/VppResourceOverviewServiceImpl.java

@@ -1,9 +1,14 @@
 package com.usky.vpp.service.impl;
 
 import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
+import com.usky.common.security.utils.SecurityUtils;
+import com.usky.vpp.constant.VppTsdbConstants;
+import com.usky.vpp.domain.VppDevice;
 import com.usky.vpp.domain.VppResourcePoint;
+import com.usky.vpp.mapper.VppDeviceMapper;
 import com.usky.vpp.mapper.VppResourcePointMapper;
 import com.usky.vpp.service.VppResourceOverviewService;
+import com.usky.vpp.service.VppTsdbQueryService;
 import com.usky.vpp.service.vo.AlarmLevelStatVO;
 import com.usky.vpp.service.vo.EnergyTodayVO;
 import com.usky.vpp.service.vo.ResourceTypeStatVO;
@@ -11,14 +16,24 @@ import com.usky.vpp.util.VppAuditHelper;
 import com.usky.vpp.util.VppResourceMockHelper;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.stereotype.Service;
+import org.springframework.util.CollectionUtils;
+import org.springframework.util.StringUtils;
 
 import java.math.BigDecimal;
 import java.math.RoundingMode;
+import java.time.LocalDate;
+import java.time.LocalDateTime;
 import java.util.ArrayList;
 import java.util.Arrays;
+import java.util.Collections;
 import java.util.LinkedHashMap;
+import java.util.LinkedHashSet;
 import java.util.List;
 import java.util.Map;
+import java.util.Objects;
+import java.util.Set;
+import java.util.TreeMap;
+import java.util.stream.Collectors;
 
 @Service
 public class VppResourceOverviewServiceImpl implements VppResourceOverviewService {
@@ -27,6 +42,10 @@ public class VppResourceOverviewServiceImpl implements VppResourceOverviewServic
 
     @Autowired
     private VppResourcePointMapper resourcePointMapper;
+    @Autowired
+    private VppDeviceMapper deviceMapper;
+    @Autowired
+    private VppTsdbQueryService vppTsdbQueryService;
 
     @Override
     public List<ResourceTypeStatVO> getTypeStats() {
@@ -35,7 +54,9 @@ public class VppResourceOverviewServiceImpl implements VppResourceOverviewServic
 
     @Override
     public EnergyTodayVO getEnergyToday() {
-        return buildEnergyTodayMock(listActiveResources());
+        List<VppResourcePoint> resources = listActiveResources();
+        Map<Long, VppDevice> deviceMap = loadDeviceMap(resources);
+        return buildEnergyToday(resources, deviceMap);
     }
 
     @Override
@@ -46,7 +67,8 @@ public class VppResourceOverviewServiceImpl implements VppResourceOverviewServic
     private List<VppResourcePoint> listActiveResources() {
         return resourcePointMapper.selectList(
                 new LambdaQueryWrapper<VppResourcePoint>()
-                        .eq(VppResourcePoint::getDeleteFlag, VppAuditHelper.NOT_DELETED));
+                        .eq(VppResourcePoint::getDeleteFlag, VppAuditHelper.NOT_DELETED)
+                        .eq(VppResourcePoint::getTenantId, SecurityUtils.getTenantId()));
     }
 
     private List<ResourceTypeStatVO> buildTypeStats(List<VppResourcePoint> resources) {
@@ -81,19 +103,28 @@ public class VppResourceOverviewServiceImpl implements VppResourceOverviewServic
         return new ArrayList<>(statMap.values());
     }
 
-    private EnergyTodayVO buildEnergyTodayMock(List<VppResourcePoint> resources) {
-        BigDecimal pvCapacity = resources.stream()
+    private EnergyTodayVO buildEnergyToday(List<VppResourcePoint> resources, Map<Long, VppDevice> deviceMap) {
+        List<VppResourcePoint> pvResources = resources.stream()
                 .filter(r -> "PV".equalsIgnoreCase(r.getResourceType()))
-                .map(r -> r.getCapacityKw() != null ? r.getCapacityKw() : BigDecimal.ZERO)
-                .reduce(BigDecimal.ZERO, BigDecimal::add);
-        BigDecimal loadCapacity = resources.stream()
+                .collect(Collectors.toList());
+        List<VppResourcePoint> loadResources = resources.stream()
                 .filter(r -> !"PV".equalsIgnoreCase(r.getResourceType()))
-                .map(r -> r.getCapacityKw() != null ? r.getCapacityKw() : BigDecimal.ZERO)
-                .reduce(BigDecimal.ZERO, BigDecimal::add);
+                .collect(Collectors.toList());
+
+        List<String> pvDeviceUuids = resolveDeviceUuids(pvResources, deviceMap);
+        List<String> loadDeviceUuids = resolveDeviceUuids(loadResources, deviceMap);
+
+        LocalDateTime startTime = LocalDate.now().atStartOfDay();
+        LocalDateTime endTime = LocalDateTime.now();
+
+        BigDecimal totalGeneration = queryTodayEnergy(pvDeviceUuids, startTime, endTime,
+                Collections.singletonList(VppTsdbConstants.METRIC_EDAY), true);
+        BigDecimal totalConsumption = queryTodayEnergy(loadDeviceUuids, startTime, endTime,
+                Collections.singletonList(VppTsdbConstants.METRIC_EPP), false);
 
         EnergyTodayVO energy = new EnergyTodayVO();
-        energy.setTotalGenerationKwh(pvCapacity.multiply(BigDecimal.valueOf(4.2)).setScale(2, RoundingMode.HALF_UP));
-        energy.setTotalConsumptionKwh(loadCapacity.multiply(BigDecimal.valueOf(3.6)).setScale(2, RoundingMode.HALF_UP));
+        energy.setTotalGenerationKwh(totalGeneration.setScale(2, RoundingMode.HALF_UP));
+        energy.setTotalConsumptionKwh(totalConsumption.setScale(2, RoundingMode.HALF_UP));
         if (energy.getTotalConsumptionKwh().compareTo(BigDecimal.ZERO) > 0) {
             BigDecimal ratio = energy.getTotalGenerationKwh()
                     .multiply(BigDecimal.valueOf(100))
@@ -105,6 +136,80 @@ public class VppResourceOverviewServiceImpl implements VppResourceOverviewServic
         return energy;
     }
 
+    private BigDecimal queryTodayEnergy(List<String> deviceUuids, LocalDateTime startTime, LocalDateTime endTime,
+                                          List<String> metrics, boolean useLatestValue) {
+        if (CollectionUtils.isEmpty(deviceUuids)) {
+            return BigDecimal.ZERO;
+        }
+        Map<String, Map<String, TreeMap<LocalDateTime, BigDecimal>>> history =
+                vppTsdbQueryService.queryDeviceMetricHistory(deviceUuids, startTime, endTime, metrics);
+        if (CollectionUtils.isEmpty(history)) {
+            return BigDecimal.ZERO;
+        }
+        String metric = metrics.get(0);
+        return useLatestValue ? sumLatestMetric(history, metric) : sumDiffMetric(history, metric);
+    }
+
+    private BigDecimal sumLatestMetric(Map<String, Map<String, TreeMap<LocalDateTime, BigDecimal>>> history, String metric) {
+        BigDecimal total = BigDecimal.ZERO;
+        for (Map<String, TreeMap<LocalDateTime, BigDecimal>> deviceMetrics : history.values()) {
+            TreeMap<LocalDateTime, BigDecimal> series = deviceMetrics.get(metric);
+            if (series == null || series.isEmpty()) {
+                continue;
+            }
+            total = total.add(series.lastEntry().getValue());
+        }
+        return total;
+    }
+
+    private BigDecimal sumDiffMetric(Map<String, Map<String, TreeMap<LocalDateTime, BigDecimal>>> history, String metric) {
+        BigDecimal total = BigDecimal.ZERO;
+        for (Map<String, TreeMap<LocalDateTime, BigDecimal>> deviceMetrics : history.values()) {
+            TreeMap<LocalDateTime, BigDecimal> series = deviceMetrics.get(metric);
+            if (series == null || series.isEmpty()) {
+                continue;
+            }
+            BigDecimal first = series.firstEntry().getValue();
+            BigDecimal last = series.lastEntry().getValue();
+            BigDecimal diff = last.subtract(first);
+            if (diff.compareTo(BigDecimal.ZERO) > 0) {
+                total = total.add(diff);
+            }
+        }
+        return total;
+    }
+
+    private Map<Long, VppDevice> loadDeviceMap(List<VppResourcePoint> resources) {
+        Set<Long> deviceIds = resources.stream()
+                .map(VppResourcePoint::getDeviceId)
+                .filter(Objects::nonNull)
+                .collect(Collectors.toSet());
+        if (deviceIds.isEmpty()) {
+            return Collections.emptyMap();
+        }
+        return deviceMapper.selectBatchIds(deviceIds).stream()
+                .filter(d -> !VppAuditHelper.isDeleted(d.getDeleteFlag()))
+                .collect(Collectors.toMap(VppDevice::getId, d -> d, (a, b) -> a));
+    }
+
+    private List<String> resolveDeviceUuids(List<VppResourcePoint> resources, Map<Long, VppDevice> deviceMap) {
+        if (CollectionUtils.isEmpty(resources) || CollectionUtils.isEmpty(deviceMap)) {
+            return Collections.emptyList();
+        }
+        Set<String> uuids = new LinkedHashSet<>();
+        for (VppResourcePoint resource : resources) {
+            if (resource.getDeviceId() == null) {
+                continue;
+            }
+            VppDevice device = deviceMap.get(resource.getDeviceId());
+            if (device == null || !StringUtils.hasText(device.getDeviceUuid())) {
+                continue;
+            }
+            uuids.add(device.getDeviceUuid().trim());
+        }
+        return new ArrayList<>(uuids);
+    }
+
     private List<AlarmLevelStatVO> buildAlarmStatsMock() {
         List<AlarmLevelStatVO> stats = new ArrayList<>();
         stats.add(buildAlarmStat(1, "紧急", 3L));

+ 391 - 196
service-vpp/service-vpp-biz/src/main/java/com/usky/vpp/service/impl/VppTsdbQueryServiceImpl.java

@@ -1,196 +1,391 @@
-package com.usky.vpp.service.impl;

-

-import com.usky.common.core.bean.ApiResult;

-import com.usky.demo.RemoteTsdbProxyService;

-import com.usky.demo.domain.HistorysInnerRequestVO;

-import com.usky.demo.domain.HistorysInnerResultVO;

-import com.usky.demo.domain.LastInnerQueryVO;

-import com.usky.demo.domain.LastInnerResultVO;

-import com.usky.demo.domain.MetricVO;

-import com.usky.vpp.constant.VppTsdbConstants;

-import com.usky.vpp.service.VppTsdbQueryService;

-import com.usky.vpp.util.VppBaselineHelper;

-import org.slf4j.Logger;

-import org.slf4j.LoggerFactory;

-import org.springframework.beans.factory.annotation.Autowired;

-import org.springframework.stereotype.Service;

-import org.springframework.util.CollectionUtils;

-import org.springframework.util.StringUtils;

-

-import java.math.BigDecimal;

-import java.math.RoundingMode;

-import java.time.LocalDate;

-import java.time.LocalDateTime;

-import java.time.LocalTime;

-import java.time.format.DateTimeFormatter;

-import java.time.format.DateTimeFormatterBuilder;

-import java.time.temporal.ChronoField;

-import java.util.ArrayList;

-import java.util.Collections;

-import java.util.HashMap;

-import java.util.LinkedHashSet;

-import java.util.List;

-import java.util.Map;

-import java.util.TreeMap;

-

-/**

- * TDengine 时序数据查询(通过 service-tsdb Feign,与 service-ems 一致)

- */

-@Service

-public class VppTsdbQueryServiceImpl implements VppTsdbQueryService {

-

-    private static final Logger log = LoggerFactory.getLogger(VppTsdbQueryServiceImpl.class);

-

-    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();

-

-    @Autowired

-    private RemoteTsdbProxyService remoteTsdbProxyService;

-

-    @Override

-    public Map<String, Map<String, TreeMap<LocalDateTime, BigDecimal>>> queryDeviceMetricHistory(

-            List<String> deviceUuids,

-            LocalDateTime startTime,

-            LocalDateTime endTime,

-            List<String> metrics) {

-        if (CollectionUtils.isEmpty(deviceUuids) || startTime == null || endTime == null) {

-            return Collections.emptyMap();

-        }

-

-        HistorysInnerRequestVO request = new HistorysInnerRequestVO();

-        request.setDeviceuuid(new ArrayList<>(new LinkedHashSet<>(deviceUuids)));

-        request.setStartTime(startTime.format(DateTimeFormatter.ofPattern(VppTsdbConstants.TIME_FORMAT)));

-        request.setEndTime(endTime.format(DateTimeFormatter.ofPattern(VppTsdbConstants.TIME_FORMAT)));

-        if (!CollectionUtils.isEmpty(metrics)) {

-            request.setMetrics(new ArrayList<>(metrics));

-        }

-

-        try {

-            ApiResult<List<HistorysInnerResultVO>> result = remoteTsdbProxyService.queryHistoryDeviceData(request);

-            if (result == null || !result.isSuccess() || CollectionUtils.isEmpty(result.getData())) {

-                log.warn("TDengine 历史查询无数据, devices={}, start={}, end={}",

-                        deviceUuids.size(), request.getStartTime(), request.getEndTime());

-                return Collections.emptyMap();

-            }

-            return parseHistoryResult(result.getData());

-        } catch (Exception ex) {

-            log.error("TDengine 历史查询失败: {}", ex.getMessage(), ex);

-            return Collections.emptyMap();

-        }

-    }

-

-    @Override

-    public Map<String, Map<String, BigDecimal>> queryLatestMetrics(List<String> deviceUuids, List<String> metrics) {

-        if (CollectionUtils.isEmpty(deviceUuids)) {

-            return Collections.emptyMap();

-        }

-

-        LastInnerQueryVO request = new LastInnerQueryVO();

-        request.setDeviceuuid(new ArrayList<>(new LinkedHashSet<>(deviceUuids)));

-        if (!CollectionUtils.isEmpty(metrics)) {

-            request.setMetrics(new ArrayList<>(metrics));

-        }

-

-        try {

-            ApiResult<List<LastInnerResultVO>> result = remoteTsdbProxyService.queryLastDeviceData(request);

-            if (result == null || !result.isSuccess() || CollectionUtils.isEmpty(result.getData())) {

-                return Collections.emptyMap();

-            }

-            return parseLatestResult(result.getData());

-        } catch (Exception ex) {

-            log.error("TDengine 最新值查询失败: {}", ex.getMessage(), ex);

-            return Collections.emptyMap();

-        }

-    }

-

-    @Override

-    public Map<String, BigDecimal> queryBaselineAvgByTimePoint(List<String> deviceUuids,

-                                                              List<LocalDate> referenceDates,

-                                                              LocalTime periodStart,

-                                                              LocalTime periodEnd,

-                                                              List<String> metrics,

-                                                              Integer intervalMinutes,

-                                                              BigDecimal correctionFactor) {

-        if (CollectionUtils.isEmpty(deviceUuids) || CollectionUtils.isEmpty(referenceDates)) {

-            return Collections.emptyMap();

-        }

-        int interval = intervalMinutes != null && intervalMinutes > 0

-                ? intervalMinutes : VppBaselineHelper.INTERVAL_MINUTES;

-        return VppBaselineHelper.aggregateBaselineAvgByTimePoint(

-                referenceDates,

-                periodStart,

-                periodEnd,

-                interval,

-                correctionFactor,

-                (start, end) -> queryDeviceMetricHistory(deviceUuids, start, end, metrics));

-    }

-

-    private Map<String, Map<String, TreeMap<LocalDateTime, BigDecimal>>> parseHistoryResult(

-            List<HistorysInnerResultVO> historyDataList) {

-        Map<String, Map<String, TreeMap<LocalDateTime, BigDecimal>>> deviceMetricMap = new HashMap<>();

-        for (HistorysInnerResultVO result : historyDataList) {

-            if (result == null || !StringUtils.hasText(result.getDeviceuuid()) || CollectionUtils.isEmpty(result.getMetrics())) {

-                continue;

-            }

-            String deviceUuid = result.getDeviceuuid();

-            Map<String, TreeMap<LocalDateTime, BigDecimal>> metricMap =

-                    deviceMetricMap.computeIfAbsent(deviceUuid, key -> new HashMap<>());

-

-            for (MetricVO metric : result.getMetrics()) {

-                if (metric == null || !StringUtils.hasText(metric.getMetric()) || CollectionUtils.isEmpty(metric.getMetricItems())) {

-                    continue;

-                }

-                TreeMap<LocalDateTime, BigDecimal> timeValueMap =

-                        metricMap.computeIfAbsent(metric.getMetric(), key -> new TreeMap<>());

-                for (Map<String, Object> point : metric.getMetricItems()) {

-                    if (point == null || point.get("timestamp") == null || point.get("value") == null) {

-                        continue;

-                    }

-                    try {

-                        LocalDateTime timestamp = parseTimestamp(point.get("timestamp").toString());

-                        BigDecimal value = new BigDecimal(point.get("value").toString());

-                        timeValueMap.put(timestamp, value);

-                    } catch (Exception ignored) {

-                        // skip invalid point

-                    }

-                }

-            }

-        }

-        return deviceMetricMap;

-    }

-

-    private Map<String, Map<String, BigDecimal>> parseLatestResult(List<LastInnerResultVO> latestList) {

-        Map<String, Map<String, BigDecimal>> result = new HashMap<>();

-        for (LastInnerResultVO item : latestList) {

-            if (item == null || !StringUtils.hasText(item.getDeviceuuid()) || item.getMetrics() == null) {

-                continue;

-            }

-            Map<String, BigDecimal> metricValues = new HashMap<>();

-            item.getMetrics().forEach((metric, value) -> {

-                if (value == null) {

-                    return;

-                }

-                try {

-                    metricValues.put(metric, new BigDecimal(value.toString()).setScale(4, RoundingMode.HALF_UP));

-                } catch (Exception ignored) {

-                    // skip invalid value

-                }

-            });

-            result.put(item.getDeviceuuid(), metricValues);

-        }

-        return result;

-    }

-

-    private LocalDateTime parseTimestamp(String timestampStr) {

-        String normalized = timestampStr.trim();

-        if (normalized.endsWith(".0")) {

-            normalized = normalized.substring(0, normalized.length() - 2);

-        }

-        return LocalDateTime.parse(normalized, TSDB_FLEXIBLE_TIME_FORMAT);

-    }

-}

-
+package com.usky.vpp.service.impl;
+
+
+
+import com.usky.common.core.bean.ApiResult;
+
+import com.usky.demo.RemoteTsdbProxyService;
+
+import com.usky.demo.domain.HistorysInnerRequestVO;
+
+import com.usky.demo.domain.HistorysInnerResultVO;
+
+import com.usky.demo.domain.LastInnerQueryVO;
+
+import com.usky.demo.domain.LastInnerResultVO;
+
+import com.usky.demo.domain.MetricVO;
+
+import com.usky.vpp.constant.VppTsdbConstants;
+
+import com.usky.vpp.service.VppTsdbQueryService;
+
+import com.usky.vpp.util.VppBaselineHelper;
+
+import org.slf4j.Logger;
+
+import org.slf4j.LoggerFactory;
+
+import org.springframework.beans.factory.annotation.Autowired;
+
+import org.springframework.stereotype.Service;
+
+import org.springframework.util.CollectionUtils;
+
+import org.springframework.util.StringUtils;
+
+
+
+import java.math.BigDecimal;
+
+import java.math.RoundingMode;
+
+import java.time.LocalDate;
+
+import java.time.LocalDateTime;
+
+import java.time.LocalTime;
+
+import java.time.format.DateTimeFormatter;
+
+import java.time.format.DateTimeFormatterBuilder;
+
+import java.time.temporal.ChronoField;
+
+import java.util.ArrayList;
+
+import java.util.Collections;
+
+import java.util.HashMap;
+
+import java.util.LinkedHashSet;
+
+import java.util.List;
+
+import java.util.Map;
+
+import java.util.TreeMap;
+
+
+
+/**
+
+ * TDengine 时序数据查询(通过 service-tsdb Feign,与 service-ems 一致)
+
+ */
+
+@Service
+
+public class VppTsdbQueryServiceImpl implements VppTsdbQueryService {
+
+
+
+    private static final Logger log = LoggerFactory.getLogger(VppTsdbQueryServiceImpl.class);
+
+
+
+    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();
+
+
+
+    @Autowired
+
+    private RemoteTsdbProxyService remoteTsdbProxyService;
+
+
+
+    @Override
+
+    public Map<String, Map<String, TreeMap<LocalDateTime, BigDecimal>>> queryDeviceMetricHistory(
+
+            List<String> deviceUuids,
+
+            LocalDateTime startTime,
+
+            LocalDateTime endTime,
+
+            List<String> metrics) {
+
+        if (CollectionUtils.isEmpty(deviceUuids) || startTime == null || endTime == null) {
+
+            return Collections.emptyMap();
+
+        }
+
+
+
+        HistorysInnerRequestVO request = new HistorysInnerRequestVO();
+
+        request.setDeviceuuid(new ArrayList<>(new LinkedHashSet<>(deviceUuids)));
+
+        request.setStartTime(startTime.format(DateTimeFormatter.ofPattern(VppTsdbConstants.TIME_FORMAT)));
+
+        request.setEndTime(endTime.format(DateTimeFormatter.ofPattern(VppTsdbConstants.TIME_FORMAT)));
+
+        if (!CollectionUtils.isEmpty(metrics)) {
+
+            request.setMetrics(new ArrayList<>(metrics));
+
+        }
+
+
+
+        try {
+
+            ApiResult<List<HistorysInnerResultVO>> result = remoteTsdbProxyService.queryHistoryDeviceData(request);
+
+            if (result == null || !result.isSuccess() || CollectionUtils.isEmpty(result.getData())) {
+
+                log.warn("TDengine 历史查询无数据, devices={}, start={}, end={}",
+
+                        deviceUuids.size(), request.getStartTime(), request.getEndTime());
+
+                return Collections.emptyMap();
+
+            }
+
+            return parseHistoryResult(result.getData());
+
+        } catch (Exception ex) {
+
+            log.error("TDengine 历史查询失败: {}", ex.getMessage(), ex);
+
+            return Collections.emptyMap();
+
+        }
+
+    }
+
+
+
+    @Override
+
+    public Map<String, Map<String, BigDecimal>> queryLatestMetrics(List<String> deviceUuids, List<String> metrics) {
+
+        if (CollectionUtils.isEmpty(deviceUuids)) {
+
+            return Collections.emptyMap();
+
+        }
+
+
+
+        LastInnerQueryVO request = new LastInnerQueryVO();
+
+        request.setDeviceuuid(new ArrayList<>(new LinkedHashSet<>(deviceUuids)));
+
+        if (!CollectionUtils.isEmpty(metrics)) {
+
+            request.setMetrics(new ArrayList<>(metrics));
+
+        }
+
+
+
+        try {
+
+            ApiResult<List<LastInnerResultVO>> result = remoteTsdbProxyService.queryLastDeviceData(request);
+
+            if (result == null || !result.isSuccess() || CollectionUtils.isEmpty(result.getData())) {
+
+                return Collections.emptyMap();
+
+            }
+
+            return parseLatestResult(result.getData());
+
+        } catch (Exception ex) {
+
+            log.error("TDengine 最新值查询失败: {}", ex.getMessage(), ex);
+
+            return Collections.emptyMap();
+
+        }
+
+    }
+
+
+
+    @Override
+
+    public Map<String, BigDecimal> queryBaselineAvgByTimePoint(List<String> deviceUuids,
+
+                                                              List<LocalDate> referenceDates,
+
+                                                              LocalTime periodStart,
+
+                                                              LocalTime periodEnd,
+
+                                                              List<String> metrics,
+
+                                                              Integer intervalMinutes,
+
+                                                              BigDecimal correctionFactor) {
+
+        if (CollectionUtils.isEmpty(deviceUuids) || CollectionUtils.isEmpty(referenceDates)) {
+
+            return Collections.emptyMap();
+
+        }
+
+        int interval = intervalMinutes != null && intervalMinutes > 0
+
+                ? intervalMinutes : VppBaselineHelper.INTERVAL_MINUTES;
+
+        return VppBaselineHelper.aggregateBaselineAvgByTimePoint(
+
+                referenceDates,
+
+                periodStart,
+
+                periodEnd,
+
+                interval,
+
+                correctionFactor,
+
+                (start, end) -> queryDeviceMetricHistory(deviceUuids, start, end, metrics));
+
+    }
+
+
+
+    private Map<String, Map<String, TreeMap<LocalDateTime, BigDecimal>>> parseHistoryResult(
+
+            List<HistorysInnerResultVO> historyDataList) {
+
+        Map<String, Map<String, TreeMap<LocalDateTime, BigDecimal>>> deviceMetricMap = new HashMap<>();
+
+        for (HistorysInnerResultVO result : historyDataList) {
+
+            if (result == null || !StringUtils.hasText(result.getDeviceuuid()) || CollectionUtils.isEmpty(result.getMetrics())) {
+
+                continue;
+
+            }
+
+            String deviceUuid = result.getDeviceuuid();
+
+            Map<String, TreeMap<LocalDateTime, BigDecimal>> metricMap =
+
+                    deviceMetricMap.computeIfAbsent(deviceUuid, key -> new HashMap<>());
+
+
+
+            for (MetricVO metric : result.getMetrics()) {
+
+                if (metric == null || !StringUtils.hasText(metric.getMetric()) || CollectionUtils.isEmpty(metric.getMetricItems())) {
+
+                    continue;
+
+                }
+
+                TreeMap<LocalDateTime, BigDecimal> timeValueMap =
+
+                        metricMap.computeIfAbsent(metric.getMetric(), key -> new TreeMap<>());
+
+                for (Map<String, Object> point : metric.getMetricItems()) {
+
+                    if (point == null || point.get("timestamp") == null || point.get("value") == null) {
+
+                        continue;
+
+                    }
+
+                    try {
+
+                        LocalDateTime timestamp = parseTimestamp(point.get("timestamp").toString());
+
+                        BigDecimal value = new BigDecimal(point.get("value").toString());
+
+                        timeValueMap.put(timestamp, value);
+
+                    } catch (Exception ignored) {
+
+                        // skip invalid point
+
+                    }
+
+                }
+
+            }
+
+        }
+
+        return deviceMetricMap;
+
+    }
+
+
+
+    private Map<String, Map<String, BigDecimal>> parseLatestResult(List<LastInnerResultVO> latestList) {
+
+        Map<String, Map<String, BigDecimal>> result = new HashMap<>();
+
+        for (LastInnerResultVO item : latestList) {
+
+            if (item == null || !StringUtils.hasText(item.getDeviceuuid()) || item.getMetrics() == null) {
+
+                continue;
+
+            }
+
+            Map<String, BigDecimal> metricValues = new HashMap<>();
+
+            item.getMetrics().forEach((metric, value) -> {
+
+                if (value == null) {
+
+                    return;
+
+                }
+
+                try {
+
+                    metricValues.put(metric, new BigDecimal(value.toString()).setScale(4, RoundingMode.HALF_UP));
+
+                } catch (Exception ignored) {
+
+                    // skip invalid value
+
+                }
+
+            });
+
+            result.put(item.getDeviceuuid(), metricValues);
+
+        }
+
+        return result;
+
+    }
+
+
+
+    private LocalDateTime parseTimestamp(String timestampStr) {
+
+        String normalized = timestampStr.trim();
+
+        if (normalized.endsWith(".0")) {
+
+            normalized = normalized.substring(0, normalized.length() - 2);
+
+        }
+
+        return LocalDateTime.parse(normalized, TSDB_FLEXIBLE_TIME_FORMAT);
+
+    }
+
+}
+
+

+ 1 - 1
service-vpp/service-vpp-biz/src/main/java/com/usky/vpp/service/vo/EnergyTodayVO.java

@@ -5,7 +5,7 @@ import lombok.Data;
 import java.math.BigDecimal;
 
 /**
- * 今日电能概况(模拟数据
+ * 今日电能概况(TSDB 实时汇总
  */
 @Data
 public class EnergyTodayVO {

+ 1 - 1
service-vpp/service-vpp-biz/src/main/java/com/usky/vpp/service/vo/ResourceBoardItemVO.java

@@ -5,7 +5,7 @@ import lombok.Data;
 import java.math.BigDecimal;
 
 /**
- * 资源看板列表项(含模拟实时运行数据)
+ * 资源看板列表项(含 TSDB 实时运行数据)
  */
 @Data
 public class ResourceBoardItemVO {