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 |  101 +++++++++++++++++++++++++++++++++++++++-----------
 1 files changed, 78 insertions(+), 23 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 5b06844..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
@@ -7,11 +7,13 @@
 import java.util.Locale;
 import org.springframework.dao.DuplicateKeyException;
 import org.springframework.stereotype.Service;
+import com.alibaba.fastjson2.JSON;
+import com.alibaba.fastjson2.JSONObject;
 import com.ruoyi.common.exception.ServiceException;
 import com.ruoyi.common.utils.StringUtils;
 import com.ruoyi.device.api.DeviceAdapterException;
-import com.ruoyi.device.bts.BtsProtocolException;
-import com.ruoyi.device.bts.BtsTransportException;
+import com.ruoyi.device.bts.BtsDevelopmentSimulator;
+import com.ruoyi.device.discovery.DeviceDiscoveryCandidateStore;
 import com.ruoyi.device.model.AdapterCapabilities;
 import com.ruoyi.device.registry.AdapterInstanceConfig;
 import com.ruoyi.device.registry.AdapterRegistryException;
@@ -22,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
@@ -34,23 +37,22 @@
     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;
     }
 
-    DeviceAdapterServiceImpl(TDeviceAdapterMapper mapper, DeviceAdapterRegistry registry)
-    {
-        this(mapper, registry, new DeviceDiscoveryPersistenceService(mapper));
-    }
-
+    /** 按管理端筛选条件查询适配器及其运行状态。 */
     @Override
     public List<TDeviceAdapter> selectList(TDeviceAdapter query) { return mapper.selectDeviceAdapterList(query); }
 
+    /** 查询适配器详情;实例不存在时抛出稳定业务错误。 */
     @Override
     public TDeviceAdapter selectById(Long adapterId)
     {
@@ -59,10 +61,30 @@
         return adapter;
     }
 
+    /** 幂等注册应用托管的只读适配器;不预写检测柜或通道事实。 */
+    @Override
+    public TDeviceAdapter ensureManagedAdapter(TDeviceAdapter adapter)
+    {
+        normalizeAndValidate(adapter);
+        rejectDevelopmentSimulatorDatabaseConfig(adapter);
+        AdapterCapabilities capabilities = parseCapabilities(adapter.getCapabilities());
+        if (capabilities.control())
+            throw error("当前里程碑只允许只读设备适配器", DeviceAdapterErrorCodes.INVALID_CONFIG);
+        adapter.setStatus(ENABLED);
+        adapter.setConnectionStatus("DISCONNECTED");
+        adapter.setDelFlag("0");
+        mapper.upsertManagedDeviceAdapter(adapter);
+        TDeviceAdapter registered = mapper.selectDeviceAdapterByCode(adapter.getAdapterCode());
+        if (registered == null) throw error("托管设备适配器注册失败", DeviceAdapterErrorCodes.NOT_FOUND);
+        return registered;
+    }
+
+    /** 新增默认禁用的只读适配器配置。 */
     @Override
     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);
@@ -73,13 +95,14 @@
         catch (DuplicateKeyException exception) { throw error("适配器编码已存在", DeviceAdapterErrorCodes.DUPLICATE_CODE); }
     }
 
+    /** 更新配置;已启用实例会同步运行时注册表。 */
     @Override
     public int update(TDeviceAdapter adapter)
     {
         TDeviceAdapter current = selectById(adapter.getAdapterId());
         normalizeAndValidate(adapter);
         adapter.setAdapterCode(current.getAdapterCode());
-        adapter.setCapabilities(READ_ONLY_CAPABILITIES);
+        adapter.setCapabilities(current.getCapabilities());
         int rows = mapper.updateDeviceAdapter(adapter);
         if (ENABLED.equals(current.getStatus()))
         {
@@ -89,6 +112,7 @@
         return rows;
     }
 
+    /** 启停适配器并同步其运行时实例。 */
     @Override
     public int changeStatus(Long adapterId, boolean enabled, String updateBy)
     {
@@ -101,6 +125,7 @@
         return rows;
     }
 
+    /** 执行一次发现和实时观测,将未登记设备写入 Redis 候选。 */
     @Override
     public AdapterDiscoveryResult discover(Long adapterId)
     {
@@ -110,39 +135,54 @@
         try
         {
             registry.synchronize(toRuntimeConfig(adapter));
-            List<com.ruoyi.device.model.DetectedChannel> channels = registry.require(adapter.getAdapterCode()).discoverChannels();
-            Instant observedAt = Instant.now();
-            AdapterDiscoveryResult result = persistence.persist(adapter, channels, 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 (BtsTransportException exception)
+        catch (DeviceAdapterException exception)
         {
-            return failDiscovery(adapterId, exception.reason().name(), exception);
+            return failDiscovery(adapterId, exception.failureCode(), exception);
         }
-        catch (BtsProtocolException exception)
-        {
-            return failDiscovery(adapterId, exception.reason().name(), exception);
-        }
-        catch (DeviceAdapterException | AdapterRegistryException exception)
+        catch (AdapterRegistryException exception)
         {
             return failDiscovery(adapterId, "ADAPTER_FAILURE", exception);
         }
     }
 
+    /** 查询指定适配器当前未过期的 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();
     }
 
+    /** 启动时恢复所有已启用适配器的运行时注册。 */
     @Override
     public void synchronizeEnabledAdapters()
     {
         mapper.selectEnabledDeviceAdapters().forEach(adapter -> registry.synchronize(toRuntimeConfig(adapter)));
     }
 
+    /** 轮询达到周期的已启用适配器,并持久化失败状态后继续调度。 */
     @Override
     public void pollDueAdapters()
     {
@@ -188,11 +228,26 @@
         }
     }
 
+    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(),
                 adapter.getPort(), adapter.getPollIntervalMs(), adapter.getTimezoneId(),
-                ENABLED.equals(adapter.getStatus()), AdapterCapabilities.observeOnly());
+                ENABLED.equals(adapter.getStatus()), parseCapabilities(adapter.getCapabilities()));
+    }
+
+    private AdapterCapabilities parseCapabilities(String capabilities)
+    {
+        JSONObject value = JSON.parseObject(capabilities);
+        if (value == null) throw error("设备适配器能力配置不正确", DeviceAdapterErrorCodes.INVALID_CONFIG);
+        return new AdapterCapabilities(value.getBooleanValue("discovery"),
+                value.getBooleanValue("realtimeObservation"), value.getBooleanValue("historyDownload"),
+                value.getBooleanValue("control"));
     }
 
     private ServiceException error(String message, int code) { return new ServiceException(message, code); }

--
Gitblit v1.7.1