From 210663040bf65ee00b5f86545fdc31bd0356fddf Mon Sep 17 00:00:00 2001
From: yeg <1347747024@qq.com>
Date: 星期四, 27 八月 2026 16:31:53 +0800
Subject: [PATCH] feat(device): persist simulator discovery in redis

---
 ruoyi-system/src/main/java/com/ruoyi/system/service/impl/DeviceAdapterServiceImpl.java |   67 +++++++++++++++++++--------------
 1 files changed, 38 insertions(+), 29 deletions(-)

diff --git a/ruoyi-system/src/main/java/com/ruoyi/system/service/impl/DeviceAdapterServiceImpl.java b/ruoyi-system/src/main/java/com/ruoyi/system/service/impl/DeviceAdapterServiceImpl.java
index cc74b8f..dd57211 100644
--- a/ruoyi-system/src/main/java/com/ruoyi/system/service/impl/DeviceAdapterServiceImpl.java
+++ b/ruoyi-system/src/main/java/com/ruoyi/system/service/impl/DeviceAdapterServiceImpl.java
@@ -3,12 +3,8 @@
 import java.time.Duration;
 import java.time.Instant;
 import java.time.ZoneId;
-import java.util.LinkedHashSet;
 import java.util.List;
 import java.util.Locale;
-import java.util.Map;
-import java.util.Set;
-import java.util.stream.Collectors;
 import org.springframework.dao.DuplicateKeyException;
 import org.springframework.stereotype.Service;
 import com.alibaba.fastjson2.JSON;
@@ -16,10 +12,9 @@
 import com.ruoyi.common.exception.ServiceException;
 import com.ruoyi.common.utils.StringUtils;
 import com.ruoyi.device.api.DeviceAdapterException;
+import com.ruoyi.device.bts.BtsDevelopmentSimulator;
+import com.ruoyi.device.discovery.DeviceDiscoveryCandidateStore;
 import com.ruoyi.device.model.AdapterCapabilities;
-import com.ruoyi.device.model.ChannelIdentity;
-import com.ruoyi.device.model.DetectedChannel;
-import com.ruoyi.device.model.RealtimeSnapshot;
 import com.ruoyi.device.registry.AdapterInstanceConfig;
 import com.ruoyi.device.registry.AdapterRegistryException;
 import com.ruoyi.device.registry.DeviceAdapterRegistry;
@@ -29,6 +24,7 @@
 import com.ruoyi.system.mapper.TDeviceAdapterMapper;
 import com.ruoyi.system.service.DeviceAdapterErrorCodes;
 import com.ruoyi.system.service.IDeviceAdapterService;
+import com.ruoyi.system.service.impl.DeviceDiscoveryPersistenceService.DiscoverySource;
 
 @Service
 public class DeviceAdapterServiceImpl implements IDeviceAdapterService
@@ -41,13 +37,15 @@
     private final TDeviceAdapterMapper mapper;
     private final DeviceAdapterRegistry registry;
     private final DeviceDiscoveryPersistenceService persistence;
+    private final DeviceDiscoveryCandidateStore candidateStore;
 
     public DeviceAdapterServiceImpl(TDeviceAdapterMapper mapper, DeviceAdapterRegistry registry,
-            DeviceDiscoveryPersistenceService persistence)
+            DeviceDiscoveryPersistenceService persistence, DeviceDiscoveryCandidateStore candidateStore)
     {
         this.mapper = mapper;
         this.registry = registry;
         this.persistence = persistence;
+        this.candidateStore = candidateStore;
     }
 
     /** 按管理端筛选条件查询适配器及其运行状态。 */
