From 370ef4f5e663fdfc1d19d2774dfc4ec1ed0af60e Mon Sep 17 00:00:00 2001
From: yeg <1347747024@qq.com>
Date: 星期六, 29 八月 2026 14:57:48 +0800
Subject: [PATCH] 修复采集器历史状态持续序列化错误

---
 ruoyi-device/src/main/java/com/ruoyi/device/bts/BtsDevelopmentSimulator.java |   45 +++++++++++++++++++++++++++++++++------------
 1 files changed, 33 insertions(+), 12 deletions(-)

diff --git a/ruoyi-device/src/main/java/com/ruoyi/device/bts/BtsDevelopmentSimulator.java b/ruoyi-device/src/main/java/com/ruoyi/device/bts/BtsDevelopmentSimulator.java
index 1663a40..5548804 100644
--- a/ruoyi-device/src/main/java/com/ruoyi/device/bts/BtsDevelopmentSimulator.java
+++ b/ruoyi-device/src/main/java/com/ruoyi/device/bts/BtsDevelopmentSimulator.java
@@ -13,6 +13,7 @@
 import com.ruoyi.device.simulator.BtsSimulatorStateStore.ChannelState;
 import com.ruoyi.device.simulator.BtsSimulatorStateStore.ChannelTombstone;
 import com.ruoyi.device.simulator.BtsSimulatorStateStore.CollectorChannelState;
+import com.ruoyi.device.simulator.BtsSimulatorStateStore.CollectorHistoryAppend;
 import com.ruoyi.device.simulator.BtsSimulatorStateStore.CollectorHistoryFaults;
 import com.ruoyi.device.simulator.BtsSimulatorStateStore.CollectorMeasurementRecord;
 import com.ruoyi.device.simulator.BtsSimulatorStateStore.CollectorMetricState;
@@ -23,6 +24,7 @@
 import com.ruoyi.device.simulator.BtsSimulatorStateStore.SimulatorState;
 import com.ruoyi.device.simulator.InMemoryBtsSimulatorStateStore;
 import com.ruoyi.device.simulator.DevelopmentCollectorSimulation;
+import com.ruoyi.device.simulator.DevelopmentCollectorSimulation.CollectorAdvance;
 import com.ruoyi.device.simulator.DevelopmentCollectorSimulation.CollectorHistoryPage;
 import com.ruoyi.device.simulator.DevelopmentCollectorSimulation.CollectorRealtimeSnapshot;
 
@@ -58,6 +60,7 @@
         this.stateStore = stateStore;
         state = stateStore.loadOrInitialize(defaultState());
         validateRestoredState();
+        stateStore.completeValidatedRestore(state);
         simulator = ScriptedBtsTcpSimulator.responding(port, this::respond);
     }
 
@@ -155,11 +158,13 @@
     /** 删除指定开发采集器,不影响检测柜 TCP 发现结果。 */
     public synchronized void deleteCollector(int collectorId)
     {
-        requireCollector(collectorId);
+        CollectorState collector = requireCollector(collectorId);
         List<CollectorState> collectors = new ArrayList<>(state.collectors());
         collectors.removeIf(item -> item.collectorId() == collectorId);
-        persist(new SimulatorState(state.version(), state.devices(), collectors, state.nextDeviceId(),
-                state.nextCollectorId(), state.tombstones()));
+        SimulatorState next = new SimulatorState(state.version(), state.devices(), collectors, state.nextDeviceId(),
+                state.nextCollectorId(), state.tombstones());
+        stateStore.saveAndDeleteCollectorHistory(next, collector.deviceCode());
+        this.state = next;
     }
 
     /** 配置采集器独立采样频率和历史下载故障场景。 */
