Procházet zdrojové kódy

对接营服中心平台1

james před 5 dny
rodič
revize
b7c8d5499b

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

@@ -0,0 +1,14 @@
+package com.usky.vpp.service;
+
+/**
+ * 运管平台 UN 曲线数据报告(IntervalDataReport)定时任务
+ */
+public interface VppUnIntervalDataReportTaskService {
+
+    /**
+     * 向 UN 提交 IntervalDataReportRequest。
+     *
+     * @param tenantId 租户 ID
+     */
+    void intervalDataReportUn(Integer tenantId);
+}

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

@@ -0,0 +1,14 @@
+package com.usky.vpp.service;
+
+/**
+ * 运管平台 UN 注册定时任务
+ */
+public interface VppUnRegisterTaskService {
+
+    /**
+     * 按租户向 UN 执行 Token 刷新、加载 registrationID、注册/重新注册。
+     *
+     * @param tenantId 租户 ID
+     */
+    void registerUn(Integer tenantId);
+}

+ 44 - 0
service-vpp/service-vpp-biz/src/main/java/com/usky/vpp/service/impl/VppUnIntervalDataReportTaskServiceImpl.java

@@ -0,0 +1,44 @@
+package com.usky.vpp.service.impl;
+
+import com.usky.vpp.config.VppUnProperties;
+import com.usky.vpp.config.VppUnPropertiesRegistry;
+import com.usky.vpp.config.VppUnTenantContext;
+import com.usky.vpp.service.VppUnIntervalDataReportTaskService;
+import com.usky.vpp.service.VppUnReportService;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+
+@Service
+public class VppUnIntervalDataReportTaskServiceImpl implements VppUnIntervalDataReportTaskService {
+
+    private static final Logger log = LoggerFactory.getLogger(VppUnIntervalDataReportTaskServiceImpl.class);
+
+    @Autowired
+    private VppUnPropertiesRegistry propertiesRegistry;
+
+    @Autowired
+    private VppUnReportService reportService;
+
+    @Override
+    public void intervalDataReportUn(Integer tenantId) {
+        log.info("intervalDataReportUn start, tenantId={}", tenantId);
+        VppUnProperties properties = propertiesRegistry.get(tenantId);
+        if (properties == null) {
+            log.warn("intervalDataReportUn skipped: tenantId={} 未配置运管平台 UN 对接信息", tenantId);
+            return;
+        }
+        if (!properties.isOutboundActive()) {
+            log.info("intervalDataReportUn skipped: outbound not active, tenantId={}", tenantId);
+            return;
+        }
+        VppUnTenantContext.runWithTenant(tenantId, () -> {
+            try {
+                reportService.submitIntervalDataReport(null);
+            } catch (Exception ex) {
+                log.warn("IntervalDataReport 提交失败, tenantId={}: {}", tenantId, ex.getMessage());
+            }
+        });
+    }
+}

+ 68 - 0
service-vpp/service-vpp-biz/src/main/java/com/usky/vpp/service/impl/VppUnRegisterTaskServiceImpl.java

