From 68156d14934ffb7d7b6e0dca63d3dcf3ed43c995 Mon Sep 17 00:00:00 2001
From: yeg <1347747024@qq.com>
Date: 星期六, 05 九月 2026 18:01:40 +0800
Subject: [PATCH] 修复离线设备无法发现和登记的问题

---
 battery-business/src/main/java/com/battery/business/service/impl/TCollectorServiceImpl.java        |    5 +-
 battery-business/src/main/java/com/battery/business/service/impl/TDetectionCabinetServiceImpl.java |   12 ++---
 ruoyi-admin/src/main/java/com/battery/business/runtime/DevelopmentCollectorDiscoveryService.java   |    7 +--
 ruoyi-admin/src/test/java/com/battery/business/service/impl/TCollectorServiceImplTest.java         |   12 ++++++
 ruoyi-admin/src/test/java/com/battery/business/service/impl/CollectorDiscoveryServiceImplTest.java |    7 ++-
 ruoyi-admin/src/test/java/com/battery/business/service/impl/TDetectionCabinetServiceImplTest.java  |   19 +++++++++
 6 files changed, 45 insertions(+), 17 deletions(-)

diff --git a/battery-business/src/main/java/com/battery/business/service/impl/TCollectorServiceImpl.java b/battery-business/src/main/java/com/battery/business/service/impl/TCollectorServiceImpl.java
index c3603a8..95fd84a 100644
--- a/battery-business/src/main/java/com/battery/business/service/impl/TCollectorServiceImpl.java
+++ b/battery-business/src/main/java/com/battery/business/service/impl/TCollectorServiceImpl.java
@@ -98,8 +98,7 @@
         if (collector == null) throw new ServiceException("采集器信息不能为空");
         collector.setCollectorCode(trimToNull(collector.getCollectorCode()));
         CollectorCandidate candidate = candidateStore.findCollector(collector.getCollectorCode(), Instant.now())
-                .filter(CollectorCandidate::online)
-                .orElseThrow(() -> new ServiceException("请先选择在线的发现候选设备"));
+                .orElseThrow(() -> new ServiceException("请先选择有效的发现候选设备"));
         collector.setIpAddress(candidate.ip());
         collector.setMacAddress(candidate.mac());
         collector.setChannelCount(candidate.channelCount());
@@ -120,7 +119,7 @@
         collector.setDelFlag("0");
         collector.setCreateTime(new Date());
         int rows = collectorMapper.insertCollector(collector);
-        // 登记瞬间同步候选事实,避免正式记录暂时显示为离线且没有最近观测时间。
+        // 登记只确认来源身份,在线与离线事实都按候选原样同步。
         collectorMapper.updateRegisteredObservation(collector.getCollectorCode(),
                 candidate.online() ? "ONLINE" : "OFFLINE");
         return rows;
