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