@@ -0,0 +1,68 @@
+package com.usky.vpp.service.impl;
+
+import com.usky.vpp.config.VppUnProperties;
+import com.usky.vpp.config.VppUnPropertiesRegistry;
+import com.usky.vpp.config.VppUnTenantContext;
+import com.usky.vpp.service.VppUnIntegrationService;
+import com.usky.vpp.service.VppUnRegisterTaskService;
+import com.usky.vpp.service.VppUnReportService;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+import org.springframework.util.StringUtils;
+
+@Service
+public class VppUnRegisterTaskServiceImpl implements VppUnRegisterTaskService {
+
+    private static final Logger log = LoggerFactory.getLogger(VppUnRegisterTaskServiceImpl.class);
+
+    @Autowired
+    private VppUnPropertiesRegistry propertiesRegistry;
+
+    @Autowired
+    private VppUnIntegrationService integrationService;
+
+    @Autowired
+    private VppUnReportService reportService;
+
+    @Override
+    public void registerUn(Integer tenantId) {
+        log.info("registerUn start, tenantId={}", tenantId);
+        VppUnProperties properties = propertiesRegistry.get(tenantId);
+        if (properties == null) {
+            log.warn("registerUn skipped: tenantId={} 未配置运管平台 UN 对接信息", tenantId);
+            return;
+        }
+        if (!properties.isOutboundActive()) {
+            log.info("registerUn skipped: outbound not active, tenantId={}", tenantId);
+            return;
+        }
+        VppUnTenantContext.runWithTenant(tenantId, () -> {
+            try {
+                integrationService.refreshToken();
+                integrationService.loadPersistedRegistrationId();
+                boolean reregister = StringUtils.hasText(properties.getRegistrationId());
+                if (reregister) {
+                    log.info("租户 {} 使用已持久化的 registrationID={} 向 UN 重新注册",
+                            tenantId, properties.getRegistrationId());
+                }
+                integrationService.register();
+                log.info("租户 {} {} UN 完成 registrationID={}",
+                        tenantId, reregister ? "重新注册" : "注册", properties.getRegistrationId());
+            } catch (Exception ex) {
+                log.warn("租户 {} UN 注册失败: {}", tenantId, ex.getMessage());
+            }
+            try {
+                reportService.submitRegisterReport();
+            } catch (Exception ex) {
+                log.warn("租户 {} RegisterReport元数据报告请求 提交失败: {}", tenantId, ex.getMessage());
+            }
+            try {
+                reportService.submitMomentDataReport();
+            } catch (Exception ex) {
+                log.warn("租户 {} MomentDataReport实时数据报告请求 提交失败: {}", tenantId, ex.getMessage());
+            }
+        });
+    }
+}

+ 196 - 0
service-vpp/service-vpp-biz/src/main/java/com/usky/vpp/util/VppUnIntervalDataHelper.java