diff --git a/battery-business/src/main/java/com/battery/business/service/impl/TDetectionCabinetServiceImpl.java b/battery-business/src/main/java/com/battery/business/service/impl/TDetectionCabinetServiceImpl.java
index fb49958..7e8ce2b 100644
--- a/battery-business/src/main/java/com/battery/business/service/impl/TDetectionCabinetServiceImpl.java
+++ b/battery-business/src/main/java/com/battery/business/service/impl/TDetectionCabinetServiceImpl.java
@@ -101,7 +101,7 @@
     }
 
     /**
-     * 读取 Redis 在线候选供原选择设备弹窗展示,不生成模拟设备。
+     * 读取未过期且未登记的候选,离线设备仍保留身份并可选择。
      * 测试占用状态由实时通道工作状态聚合;测试归属留待后续绑定闭环确认。
      *
      * @return 可供选择的检测柜设备摘要
@@ -111,13 +111,12 @@
     {
         Set<String> registeredCodes = Set.copyOf(detectionCabinetMapper.selectRegisteredCabinetCodes());
         return candidateStore.snapshot(Instant.now()).cabinets().stream()
-                .filter(CabinetCandidate::online)
                 .filter(candidate -> !registeredCodes.contains(candidate.deviceCode()))
                 .map(candidate -> {
-                    boolean active = isActive(candidate);
+                    boolean active = candidate.online() && isActive(candidate);
                     return new DiscoveredCabinetVo(
                             candidate.deviceCode(), candidate.ip(), candidate.mac(), candidate.name(),
-                            candidate.manufacturer(), candidate.model(), candidate.channels().size(), "在线",
+                            candidate.manufacturer(), candidate.model(), candidate.channels().size(), candidate.online() ? "在线" : "离线",
                             active ? "测试中" : "空闲",
                             active ? "待确认" : "-");
                 })
@@ -136,8 +135,7 @@
     {
         normalizeBasicFields(detectionCabinet);
         CabinetCandidate candidate = candidateStore.findCabinet(detectionCabinet.getCabinetCode(), Instant.now())
-                .filter(CabinetCandidate::online)
-                .orElseThrow(() -> new ServiceException("请先选择在线的发现候选设备"));
+                .orElseThrow(() -> new ServiceException("请先选择有效的发现候选设备"));
         detectionCabinet.setAdapterId(candidate.adapterId());
         detectionCabinet.setAdapterCode(candidate.adapterCode());
         detectionCabinet.setVendorDeviceType(candidate.devtype());
@@ -169,7 +167,7 @@
                 value.setLastSeenAtUtc(null);
                 detectionCabinetMapper.insertDetectionChannel(value);
             }
-            // 登记瞬间的候选也是一次有效观测,立即写入正式事实,避免等待下一轮轮询才显示在线。
+            // 登记只确认来源身份,在线与离线事实都按候选原样同步。
             detectionCabinetMapper.updateRegisteredCabinetObservation(candidate.adapterCode(), candidate.devtype(),
                     candidate.devid(), candidate.online() ? "ONLINE" : "OFFLINE", candidate.observedAt(),
                     candidate.online());
diff --git a/ruoyi-admin/src/main/java/com/battery/business/runtime/DevelopmentCollectorDiscoveryService.java b/ruoyi-admin/src/main/java/com/battery/business/runtime/DevelopmentCollectorDiscoveryService.java
index e73a87d..6741cf1 100644
--- a/ruoyi-admin/src/main/java/com/battery/business/runtime/DevelopmentCollectorDiscoveryService.java
+++ b/ruoyi-admin/src/main/java/com/battery/business/runtime/DevelopmentCollectorDiscoveryService.java
@@ -23,17 +23,16 @@
         this.collectorService = collectorService;
     }
 
-    /** 返回在线且未登记的独立采集器快照,不混入 BTS 检测柜设备。 */
+    /** 返回未过期且未登记的采集器,离线事实不影响发现展示或登记资格。 */
     @Override
     public List<DiscoveredCollectorVo> selectDiscoveredCollectorList()
     {
         Set<String> registeredCodes = collectorService.selectRegisteredCollectorCodes();
         return candidateStore.snapshot(Instant.now()).collectors().stream()
-                .filter(collector -> collector.online()
-                        && !registeredCodes.contains(collector.deviceCode()))
+                .filter(collector -> !registeredCodes.contains(collector.deviceCode()))
                 .map(collector -> new DiscoveredCollectorVo(collector.deviceCode(), collector.ip(), collector.mac(),
                         collector.name(), collector.manufacturer(), collector.model(), collector.channelCount(),
-                        "在线", "空闲", "-"))
+                        collector.online() ? "在线" : "离线", "空闲", "-"))
                 .toList();
     }
 }
diff --git a/ruoyi-admin/src/test/java/com/battery/business/service/impl/CollectorDiscoveryServiceImplTest.java b/ruoyi-admin/src/test/java/com/battery/business/service/impl/CollectorDiscoveryServiceImplTest.java
index 273485c..c42ac66 100644
--- a/ruoyi-admin/src/test/java/com/battery/business/service/impl/CollectorDiscoveryServiceImplTest.java
+++ b/ruoyi-admin/src/test/java/com/battery/business/service/impl/CollectorDiscoveryServiceImplTest.java
@@ -50,7 +50,7 @@
     }
 
     @Test
-    public void shouldHideOfflineCollectors() throws Exception
+    public void shouldReturnOfflineUnregisteredCollectors() throws Exception
     {
         try (BtsDevelopmentSimulator simulator = new BtsDevelopmentSimulator(0))
         {
@@ -65,7 +65,9 @@
 
             List<DiscoveredCollectorVo> result = service.selectDiscoveredCollectorList();
 
-            assertEquals(0, result.size());
+            assertEquals(1, result.size());
+            assertEquals("dev-sim-collector-1", result.get(0).getDeviceCode());
+            assertEquals("离线", result.get(0).getOnlineStatus());
         }
     }
 