@@ -181,12 +186,18 @@
     /** 按每台采集器自己的频率推进实时和历史采样。 */
     public synchronized void advanceCollectors(Instant now)
     {
-        List<CollectorState> collectors = state.collectors().stream()
-                .map(collector -> DevelopmentCollectorSimulation.advance(collector, now))
-                .toList();
-        if (!collectors.equals(state.collectors()))
+        List<CollectorState> collectors = new ArrayList<>(state.collectors().size());
+        List<CollectorHistoryAppend> historyAppends = new ArrayList<>();
+        for (CollectorState collector : state.collectors())
+        {
+            CollectorAdvance advance = DevelopmentCollectorSimulation.advance(collector, now);
+            collectors.add(advance.state());
+            if (!advance.appendedRecords().isEmpty())
+                historyAppends.add(new CollectorHistoryAppend(collector.deviceCode(), advance.appendedRecords()));
+        }
+        if (!historyAppends.isEmpty())
             persist(new SimulatorState(state.version(), state.devices(), collectors, state.nextDeviceId(),
-                    state.nextCollectorId(), state.tombstones()));
+                    state.nextCollectorId(), state.tombstones()), historyAppends);
     }
 
     /** 返回采集器最新实时快照;快照与分页历史保持不同读取路径。 */
@@ -198,7 +209,9 @@
     /** 分页下载采集器模拟历史,并应用已配置的重复、冲突、乱序或缺失场景。 */
     public synchronized CollectorHistoryPage collectorHistory(int collectorId, long offset, int limit)
     {
-        return DevelopmentCollectorSimulation.history(requireCollector(collectorId), offset, limit);
+        CollectorState collector = requireCollector(collectorId);
+        return DevelopmentCollectorSimulation.history(collector,
+                stateStore.loadCollectorHistory(collector.deviceCode()), offset, limit);
     }
 
     /** 返回控制页需要的运行状态快照。 */
@@ -215,7 +228,7 @@
                 .map(collector -> new CollectorSnapshot(collector.collectorId(), collector.deviceCode(),
                         collector.ip(), collector.mac(), collector.name(), collector.manufacturer(),
                         collector.model(), collector.channelCount(), collector.online(), collector.sampleIntervalMs(),
-                        collector.channels(), collector.historyFaults(), collector.history().size(),
+                        collector.channels(), collector.historyFaults(), collector.historyRecordCount(),
                         DevelopmentCollectorSimulation.realtime(collector).measurements()))
                 .toList();
         return new StateSnapshot(running, HOST + ":" + port(), deviceSnapshots, collectorSnapshots,
@@ -452,6 +465,13 @@
         state = nextState;
     }
 
+    private void persist(SimulatorState nextState, List<CollectorHistoryAppend> historyAppends)
+    {
+        // 主状态与原始历史批次必须一起成功,内存序号才能向前推进。
+        stateStore.save(nextState, historyAppends);
+        state = nextState;
+    }
+
     private String simulatorMac(int devtype, int devid)
     {
         return String.format("02:44:%02X:%02X:%02X:%02X",
@@ -498,7 +518,8 @@
                     || !isIpv4(collector.ip()) || !ips.add(collector.ip())
                     || collector.channelCount() < 1 || collector.channelCount() > MAX_CHANNEL_COUNT)
                 throw new IllegalStateException("Invalid BTS simulator collector state in Redis");
-            DevelopmentCollectorSimulation.validate(collector);
+            DevelopmentCollectorSimulation.validate(collector,
+                    stateStore.loadCollectorHistory(collector.deviceCode()));
             maximumCollectorId = Math.max(maximumCollectorId, collector.collectorId());
         }
         java.util.Set<String> tombstoneIds = new java.util.HashSet<>();
@@ -529,7 +550,7 @@
 
     public record CollectorSnapshot(int collectorId, String deviceCode, String ip, String mac, String name,
             String manufacturer, String model, int channelCount, boolean online, int sampleIntervalMs,
-            List<CollectorChannelState> channels, CollectorHistoryFaults historyFaults, int historyRecordCount,
+            List<CollectorChannelState> channels, CollectorHistoryFaults historyFaults, long historyRecordCount,
             List<CollectorRealtimeMeasurement> latestMeasurements)
     {
         public CollectorSnapshot

--
Gitblit v1.7.1