@@ -0,0 +1,196 @@
+package com.usky.vpp.util;
+
+import com.usky.vpp.service.VppTsdbQueryService;
+
+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.util.ArrayList;
+import java.util.Collections;
+import java.util.LinkedHashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.TreeMap;
+
+/**
+ * IntervalDataReport 明日预测曲线(15 分钟整点,按量测点类型均值/累加)。
+ */
+public final class VppUnIntervalDataHelper {
+
+    public static final int SLOT_MINUTES = VppUnMomentDataHelper.WINDOW_MINUTES;
+
+    private static final DateTimeFormatter TIME_KEY_FMT = DateTimeFormatter.ofPattern("HH:mm");
+
+    private VppUnIntervalDataHelper() {
+    }
+
+    public static LocalDate resolveForecastDate() {
+        return LocalDate.now().plusDays(1);
+    }
+
+    public static List<LocalDateTime> buildDayQuarterHourSlots(LocalDate forecastDate) {
+        List<LocalDateTime> slots = new ArrayList<>();
+        if (forecastDate == null) {
+            return slots;
+        }
+        LocalDateTime cursor = forecastDate.atStartOfDay();
+        LocalDateTime dayEnd = forecastDate.plusDays(1).atStartOfDay();
+        while (cursor.isBefore(dayEnd)) {
+            slots.add(cursor);
+            cursor = cursor.plusMinutes(SLOT_MINUTES);
+        }
+        return slots;
+    }
+
+    /**
+     * 基于典型历史日预测 forecastDate 全天 96 个 15 分钟点。
+     */
+    public static List<Map<String, Object>> buildForecastCurveValues(VppTsdbQueryService tsdbQueryService,
+                                                                     List<String> deviceUuids,
+                                                                     List<LocalDate> referenceDates,
+                                                                     LocalDate forecastDate,
+                                                                     VppUnRegisterMetricDefinitions.RegisterMetric registerMetric) {
+        List<LocalDateTime> slots = buildDayQuarterHourSlots(forecastDate);
+        if (slots.isEmpty()) {
+            return Collections.emptyList();
+        }
+        if (tsdbQueryService == null || deviceUuids == null || deviceUuids.isEmpty()
+                || referenceDates == null || referenceDates.isEmpty() || registerMetric == null) {
+            return buildZeroCurve(slots);
+        }
+        List<String> tsdbMetrics = VppUnMomentDataHelper.resolveTsdbMetrics(registerMetric);
+        if (tsdbMetrics.isEmpty()) {
+            return buildZeroCurve(slots);
+        }
+        List<String> distinctUuids = new ArrayList<>(new LinkedHashSet<>(deviceUuids));
+        if (VppUnMomentDataHelper.resolveAggregationMode(registerMetric)
+                == VppUnMomentDataHelper.AggregationMode.AVERAGE) {
+            return buildAverageForecastCurve(tsdbQueryService, distinctUuids, referenceDates, slots, tsdbMetrics);
+        }
+        return buildAccumulativeForecastCurve(tsdbQueryService, distinctUuids, referenceDates, slots, tsdbMetrics);
+    }
+
+    private static List<Map<String, Object>> buildAverageForecastCurve(
+            VppTsdbQueryService tsdbQueryService,
+            List<String> deviceUuids,
+            List<LocalDate> referenceDates,
+            List<LocalDateTime> slots,
+            List<String> tsdbMetrics) {
+        Map<String, BigDecimal> avgByTime = tsdbQueryService.queryBaselineAvgByTimePoint(
+                deviceUuids,
+                referenceDates,
+                LocalTime.MIN,
+                LocalTime.of(23, 59, 59),
+                tsdbMetrics,
+                SLOT_MINUTES,
+                null);
+        List<Map<String, Object>> values = new ArrayList<>(slots.size());
+        for (LocalDateTime slot : slots) {
+            String timeKey = slot.format(TIME_KEY_FMT);
+            BigDecimal value = avgByTime != null ? avgByTime.get(timeKey) : null;
+            values.add(VppUnMessageBuilder.buildIntervalCurveValue(scale(value), slot, "good"));
+        }
+        return values;
+    }
+
+    private static List<Map<String, Object>> buildAccumulativeForecastCurve(
+            VppTsdbQueryService tsdbQueryService,
+            List<String> deviceUuids,
+            List<LocalDate> referenceDates,
+            List<LocalDateTime> slots,
+            List<String> tsdbMetrics) {
+        Map<String, List<BigDecimal>> samplesByTimeKey = new TreeMap<>();
+        for (LocalDate referenceDate : referenceDates) {
+            LocalDateTime dayStart = referenceDate.atStartOfDay();
+            LocalDateTime dayEnd = referenceDate.plusDays(1).atStartOfDay();
+            Map<String, Map<String, TreeMap<LocalDateTime, BigDecimal>>> history =
+                    tsdbQueryService.queryDeviceMetricHistory(deviceUuids, dayStart, dayEnd, tsdbMetrics);
+            if (history == null || history.isEmpty()) {
+                continue;
+            }
+            for (LocalDateTime slot : slots) {
+                LocalDateTime slotStart = LocalDateTime.of(referenceDate, slot.toLocalTime());
+                LocalDateTime slotEnd = slotStart.plusMinutes(SLOT_MINUTES);
+                LocalDateTime endExclusive = slotEnd.plusSeconds(1);
+                BigDecimal slotTotal = BigDecimal.ZERO;
+                for (String deviceUuid : deviceUuids) {
+                    Map<String, TreeMap<LocalDateTime, BigDecimal>> metricMap = history.get(deviceUuid);
+                    if (metricMap == null || metricMap.isEmpty()) {
+                        continue;
+                    }
+                    TreeMap<LocalDateTime, BigDecimal> series = pickSeries(metricMap, tsdbMetrics);
+                    BigDecimal usage = VppEnergyUsageHelper.calcUsage(series, slotStart, endExclusive);
+                    if (usage != null) {
+                        slotTotal = slotTotal.add(usage);
+                    }
+                }
+                samplesByTimeKey.computeIfAbsent(slot.format(TIME_KEY_FMT), key -> new ArrayList<>())
+                        .add(slotTotal);
+            }
+        }
+        List<Map<String, Object>> values = new ArrayList<>(slots.size());
+        for (LocalDateTime slot : slots) {
+            List<BigDecimal> samples = samplesByTimeKey.get(slot.format(TIME_KEY_FMT));
+            values.add(VppUnMessageBuilder.buildIntervalCurveValue(
+                    scale(average(samples)), slot, "good"));
+        }
+        return values;
+    }
+
+    private static List<Map<String, Object>> buildZeroCurve(List<LocalDateTime> slots) {
+        List<Map<String, Object>> values = new ArrayList<>(slots.size());
+        for (LocalDateTime slot : slots) {
+            values.add(VppUnMessageBuilder.buildIntervalCurveValue(BigDecimal.ZERO, slot, "good"));
+        }
+        return values;
+    }
+
+    private static TreeMap<LocalDateTime, BigDecimal> pickSeries(Map<String, TreeMap<LocalDateTime, BigDecimal>> metricMap,
+                                                                 List<String> preferredMetrics) {
+        if (metricMap == null || metricMap.isEmpty()) {
+            return null;
+        }
+        if (preferredMetrics != null) {
+            for (String metric : preferredMetrics) {
+                TreeMap<LocalDateTime, BigDecimal> series = metricMap.get(metric);
+                if (series != null && !series.isEmpty()) {
+                    return series;
+                }
+            }
+        }
+        for (TreeMap<LocalDateTime, BigDecimal> series : metricMap.values()) {
+            if (series != null && !series.isEmpty()) {
+                return series;
+            }
+        }
+        return null;
+    }
+
+    private static BigDecimal average(List<BigDecimal> values) {
+        if (values == null || values.isEmpty()) {
+            return BigDecimal.ZERO;
+        }
+        BigDecimal sum = BigDecimal.ZERO;
+        int count = 0;
+        for (BigDecimal value : values) {
+            if (value != null) {
+                sum = sum.add(value);
+                count++;
+            }
+        }
+        if (count == 0) {
+            return BigDecimal.ZERO;
+        }
+        return sum.divide(BigDecimal.valueOf(count), 6, RoundingMode.HALF_UP);
+    }
+
+    private static BigDecimal scale(BigDecimal value) {
+        if (value == null) {
+            return BigDecimal.ZERO.setScale(2, RoundingMode.HALF_UP);
+        }
+        return value.setScale(2, RoundingMode.HALF_UP);
+    }
+}

