From b985e6140e7f395ead7dc55e41e6b16d97f9f02b Mon Sep 17 00:00:00 2001
From: yeg <1347747024@qq.com>
Date: 星期日, 30 八月 2026 14:44:30 +0800
Subject: [PATCH] 简化开发模拟器历史数据管理

---
 ruoyi-device/src/main/java/com/ruoyi/device/bts/BtsDevelopmentSimulator.java              |    1 
 ruoyi-device/src/main/java/com/ruoyi/device/simulator/InMemoryBtsSimulatorStateStore.java |    9 +++-
 ruoyi-admin/src/main/java/com/ruoyi/config/RedisBtsSimulatorStateStore.java               |   24 ++++++++++++
 ruoyi-device/src/main/java/com/ruoyi/device/simulator/BtsSimulatorStateStore.java         |    5 ++
 ruoyi-admin/src/test/java/com/ruoyi/config/RedisStateStoreTest.java                       |   11 +++--
 ruoyi-device/src/main/java/com/ruoyi/device/simulator/DevelopmentCollectorSimulation.java |    3 +
 ruoyi-device/src/test/java/com/ruoyi/device/contract/SimulatorPublicContractTest.java     |    8 ++--
 7 files changed, 49 insertions(+), 12 deletions(-)

diff --git a/ruoyi-admin/src/main/java/com/ruoyi/config/RedisBtsSimulatorStateStore.java b/ruoyi-admin/src/main/java/com/ruoyi/config/RedisBtsSimulatorStateStore.java
index f151ea6..796c15e 100644
--- a/ruoyi-admin/src/main/java/com/ruoyi/config/RedisBtsSimulatorStateStore.java
+++ b/ruoyi-admin/src/main/java/com/ruoyi/config/RedisBtsSimulatorStateStore.java
@@ -47,6 +47,29 @@
         return restoreState(stored);
     }
 
+    @Override
+    public SimulatorState resetHistoryForDemoStartup(SimulatorState state)
+    {
+        pendingEmbeddedHistory = List.of();
+        Set<Object> historyKeys = redisTemplate.opsForSet().members(HISTORY_KEYS_KEY);
+        if (historyKeys != null && !historyKeys.isEmpty()) redisTemplate.delete(historyKeys);
+        redisTemplate.delete(HISTORY_KEYS_KEY);
+
+        List<com.ruoyi.device.simulator.BtsSimulatorStateStore.CollectorState> collectors = state.collectors()
+                .stream()
+                .map(collector -> new com.ruoyi.device.simulator.BtsSimulatorStateStore.CollectorState(
+                        collector.collectorId(), collector.deviceCode(), collector.ip(), collector.mac(),
+                        collector.name(), collector.manufacturer(), collector.model(), collector.channelCount(),
+                        collector.online(), collector.sampleIntervalMs(), 1L,
+                        null, collector.channels(), List.of(), 0L,
+                        collector.historyFaults()))
+                .toList();
+        SimulatorState reset = new SimulatorState(state.version(), state.devices(), collectors,
+                state.nextDeviceId(), state.nextCollectorId(), state.tombstones());
+        redisTemplate.opsForValue().set(STATE_KEY, JSON.toJSONString(reset));
+        return reset;
+    }
+
     private SimulatorState restoreState(Object stored)
     {
         ParsedSimulatorState parsed = parseState(stored);
@@ -88,6 +111,7 @@
                         List<Object> records = new ArrayList<>(
                                 append.records().stream().map(JSON::toJSONString).toList());
                         typed.opsForList().rightPushAll(historyKey, records);
+                        typed.opsForList().trim(historyKey, -MAX_HISTORY_RECORDS, -1);
                         typed.opsForSet().add(HISTORY_KEYS_KEY, historyKey);
                     }
                 }
diff --git a/ruoyi-admin/src/test/java/com/ruoyi/config/RedisStateStoreTest.java b/ruoyi-admin/src/test/java/com/ruoyi/config/RedisStateStoreTest.java
index dc22f33..41523d4 100644
--- a/ruoyi-admin/src/test/java/com/ruoyi/config/RedisStateStoreTest.java
+++ b/ruoyi-admin/src/test/java/com/ruoyi/config/RedisStateStoreTest.java
@@ -125,7 +125,7 @@
     }
 
     @Test
