yeg
2026-09-28 eade173a673af574a39e1de2b96a383470f75ea6
修掉配置来源实例时的三处缺陷

现场配新威来源实例时踩到的三个问题:

- 启动恢复单个实例失败会拦住整个后端启动。现改为逐行记录并继续,装配失败的原因
由发现轮询按周期重试并持久化到该行,不在启动路径上抛异常。
- 启用实例时先写库再装配,装配失败(例如缺少口令环境变量)会留下"库里启用、
运行时没有实例"的假状态。现改为先装配成功再改状态。
- 编辑适配器接口静默丢弃 remark:update 语句没有更新该列。补上。

前两处各配了一条测试。
3个文件已修改
64 ■■■■■ 已修改文件
battery-business/src/main/java/com/battery/business/service/impl/DeviceAdapterServiceImpl.java 22 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
battery-business/src/main/resources/mapper/battery/TDeviceAdapterMapper.xml 2 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-admin/src/test/java/com/battery/business/service/impl/DeviceAdapterServiceImplTest.java 40 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
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());
            }
        }
    }
    /** 轮询达到周期的已启用适配器,并持久化失败状态后继续调度。 */
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
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()
    {