+ 170 - 0
service-vpp/service-vpp-biz/src/main/java/com/usky/vpp/util/VppUnMomentDataHelper.java

@@ -0,0 +1,170 @@
+package com.usky.vpp.util;
+
+import com.usky.vpp.constant.VppTsdbConstants;
+import com.usky.vpp.service.VppTsdbQueryService;
+
+import java.math.BigDecimal;
+import java.math.RoundingMode;
+import java.time.LocalDateTime;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.LinkedHashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.TreeMap;
+
+/**
+ * MomentDataReport pointData 聚合(按量测点类型选择均值或累加)。
+ */
+public final class VppUnMomentDataHelper {
+
+    public static final int WINDOW_MINUTES = 15;
+
+    public enum AggregationMode {
+        /** 窗口内时序均值,多设备再求和(功率、能力等瞬时值) */
+        AVERAGE,
+        /** 窗口内累计量增量(末值 − 首值),多设备再求和(电能 Wh) */
+        ACCUMULATIVE
+    }
+
+    /**
+     * 将参考时间对齐到 00/15/30/45 整分作为窗口结束时间。
+     * 例:15:17 → 15:15;15:15 → 15:15。
+     */
+    public static LocalDateTime resolveWindowEnd(LocalDateTime referenceTime) {
+        LocalDateTime base = referenceTime != null ? referenceTime : LocalDateTime.now();
+        int flooredMinute = (base.getMinute() / WINDOW_MINUTES) * WINDOW_MINUTES;
+        return base.withMinute(flooredMinute).withSecond(0).withNano(0);
+    }
+
+    public static LocalDateTime resolveWindowStart(LocalDateTime windowEnd) {
+        return windowEnd != null ? windowEnd.minusMinutes(WINDOW_MINUTES) : null;
+    }
+
+    private VppUnMomentDataHelper() {
+    }
+
+    public static List<String> resolveTsdbMetrics(VppUnRegisterMetricDefinitions.RegisterMetric metric) {
+        if (metric == null) {
+            return Collections.emptyList();
+        }
+        switch (metric.getMetricName()) {
+            case "AP":
+                return VppBaselineHelper.POWER_METRICS;
+            case "AP_E":
+                return Collections.singletonList(VppTsdbConstants.METRIC_EPP);
+            case "AP_PE":
+                return Arrays.asList(VppTsdbConstants.METRIC_EPN, VppTsdbConstants.METRIC_EDAY);
+            case "REGULATE_UP":
+            case "F_REGULATE_UP":
+                return VppTsdbConstants.UP_CAPACITY_METRICS;
+            case "REGULATE_DOWN":
+            case "F_REGULATE_DOWN":
+                return VppTsdbConstants.DOWN_CAPACITY_METRICS;
+            default:
+                return Collections.emptyList();
+        }
+    }
+
+    public static AggregationMode resolveAggregationMode(VppUnRegisterMetricDefinitions.RegisterMetric metric) {
+        if (metric != null && "Wh".equals(metric.getSymbol())) {
+            return AggregationMode.ACCUMULATIVE;
+        }
+        return AggregationMode.AVERAGE;
+    }
+
+    /**
+     * 按量测点类型聚合:瞬时值类取窗口均值后跨设备求和;累计电能类取窗口增量后跨设备求和。
+     */
+    public static BigDecimal aggregateMetricValue(VppTsdbQueryService tsdbQueryService,
+                                                  List<String> deviceUuids,
+                                                  LocalDateTime windowStart,
+                                                  LocalDateTime windowEnd,
+                                                  VppUnRegisterMetricDefinitions.RegisterMetric registerMetric) {
+        if (tsdbQueryService == null || deviceUuids == null || deviceUuids.isEmpty() || registerMetric == null) {
+            return BigDecimal.ZERO;
+        }
+        List<String> tsdbMetrics = resolveTsdbMetrics(registerMetric);
+        if (tsdbMetrics.isEmpty()) {
+            return BigDecimal.ZERO;
+        }
+        AggregationMode mode = resolveAggregationMode(registerMetric);
+        List<String> distinctUuids = new ArrayList<>(new LinkedHashSet<>(deviceUuids));
+        Map<String, Map<String, TreeMap<LocalDateTime, BigDecimal>>> history =
+                tsdbQueryService.queryDeviceMetricHistory(distinctUuids, windowStart, windowEnd, tsdbMetrics);
+        if (history == null || history.isEmpty()) {
+            return BigDecimal.ZERO;
+        }
+        BigDecimal total = BigDecimal.ZERO;
+        for (String deviceUuid : distinctUuids) {
+            Map<String, TreeMap<LocalDateTime, BigDecimal>> metricMap = history.get(deviceUuid);
+            if (metricMap == null || metricMap.isEmpty()) {
+                continue;
+            }
+            TreeMap<LocalDateTime, BigDecimal> series = pickSeries(metricMap, tsdbMetrics);
+            if (series == null || series.isEmpty()) {
+                continue;
+            }
+            BigDecimal deviceValue = aggregateDeviceSeries(series, windowStart, windowEnd, mode);
+            if (deviceValue != null) {
+                total = total.add(deviceValue);
+            }
+        }
+        return scale(total);
+    }
+
+    private static BigDecimal aggregateDeviceSeries(TreeMap<LocalDateTime, BigDecimal> series,
+                                                    LocalDateTime windowStart,
+                                                    LocalDateTime windowEnd,
+                                                    AggregationMode mode) {
+        if (mode == AggregationMode.ACCUMULATIVE) {
+            LocalDateTime endExclusive = windowEnd != null ? windowEnd.plusSeconds(1) : null;
+            return VppEnergyUsageHelper.calcUsage(series, windowStart, endExclusive);
+        }
+        return average(series.values());
+    }
+
+    private static TreeMap<LocalDateTime, BigDecimal> pickSeries(Map<String, TreeMap<LocalDateTime, BigDecimal>> metricMap,
+                                                                 List<String> preferredMetrics) {
+        if (metricMap == null || metricMap.isEmpty()) {
+            return null;
+        }
+        if (preferredMetrics != null) {
+            for (String metric : preferredMetrics) {
+                TreeMap<LocalDateTime, BigDecimal> series = metricMap.get(metric);
+                if (series != null && !series.isEmpty()) {
+                    return series;
+                }
+            }
+        }
+        for (TreeMap<LocalDateTime, BigDecimal> series : metricMap.values()) {
+            if (series != null && !series.isEmpty()) {
+                return series;
+            }
+        }
+        return null;
+    }
+
+    private static BigDecimal average(Iterable<BigDecimal> values) {
+        BigDecimal sum = BigDecimal.ZERO;
+        int count = 0;
+        for (BigDecimal value : values) {
+            if (value != null) {
+                sum = sum.add(value);
+                count++;
+            }
+        }
+        if (count == 0) {
+            return BigDecimal.ZERO;
+        }
+        return sum.divide(BigDecimal.valueOf(count), 6, RoundingMode.HALF_UP);
+    }
+
+    private static BigDecimal scale(BigDecimal value) {
+        if (value == null) {
+            return BigDecimal.ZERO.setScale(2, RoundingMode.HALF_UP);
+        }
+        return value.setScale(2, RoundingMode.HALF_UP);
+    }
+}

