From 0162f3730ca2e9644322a90450728b63243d40d0 Mon Sep 17 00:00:00 2001
From: yeg <1347747024@qq.com>
Date: 星期六, 29 八月 2026 13:36:20 +0800
Subject: [PATCH] 修正采集器历史与实时快照模型
---
ruoyi-device/src/main/java/com/ruoyi/device/simulator/DevelopmentCollectorSimulation.java | 145 ++++++++++++++++++++++++++++++++++++++++++------
1 files changed, 127 insertions(+), 18 deletions(-)
diff --git a/ruoyi-device/src/main/java/com/ruoyi/device/simulator/DevelopmentCollectorSimulation.java b/ruoyi-device/src/main/java/com/ruoyi/device/simulator/DevelopmentCollectorSimulation.java
index c1092c5..369172a 100644
--- a/ruoyi-device/src/main/java/com/ruoyi/device/simulator/DevelopmentCollectorSimulation.java
+++ b/ruoyi-device/src/main/java/com/ruoyi/device/simulator/DevelopmentCollectorSimulation.java
@@ -1,10 +1,12 @@
package com.ruoyi.device.simulator;
import java.math.BigDecimal;
-import java.time.Duration;
import java.time.Instant;
+import java.time.LocalDateTime;
import java.time.ZoneId;
import java.time.format.DateTimeFormatter;
+import java.time.format.DateTimeParseException;
+import java.time.temporal.ChronoUnit;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.LinkedHashMap;
@@ -15,6 +17,7 @@
import com.ruoyi.device.simulator.BtsSimulatorStateStore.CollectorHistoryFaults;
import com.ruoyi.device.simulator.BtsSimulatorStateStore.CollectorMeasurementRecord;
import com.ruoyi.device.simulator.BtsSimulatorStateStore.CollectorMetricState;
+import com.ruoyi.device.simulator.BtsSimulatorStateStore.CollectorRealtimeMeasurement;
import com.ruoyi.device.simulator.BtsSimulatorStateStore.CollectorState;
/** 实现第一版开发采集器的独立采样与故障注入合同。 */
@@ -24,7 +27,6 @@
public static final int MIN_SAMPLE_INTERVAL_MS = 100;
public static final int MAX_SAMPLE_INTERVAL_MS = 60000;
public static final int MAX_HISTORY_PAGE_SIZE = 1000;
- private static final int MAX_HISTORY_RECORDS = 50000;
private static final int MAX_SAMPLES_PER_ADVANCE = 1000;
private static final ZoneId DEVICE_ZONE = ZoneId.of("Asia/Hong_Kong");
private static final DateTimeFormatter DEVICE_TIME_FORMATTER =
@@ -56,8 +58,10 @@
for (int index = 0; index < channelCount; index++)
channels.add(index < current.channels().size() ? current.channels().get(index) : defaults.get(index));
Instant lastSampledAt = online == current.online() ? current.lastSampledAt() : changedAt;
+ List<CollectorRealtimeMeasurement> latestMeasurements = retainConfiguredMeasurements(
+ current.latestMeasurements(), channels);
return copy(current, ip, channelCount, online, current.sampleIntervalMs(), current.nextSequence(),
- lastSampledAt, channels, current.history(), current.historyFaults());
+ lastSampledAt, channels, latestMeasurements, current.history(), current.historyFaults());
}
/** 更新采样频率和历史下载故障;测量原始历史保持只追加。 */
@@ -67,7 +71,8 @@
validateSampleInterval(sampleIntervalMs);
validateFaults(historyFaults);
return copy(current, current.ip(), current.channelCount(), current.online(), sampleIntervalMs,
- current.nextSequence(), current.lastSampledAt(), current.channels(), current.history(), historyFaults);
+ current.nextSequence(), current.lastSampledAt(), current.channels(), current.latestMeasurements(),
+ current.history(), historyFaults);
}
/** 替换一个演示通道的指标配置;动态添加项与固定指标共用指标编码合同。 */
@@ -78,8 +83,11 @@
int channelIndex = parseChannelIndex(channelCode, current.channelCount());
List<CollectorChannelState> channels = new ArrayList<>(current.channels());
channels.set(channelIndex, new CollectorChannelState(channelCode(channelIndex + 1), metrics));
+ List<CollectorRealtimeMeasurement> latestMeasurements = retainConfiguredMeasurements(
+ current.latestMeasurements(), channels);
return copy(current, current.ip(), current.channelCount(), current.online(), current.sampleIntervalMs(),
- current.nextSequence(), current.lastSampledAt(), channels, current.history(), current.historyFaults());
+ current.nextSequence(), current.lastSampledAt(), channels, latestMeasurements,
+ current.history(), current.historyFaults());
}
/** 按采集器自身频率推进采样;离线期间不生成记录,恢复后从下一序号继续。 */
@@ -92,6 +100,9 @@
if (sampleAt.isAfter(now)) return current;
List<CollectorMeasurementRecord> history = new ArrayList<>(current.history());
+ Map<String, CollectorRealtimeMeasurement> latest = new LinkedHashMap<>();
+ for (CollectorRealtimeMeasurement measurement : current.latestMeasurements())
+ latest.put(measurementKey(measurement.channelCode(), measurement.metricCode()), measurement);
long sequence = current.nextSequence();
int generated = 0;
Instant lastSampledAt = current.lastSampledAt();
@@ -101,10 +112,16 @@
{
for (CollectorMetricState metric : channel.metrics())
{
- history.add(new CollectorMeasurementRecord("dev-sim", current.deviceCode(),
+ CollectorMeasurementRecord record = new CollectorMeasurementRecord("dev-sim", current.deviceCode(),
channel.channelCode(), sequence, sampleAt,
DEVICE_TIME_FORMATTER.format(sampleAt.atZone(DEVICE_ZONE)), now,
- metric.metricCode(), metric.value(), metric.unit()));
+ metric.metricCode(), metric.value(), metric.unit());
+ history.add(record);
+ latest.put(measurementKey(record.channelCode(), record.metricCode()),
+ new CollectorRealtimeMeasurement(record.adapterCode(), record.collectorCode(),
+ record.channelCode(), record.sequenceId(), record.deviceTime(),
+ record.deviceTimeRaw(), record.serverReceivedAt(), record.metricCode(),
+ record.value(), record.unit()));
}
}
lastSampledAt = sampleAt;
@@ -112,20 +129,16 @@
generated++;
sampleAt = sampleAt.plusMillis(current.sampleIntervalMs());
}
- if (history.size() > MAX_HISTORY_RECORDS)
- history = new ArrayList<>(history.subList(history.size() - MAX_HISTORY_RECORDS, history.size()));
return copy(current, current.ip(), current.channelCount(), current.online(), current.sampleIntervalMs(),
- sequence, lastSampledAt, current.channels(), history, current.historyFaults());
+ sequence, lastSampledAt, current.channels(), List.copyOf(latest.values()), history,
+ current.historyFaults());
}
- /** 返回每个通道和指标的最新实时快照,不将快照写入另一条历史路径。 */
+ /** 返回独立保存的最新实时快照,不从原始历史反推当前状态。 */
public static CollectorRealtimeSnapshot realtime(CollectorState current)
{
- Map<String, CollectorMeasurementRecord> latest = new LinkedHashMap<>();
- for (CollectorMeasurementRecord record : current.history())
- latest.put(record.channelCode() + "\u0000" + record.metricCode(), record);
return new CollectorRealtimeSnapshot(current.deviceCode(), current.online(), current.sampleIntervalMs(),
- List.copyOf(latest.values()));
+ current.latestMeasurements());
}
/** 对只追加原始模拟历史应用可重复的下载故障视图,再按偏移量分页。 */
@@ -155,7 +168,102 @@
throw new IllegalStateException("Invalid collector channel identity in Redis");
validateMetrics(channel.metrics());
}
+ validateLatestMeasurements(state);
+ validateHistory(state);
validateFaults(state.historyFaults());
+ }
+
+ private static void validateLatestMeasurements(CollectorState state)
+ {
+ Set<String> configured = configuredMeasurementKeys(state.channels());
+ Set<String> identities = new java.util.HashSet<>();
+ for (CollectorRealtimeMeasurement measurement : state.latestMeasurements())
+ {
+ String key = measurementKey(measurement.channelCode(), measurement.metricCode());
+ if (!validMeasurement(state, measurement.adapterCode(), measurement.collectorCode(),
+ measurement.channelCode(), measurement.sequenceId(), measurement.deviceTime(),
+ measurement.deviceTimeRaw(), measurement.serverReceivedAt(), measurement.metricCode(),
+ measurement.value(), measurement.unit()) || !configured.contains(key) || !identities.add(key)
+ || measurement.sequenceId() >= state.nextSequence())
+ throw new IllegalStateException("Invalid collector realtime state in Redis");
+ }
+ }
+
+ private static void validateHistory(CollectorState state)
+ {
+ long maximumSequence = 0;
+ long previousSequence = 0;
+ Set<String> identities = new java.util.HashSet<>();
+ for (CollectorMeasurementRecord record : state.history())
+ {
+ String identity = record.sequenceId() + "\u0000" + record.channelCode() + "\u0000" + record.metricCode();
+ if (!validMeasurement(state, record.adapterCode(), record.collectorCode(), record.channelCode(),
+ record.sequenceId(), record.deviceTime(), record.deviceTimeRaw(), record.serverReceivedAt(),
+ record.metricCode(), record.value(), record.unit()) || !identities.add(identity)
+ || record.sequenceId() < previousSequence)
+ throw new IllegalStateException("Invalid collector history state in Redis");
+ maximumSequence = Math.max(maximumSequence, record.sequenceId());
+ previousSequence = record.sequenceId();
+ }
+ if (state.nextSequence() <= maximumSequence)
+ throw new IllegalStateException("Invalid collector history sequence in Redis");
+ }
+
+ private static boolean validMeasurement(CollectorState state, String adapterCode, String collectorCode,
+ String channelCode, long sequenceId, Instant deviceTime, String deviceTimeRaw,
+ Instant serverReceivedAt, String metricCode, BigDecimal value, String unit)
+ {
+ return "dev-sim".equals(adapterCode) && state.deviceCode().equals(collectorCode)
+ && validHistoricalChannelCode(channelCode)
+ && sequenceId > 0 && deviceTime != null && deviceTimeRaw != null && !deviceTimeRaw.isBlank()
+ && validDeviceTime(deviceTime, deviceTimeRaw) && serverReceivedAt != null
+ && !serverReceivedAt.isBefore(deviceTime) && metricCode != null && !metricCode.isBlank()
+ && metricCode.equals(metricCode.trim()) && value != null && unit != null && !unit.isBlank()
+ && unit.equals(unit.trim());
+ }
+
+ private static boolean validHistoricalChannelCode(String channelCode)
+ {
+ if (channelCode == null || !channelCode.matches("S-\\d{2}")) return false;
+ int index = Integer.parseInt(channelCode.substring(2));
+ return index >= 1 && channelCode(index).equals(channelCode);
+ }
+
+ private static boolean validDeviceTime(Instant deviceTime, String deviceTimeRaw)
+ {
+ try
+ {
+ Instant parsed = LocalDateTime.parse(deviceTimeRaw, DEVICE_TIME_FORMATTER)
+ .atZone(DEVICE_ZONE).toInstant();
+ return parsed.equals(deviceTime.truncatedTo(ChronoUnit.MILLIS));
+ }
+ catch (DateTimeParseException exception)
+ {
+ return false;
+ }
+ }
+
+ private static List<CollectorRealtimeMeasurement> retainConfiguredMeasurements(
+ List<CollectorRealtimeMeasurement> measurements, List<CollectorChannelState> channels)
+ {
+ Set<String> configured = configuredMeasurementKeys(channels);
+ return measurements.stream()
+ .filter(item -> configured.contains(measurementKey(item.channelCode(), item.metricCode())))
+ .toList();
+ }
+
+ private static Set<String> configuredMeasurementKeys(List<CollectorChannelState> channels)
+ {
+ Set<String> configured = new java.util.HashSet<>();
+ for (CollectorChannelState channel : channels)
+ for (CollectorMetricState metric : channel.metrics())
+ configured.add(measurementKey(channel.channelCode(), metric.metricCode()));
+ return configured;
+ }
+
+ private static String measurementKey(String channelCode, String metricCode)
+ {
+ return channelCode + "\u0000" + metricCode;
}
private static List<CollectorMeasurementRecord> faultedHistory(CollectorState current)
@@ -183,12 +291,13 @@
private static CollectorState copy(CollectorState current, String ip, int channelCount, boolean online,
int sampleIntervalMs, long nextSequence, Instant lastSampledAt,
- List<CollectorChannelState> channels, List<CollectorMeasurementRecord> history,
+ List<CollectorChannelState> channels, List<CollectorRealtimeMeasurement> latestMeasurements,
+ List<CollectorMeasurementRecord> history,
CollectorHistoryFaults faults)
{
return new CollectorState(current.collectorId(), current.deviceCode(), ip.trim(), current.mac(),
current.name(), current.manufacturer(), current.model(), channelCount, online, sampleIntervalMs,
- nextSequence, lastSampledAt, channels, history, faults);
+ nextSequence, lastSampledAt, channels, latestMeasurements, history, faults);
}
private static void validateSampleInterval(int sampleIntervalMs)
@@ -236,7 +345,7 @@
}
public record CollectorRealtimeSnapshot(String collectorCode, boolean online, int sampleIntervalMs,
- List<CollectorMeasurementRecord> measurements)
+ List<CollectorRealtimeMeasurement> measurements)
{
public CollectorRealtimeSnapshot { measurements = List.copyOf(measurements); }
}
--
Gitblit v1.7.1