@@ -79,6 +81,7 @@
             DevelopmentCollectorDiscoveryService service =
                     new DevelopmentCollectorDiscoveryService(store, collectorService);
             TCollectorMapper collectorMapper = mock(TCollectorMapper.class);
+            simulator.updateCollector(1, "127.0.1.2", 4, false);
             new DevelopmentCollectorStatusSynchronizer(simulator, store, collectorMapper).synchronizeOnlineStatus();
             when(collectorService.selectRegisteredCollectorCodes())
                     .thenReturn(Set.of("dev-sim-collector-1"))
diff --git a/ruoyi-admin/src/test/java/com/battery/business/service/impl/TCollectorServiceImplTest.java b/ruoyi-admin/src/test/java/com/battery/business/service/impl/TCollectorServiceImplTest.java
index d87722e..3aef6e8 100644
--- a/ruoyi-admin/src/test/java/com/battery/business/service/impl/TCollectorServiceImplTest.java
+++ b/ruoyi-admin/src/test/java/com/battery/business/service/impl/TCollectorServiceImplTest.java
@@ -166,6 +166,18 @@
     }
 
     @Test
+    void offlineCollectorCanBeRegisteredWithoutChangingOnlineFact()
+    {
+        java.time.Instant now = java.time.Instant.now();
+        candidateStore.replaceCollectors("dev-sim", java.util.List.of(new CollectorCandidate(
+                "dev-sim", "COL-001", "192.168.1.10", "AA:BB:CC:DD:EE:FF", "一号采集器",
+                "开发模拟器", "Virtual Collector", 8, false, now, now.plusSeconds(60))));
+        when(collectorMapper.insertCollector(any(TCollector.class))).thenReturn(1);
+        assertEquals(1, collectorService.insertCollector(collector));
+        verify(collectorMapper).updateRegisteredObservation("COL-001", "OFFLINE");
+    }
+
+    @Test
     void insertCollectorShouldRejectDuplicateCode()
     {
         collector.setCollectorId(null);
diff --git a/ruoyi-admin/src/test/java/com/battery/business/service/impl/TDetectionCabinetServiceImplTest.java b/ruoyi-admin/src/test/java/com/battery/business/service/impl/TDetectionCabinetServiceImplTest.java
index 26d0218..6a464b1 100644
--- a/ruoyi-admin/src/test/java/com/battery/business/service/impl/TDetectionCabinetServiceImplTest.java
+++ b/ruoyi-admin/src/test/java/com/battery/business/service/impl/TDetectionCabinetServiceImplTest.java
@@ -228,6 +228,23 @@
         assertEquals(8, devices.get(0).getChannelCount());
     }
 
+    @Test
+    void offlineCabinetRemainsDiscoverableAndCanBeRegistered()
+    {
+        var store = new InMemoryDeviceDiscoveryCandidateStore();
+        store.recordCabinets(List.of(candidate("CABINET-001", false, DeviceWorkStatus.UNOBSERVABLE)));
+        var service = new TDetectionCabinetServiceImpl(detectionCabinetMapper, store);
+        when(detectionCabinetMapper.selectRegisteredCabinetCodes()).thenReturn(List.of());
+        var devices = service.selectDiscoveredCabinetList();
+        assertEquals(1, devices.size());
+        assertEquals("离线", devices.get(0).getOnlineStatus());
+        when(detectionCabinetMapper.insertDetectionCabinet(any(TDetectionCabinet.class))).thenReturn(1);
+        assertEquals(1, service.insertDetectionCabinet(detectionCabinet));
+        assertEquals("OFFLINE", detectionCabinet.getOnlineStatus());
+        when(detectionCabinetMapper.selectRegisteredCabinetCodes()).thenReturn(List.of("CABINET-001"));
+        assertEquals(0, service.selectDiscoveredCabinetList().size());
+    }
+
     /**
      * 验证重复设备编码会阻止新增且不会执行写库操作。
      */
@@ -255,7 +272,7 @@
         ServiceException exception = assertThrows(ServiceException.class,
                 () -> detectionCabinetService.insertDetectionCabinet(detectionCabinet));
 
-        assertEquals("请先选择在线的发现候选设备", exception.getMessage());
+        assertEquals("请先选择有效的发现候选设备", exception.getMessage());
         verify(detectionCabinetMapper, never()).insertDetectionCabinet(any());
     }
 

--
Gitblit v1.7.1