+ 61 - 0
service-vpp/service-vpp-biz/src/main/java/com/usky/vpp/util/VppUnRegisterMetricDefinitions.java

@@ -0,0 +1,61 @@
+package com.usky.vpp.util;
+
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+
+/**
+ * 虚拟电厂 RegisterReport 量测点定义(DL/T 1867—2024 表31 MetricName)。
+ */
+public final class VppUnRegisterMetricDefinitions {
+
+    private static final String MULTIPLIER_K = "k";
+    private static final String MULTIPLIER_NONE = "x-notApplicable";
+
+    public static final class RegisterMetric {
+        private final String metricName;
+        private final String multiplier;
+        private final String symbol;
+
+        public RegisterMetric(String metricName, String multiplier, String symbol) {
+            this.metricName = metricName;
+            this.multiplier = multiplier;
+            this.symbol = symbol;
+        }
+
+        public String getMetricName() {
+            return metricName;
+        }
+
+        public String getMultiplier() {
+            return multiplier;
+        }
+
+        public String getSymbol() {
+            return symbol;
+        }
+    }
+
+    /** 站点/资源点注册的有功功率量测点(kW)。 */
+    public static final RegisterMetric ACTIVE_POWER = new RegisterMetric("AP", MULTIPLIER_K, "W");
+
+    /** 虚拟电厂需注册的 13 项量测点,resourceID 统一为 dnID。 */
+    public static final List<RegisterMetric> VPP_REGISTER_METRICS = Collections.unmodifiableList(Arrays.asList(
+            ACTIVE_POWER,
+            new RegisterMetric("AP_E", MULTIPLIER_K, "Wh"),
+            new RegisterMetric("AP_PE", MULTIPLIER_K, "Wh"),
+            new RegisterMetric("REGULATE_UP", MULTIPLIER_K, "W"),
+            new RegisterMetric("REGULATE_DOWN", MULTIPLIER_K, "W"),
+            new RegisterMetric("RESPONSE_TIME", MULTIPLIER_NONE, "s"),
+            new RegisterMetric("CONTROL_RATE", MULTIPLIER_K, "W/min"),
+            new RegisterMetric("DURATION", MULTIPLIER_NONE, "min"),
+            new RegisterMetric("F_REGULATE_UP", MULTIPLIER_K, "W"),
+            new RegisterMetric("F_REGULATE_DOWN", MULTIPLIER_K, "W"),
+            new RegisterMetric("F_RESPONSE_TIME", MULTIPLIER_NONE, "s"),
+            new RegisterMetric("F_CONTROL_RATE", MULTIPLIER_K, "W/min"),
+            new RegisterMetric("F_DURATION", MULTIPLIER_NONE, "min")
+    ));
+
+    private VppUnRegisterMetricDefinitions() {
+    }
+}