@@ -68,6 +66,7 @@
     public TDeviceAdapter ensureManagedAdapter(TDeviceAdapter adapter)
     {
         normalizeAndValidate(adapter);
+        rejectDevelopmentSimulatorDatabaseConfig(adapter);
         AdapterCapabilities capabilities = parseCapabilities(adapter.getCapabilities());
         if (capabilities.control())
             throw error("当前里程碑只允许只读设备适配器", DeviceAdapterErrorCodes.INVALID_CONFIG);
@@ -85,6 +84,7 @@
     public int insert(TDeviceAdapter adapter)
     {
         normalizeAndValidate(adapter);
+        rejectDevelopmentSimulatorDatabaseConfig(adapter);
         if (mapper.selectDeviceAdapterByCode(adapter.getAdapterCode()) != null)
             throw error("适配器编码已存在", DeviceAdapterErrorCodes.DUPLICATE_CODE);
         adapter.setCapabilities(READ_ONLY_CAPABILITIES);
@@ -125,7 +125,7 @@
         return rows;
     }
 
-    /** 执行一次发现和实时观测,并事务性写入通道事实。 */
+    /** 执行一次发现和实时观测,将未登记设备写入 Redis 候选。 */
     @Override
     public AdapterDiscoveryResult discover(Long adapterId)
     {
@@ -135,18 +135,10 @@
         try
         {
             registry.synchronize(toRuntimeConfig(adapter));
-            DiscoveryObservation observation = registry.execute(adapter.getAdapterCode(), runtime -> {
-                List<DetectedChannel> channels = runtime.discoverChannels();
-                Set<ChannelIdentity> available = channels.stream()
-                        .filter(DetectedChannel::available)
-                        .map(DetectedChannel::identity)
-                        .collect(Collectors.toCollection(LinkedHashSet::new));
-                return new DiscoveryObservation(channels, available.isEmpty() ? Map.of() : runtime.inquire(available));
-            });
-            Instant observedAt = Instant.now();
-            AdapterDiscoveryResult result = persistence.persist(adapter, observation.channels(),
-                    observation.snapshots(), observedAt);
-            mapper.updateDiscoverySuccess(adapterId, observedAt);
+            AdapterDiscoveryResult result = persistence.discover(registry, new DiscoverySource(
+                    adapter.getAdapterCode(), adapter.getAdapterId(), adapter.getAdapterName(),
+                    adapter.getPollIntervalMs()));
+            mapper.updateDiscoverySuccess(adapterId, result.observedAtUtc());
             return result;
         }
         catch (DeviceAdapterException exception)
@@ -159,12 +151,28 @@
         }
     }
 
-    /** 查询指定适配器已发现且未删除的通道。 */
+    /** 查询指定适配器当前未过期的 Redis 发现通道。 */
     @Override
     public List<TDetectionChannel> selectChannels(Long adapterId)
     {
-        selectById(adapterId);
-        return mapper.selectDetectionChannelsByAdapterId(adapterId);
+        TDeviceAdapter adapter = selectById(adapterId);
+        return candidateStore.snapshot(Instant.now()).cabinets().stream()
+                .filter(item -> item.adapterCode().equals(adapter.getAdapterCode()))
+                .flatMap(item -> item.channels().stream().map(channel -> {
+                    TDetectionChannel value = new TDetectionChannel();
+                    value.setAdapterId(adapterId);
+                    value.setDevtype(channel.identity().devtype());
+                    value.setDevid(channel.identity().devid());
+                    value.setSubdevid(channel.identity().subdevid());
+                    value.setChlid(channel.identity().chlid());
+                    value.setDisplayNo(channel.identity().subdevid() + "-" + channel.identity().chlid());
+                    value.setOnlineStatus(channel.online() ? "ONLINE" : "OFFLINE");
+                    value.setWorkStatus(channel.workStatus().name());
+                    value.setLastSeenAtUtc(channel.observedAt());
+                    value.setStatus("0");
+                    return value;
+                }))
+                .toList();
     }
 
     /** 启动时恢复所有已启用适配器的运行时注册。 */
@@ -220,6 +228,12 @@
         }
     }
 
+    private void rejectDevelopmentSimulatorDatabaseConfig(TDeviceAdapter adapter)
+    {
+        if (BtsDevelopmentSimulator.ADAPTER_CODE.equals(adapter.getAdapterCode()))
+            throw error("dev-sim 由开发环境代码固定装配,不能写入数据库", DeviceAdapterErrorCodes.INVALID_CONFIG);
+    }
+
     private AdapterInstanceConfig toRuntimeConfig(TDeviceAdapter adapter)
     {
         return new AdapterInstanceConfig(adapter.getAdapterCode(), adapter.getAdapterType(), adapter.getHost(),
@@ -237,9 +251,4 @@
     }
 
     private ServiceException error(String message, int code) { return new ServiceException(message, code); }
-
-    private record DiscoveryObservation(List<DetectedChannel> channels,
-            Map<ChannelIdentity, RealtimeSnapshot> snapshots)
-    {
-    }
 }

--
Gitblit v1.7.1