From eade173a673af574a39e1de2b96a383470f75ea6 Mon Sep 17 00:00:00 2001
From: yeg <1347747024@qq.com>
Date: 星期一, 28 九月 2026 15:37:47 +0800
Subject: [PATCH] 修掉配置来源实例时的三处缺陷

---
 battery-business/src/main/resources/mapper/battery/TDeviceAdapterMapper.xml                    |    2 
 ruoyi-admin/src/test/java/com/battery/business/service/impl/DeviceAdapterServiceImplTest.java  |   40 ++++++++++++++++++++
 battery-business/src/main/java/com/battery/business/service/impl/DeviceAdapterServiceImpl.java |   22 +++++++++-
 3 files changed, 60 insertions(+), 4 deletions(-)

diff --git a/battery-business/src/main/java/com/battery/business/service/impl/DeviceAdapterServiceImpl.java b/battery-business/src/main/java/com/battery/business/service/impl/DeviceAdapterServiceImpl.java
index 175558e..c63c87e 100644
--- a/battery-business/src/main/java/com/battery/business/service/impl/DeviceAdapterServiceImpl.java
+++ b/battery-business/src/main/java/com/battery/business/service/impl/DeviceAdapterServiceImpl.java
@@ -5,6 +5,8 @@
 import java.time.ZoneId;
 import java.util.List;
 import java.util.Locale;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
 import org.springframework.dao.DuplicateKeyException;
 import org.springframework.stereotype.Service;
 import com.alibaba.fastjson2.JSON;
@@ -28,6 +30,7 @@
 @Service
 public class DeviceAdapterServiceImpl implements IDeviceAdapterService
 {
+    private static final Logger log = LoggerFactory.getLogger(DeviceAdapterServiceImpl.class);
     private static final String ENABLED = "0";
     private static final String DISABLED = "1";
     private static final String READ_ONLY_CAPABILITIES =
@@ -122,10 +125,11 @@
         TDeviceAdapter adapter = selectById(adapterId);
         String status = enabled ? ENABLED : DISABLED;
         if (status.equals(adapter.getStatus())) return 1;
-        int rows = mapper.updateDeviceAdapterStatus(adapterId, status, updateBy);
         adapter.setStatus(status);
+        // 先装配运行时实例:装配失败(例如缺少口令环境变量)时不把状态写成"已启用",
+        // 免得留下"库里启用、运行时没有实例"的假状态。
         registry.synchronize(toRuntimeConfig(adapter));
-        return rows;
+        return mapper.updateDeviceAdapterStatus(adapterId, status, updateBy);
     }
 
     /** 执行一次发现和实时观测,将未登记设备写入 Redis 候选。 */
@@ -184,7 +188,19 @@
     @Override
     public void synchronizeEnabledAdapters()
     {
-        mapper.selectEnabledDeviceAdapters().forEach(adapter -> registry.synchronize(toRuntimeConfig(adapter)));
+        for (TDeviceAdapter adapter : mapper.selectEnabledDeviceAdapters())
+        {
+            try
+            {
+                registry.synchronize(toRuntimeConfig(adapter));
+            }
+            catch (RuntimeException exception)
+            {
+                // 单行配置有问题(例如缺少口令环境变量)不得拦住后端启动:
+                // 记下原因,由发现轮询按周期重试并把失败持久化到这一行。
+                log.warn("设备适配器实例装配失败 adapterCode={}:{}", adapter.getAdapterCode(), exception.getMessage());
+            }
+        }
     }
 
     /** 轮询达到周期的已启用适配器,并持久化失败状态后继续调度。 */
diff --git a/battery-business/src/main/resources/mapper/battery/TDeviceAdapterMapper.xml b/battery-business/src/main/resources/mapper/battery/TDeviceAdapterMapper.xml
index e041146..e0f7025 100644
--- a/battery-business/src/main/resources/mapper/battery/TDeviceAdapterMapper.xml
+++ b/battery-business/src/main/resources/mapper/battery/TDeviceAdapterMapper.xml
@@ -84,7 +84,7 @@
     <update id="updateDeviceAdapter" parameterType="TDeviceAdapter">
         update t_device_adapter set adapter_name=#{adapterName}, adapter_type=#{adapterType}, host=#{host}, port=#{port},
             poll_interval_ms=#{pollIntervalMs}, timezone_id=#{timezoneId}, capabilities=#{capabilities},
-            source_config=cast(#{sourceConfig} as json),
+            source_config=cast(#{sourceConfig} as json), remark=#{remark},
             connection_status='OFFLINE', last_error_code=null, last_error_message=null,
             update_by=#{updateBy}, update_time=sysdate()
         where adapter_id=#{adapterId} and del_flag=0
diff --git a/ruoyi-admin/src/test/java/com/battery/business/service/impl/DeviceAdapterServiceImplTest.java b/ruoyi-admin/src/test/java/com/battery/business/service/impl/DeviceAdapterServiceImplTest.java
index 4fa3e96..ef8125b 100644
--- a/ruoyi-admin/src/test/java/com/battery/business/service/impl/DeviceAdapterServiceImplTest.java
+++ b/ruoyi-admin/src/test/java/com/battery/business/service/impl/DeviceAdapterServiceImplTest.java
@@ -94,6 +94,46 @@
         verify(registry).synchronize(any());
     }
 
+    /** 装配失败(例如缺少口令环境变量)时不能把状态改成"已启用",免得留下库里启用、运行时没实例的假状态。 */
+    @Test
+    void failedEnableKeepsAdapterDisabledAndSurfacesTheReason()
+    {
+        adapter.setStatus("1");
+        when(adapterMapper.selectDeviceAdapterById(7L)).thenReturn(adapter);
+        org.mockito.Mockito.doThrow(new DeviceAdapterException("缺少厂商库连接口令:请设置环境变量 X"))
+                .when(registry).synchronize(any());
+
+        DeviceAdapterException exception = assertThrows(DeviceAdapterException.class,
+                () -> service.changeStatus(7L, true, "admin"));
+
+        assertTrue(exception.getMessage().contains("缺少厂商库连接口令"));
+        verify(adapterMapper, never()).updateDeviceAdapterStatus(any(), any(), any());
+    }
+
+    /** 单行配置有问题不得拦住后端启动:记下原因,由发现轮询按周期重试并持久化失败。 */
+    @Test
+    void startupRestoreSurvivesOneBrokenAdapterRow()
+    {
+        TDeviceAdapter vendor = new TDeviceAdapter();
+        vendor.setAdapterId(9L);
+        vendor.setAdapterCode("neware76");
+        vendor.setAdapterName("新威 7.6 那台");
+        vendor.setAdapterType("NEWARE_MYSQL");
+        vendor.setHost("26.70.208.249");
+        vendor.setPort(3306);
+        vendor.setPollIntervalMs(60000);
+        vendor.setTimezoneId("Asia/Hong_Kong");
+        vendor.setStatus("0");
+        vendor.setCapabilities("{\"discovery\":true,\"realtimeObservation\":true,\"historyDownload\":true,\"control\":false}");
+        when(adapterMapper.selectEnabledDeviceAdapters()).thenReturn(List.of(vendor));
+        org.mockito.Mockito.doThrow(new DeviceAdapterException("缺少厂商库连接口令:请设置环境变量 X"))
+                .when(registry).synchronize(any());
+
+        service.synchronizeEnabledAdapters();
+
+        verify(registry).synchronize(any());
+    }
+
     @Test
     void springSelectsProductionConstructor()
     {

--
Gitblit v1.7.1