+ 98 - 0
service-vpp/service-vpp-biz/src/main/java/com/usky/vpp/util/VppUnRegisterReportIndex.java

@@ -0,0 +1,98 @@
+package com.usky.vpp.util;
+
+import com.usky.vpp.config.VppUnProperties;
+import com.usky.vpp.domain.VppResourcePoint;
+import com.usky.vpp.domain.VppSite;
+import org.springframework.util.StringUtils;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * RegisterReport 量测点 rID 顺序索引(与 MetaDataReport 注册顺序一致)。
+ */
+public final class VppUnRegisterReportIndex {
+
+    public enum Scope {
+        VPP, SITE, RESOURCE
+    }
+
+    public static final class Entry {
+        private final int rid;
+        private final VppUnRegisterMetricDefinitions.RegisterMetric metric;
+        private final Scope scope;
+        private final VppSite site;
+        private final VppResourcePoint resource;
+
+        public Entry(int rid,
+                     VppUnRegisterMetricDefinitions.RegisterMetric metric,
+                     Scope scope,
+                     VppSite site,
+                     VppResourcePoint resource) {
+            this.rid = rid;
+            this.metric = metric;
+            this.scope = scope;
+            this.site = site;
+            this.resource = resource;
+        }
+
+        public int getRid() {
+            return rid;
+        }
+
+        public VppUnRegisterMetricDefinitions.RegisterMetric getMetric() {
+            return metric;
+        }
+
+        public Scope getScope() {
+            return scope;
+        }
+
+        public VppSite getSite() {
+            return site;
+        }
+
+        public VppResourcePoint getResource() {
+            return resource;
+        }
+    }
+
+    public static List<Entry> buildEntries(VppUnProperties properties,
+                                           List<VppSite> sites,
+                                           List<VppResourcePoint> resources,
+                                           Map<Long, String> deviceUnResourceIdMap) {
+        List<Entry> entries = new ArrayList<>();
+        int rid = 0;
+        for (VppUnRegisterMetricDefinitions.RegisterMetric metric
+                : VppUnRegisterMetricDefinitions.VPP_REGISTER_METRICS) {
+            entries.add(new Entry(rid++, metric, Scope.VPP, null, null));
+        }
+        if (sites != null) {
+            for (VppSite site : sites) {
+                if (site == null || !StringUtils.hasText(site.getUnResourceId())) {
+                    continue;
+                }
+                entries.add(new Entry(rid++, VppUnRegisterMetricDefinitions.ACTIVE_POWER,
+                        Scope.SITE, site, null));
+            }
+        }
+        if (resources != null && deviceUnResourceIdMap != null) {
+            for (VppResourcePoint resource : resources) {
+                if (resource == null || resource.getDeviceId() == null) {
+                    continue;
+                }
+                String resourceId = deviceUnResourceIdMap.get(resource.getDeviceId());
+                if (!StringUtils.hasText(resourceId)) {
+                    continue;
+                }
+                entries.add(new Entry(rid++, VppUnRegisterMetricDefinitions.ACTIVE_POWER,
+                        Scope.RESOURCE, null, resource));
+            }
+        }
+        return entries;
+    }
+
+    private VppUnRegisterReportIndex() {
+    }
+}