-    void doesNotOverwriteDamagedEmbeddedHistoryBeforeValidation()
+    void clearsLegacyEmbeddedHistoryForDemoStartup() throws Exception
     {
         AtomicReference<Object> stored = new AtomicReference<>("{\"version\":1,\"devices\":[],"
                 + "\"collectors\":[{\"collectorId\":1,\"deviceCode\":\"dev-sim-collector-1\","
@@ -145,10 +145,11 @@
                 + "\"historyFaults\":{\"missingSequences\":[],\"outOfOrder\":false}}],"
                 + "\"nextDeviceId\":1,\"nextCollectorId\":2,\"tombstones\":[]}");
 
-        assertThrows(IllegalStateException.class,
-                () -> new BtsDevelopmentSimulator(0, store(stored)));
-
-        assertEquals(true, stored.get().toString().contains("\"history\":[{"));
+        try (BtsDevelopmentSimulator simulator = new BtsDevelopmentSimulator(0, store(stored)))
+        {
+            assertEquals(0L, simulator.snapshot().collectors().get(0).historyRecordCount());
+        }
+        assertEquals(false, stored.get().toString().contains("\"history\":[{"));
     }
 
     @Test
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 1af43b7..e66c092 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
@@ -62,6 +62,7 @@
     {
         this.stateStore = stateStore;
         state = stateStore.loadOrInitialize(defaultState());
+        state = stateStore.resetHistoryForDemoStartup(state);
         validateRestoredState();
         stateStore.completeValidatedRestore(state);
         simulator = ScriptedBtsTcpSimulator.responding(port, this::respond);
diff --git a/ruoyi-device/src/main/java/com/ruoyi/device/simulator/BtsSimulatorStateStore.java b/ruoyi-device/src/main/java/com/ruoyi/device/simulator/BtsSimulatorStateStore.java
index a950af0..0b03501 100644
--- a/ruoyi-device/src/main/java/com/ruoyi/device/simulator/BtsSimulatorStateStore.java
+++ b/ruoyi-device/src/main/java/com/ruoyi/device/simulator/BtsSimulatorStateStore.java
@@ -10,10 +10,15 @@
 {
     int CURRENT_VERSION = 1;
     int MAX_HISTORY_BATCH_SIZE = 1000;
+    /** 开发演示只保留有限历史,避免 Redis 随运行时间无限增长。 */
+    long MAX_HISTORY_RECORDS = 20_000L;
 
     /** 读取已有状态;仅在状态不存在时原子写入并返回默认状态。 */
     SimulatorState loadOrInitialize(SimulatorState defaultState);
 
+    /** 开发模拟器启动时清理上一轮演示历史;正式业务存储不需要实现该动作。 */
+    default SimulatorState resetHistoryForDemoStartup(SimulatorState state) { return state; }
+
     /** 完整保存下一版模拟器状态。 */
     void save(SimulatorState state);
 
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 c0fb1fd..504a83b 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
@@ -131,7 +131,8 @@
         CollectorState next = copy(current, current.ip(), current.channelCount(), current.online(),
                 current.sampleIntervalMs(),
                 sequence, lastSampledAt, current.channels(), List.copyOf(latest.values()),
-                current.historyRecordCount() + appendedRecords.size(),
+                Math.min(BtsSimulatorStateStore.MAX_HISTORY_RECORDS,
+                        current.historyRecordCount() + appendedRecords.size()),
                 current.historyFaults());
         return new CollectorAdvance(next, appendedRecords);
     }
diff --git a/ruoyi-device/src/main/java/com/ruoyi/device/simulator/InMemoryBtsSimulatorStateStore.java b/ruoyi-device/src/main/java/com/ruoyi/device/simulator/InMemoryBtsSimulatorStateStore.java
index 48e1263..2656282 100644
--- a/ruoyi-device/src/main/java/com/ruoyi/device/simulator/InMemoryBtsSimulatorStateStore.java
+++ b/ruoyi-device/src/main/java/com/ruoyi/device/simulator/InMemoryBtsSimulatorStateStore.java
@@ -41,8 +41,13 @@
     public synchronized void save(SimulatorState state, List<CollectorHistoryAppend> historyAppends)
     {
         for (CollectorHistoryAppend append : historyAppends)
-            collectorHistory.computeIfAbsent(append.collectorCode(), key -> new ArrayList<>())
-                    .addAll(append.records());
+        {
+            ArrayList<CollectorMeasurementRecord> history = collectorHistory.computeIfAbsent(
+                    append.collectorCode(), key -> new ArrayList<>());
+            history.addAll(append.records());
+            int overflow = history.size() - (int) MAX_HISTORY_RECORDS;
+            if (overflow > 0) history.subList(0, overflow).clear();
+        }
         this.state = state;
     }
 
diff --git a/ruoyi-device/src/test/java/com/ruoyi/device/contract/SimulatorPublicContractTest.java b/ruoyi-device/src/test/java/com/ruoyi/device/contract/SimulatorPublicContractTest.java
index df11a42..c9f9697 100644
--- a/ruoyi-device/src/test/java/com/ruoyi/device/contract/SimulatorPublicContractTest.java
+++ b/ruoyi-device/src/test/java/com/ruoyi/device/contract/SimulatorPublicContractTest.java
@@ -158,7 +158,7 @@
     }
 
     @Test
-    void collectorKeepsAppendOnlyHistoryBeyondPreviousDisplayLimit() throws Exception
+    void collectorKeepsOnlyBoundedDemoHistory() throws Exception
     {
         Instant startedAt = Instant.parse("2026-08-29T03:00:00Z");
         TrackingHistoryStore store = new TrackingHistoryStore();
@@ -171,8 +171,8 @@
 
             store.resetReadStats();
             var page = simulator.collectorHistory(1, 0, 1);
-            assertEquals(60_012, page.total());
-            assertEquals(1, page.records().get(0).sequenceId());
+            assertEquals(BtsSimulatorStateStore.MAX_HISTORY_RECORDS, page.total());
+            assertTrue(page.records().get(0).sequenceId() > 1);
             assertEquals(1, store.historyReads);
             assertEquals(1, store.recordsRead);
         }
@@ -182,7 +182,7 @@
         {
             assertTrue(store.historyReads > 1);
             assertEquals(1000, store.maximumRequestedLimit);
-            assertEquals(60_012, store.recordsRead);
+            assertEquals(BtsSimulatorStateStore.MAX_HISTORY_RECORDS, store.recordsRead);
         }
     }
 

--
Gitblit v1.7.1