package com.ruoyi.system.service.impl; 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; 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.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; import com.ruoyi.system.domain.TDetectionChannel; import com.ruoyi.system.domain.TDeviceAdapter; import com.ruoyi.system.domain.vo.AdapterDiscoveryResult; import com.ruoyi.system.mapper.TDeviceAdapterMapper; import com.ruoyi.system.service.DeviceAdapterErrorCodes; import com.ruoyi.system.service.IDeviceAdapterService; @Service public class DeviceAdapterServiceImpl implements IDeviceAdapterService { private static final String ENABLED = "0"; private static final String DISABLED = "1"; private static final String READ_ONLY_CAPABILITIES = "{\"discovery\":true,\"realtimeObservation\":true,\"historyDownload\":true,\"control\":false}"; private final TDeviceAdapterMapper mapper; private final DeviceAdapterRegistry registry; private final DeviceDiscoveryPersistenceService persistence; public DeviceAdapterServiceImpl(TDeviceAdapterMapper mapper, DeviceAdapterRegistry registry, DeviceDiscoveryPersistenceService persistence) { this.mapper = mapper; this.registry = registry; this.persistence = persistence; } /** 按管理端筛选条件查询适配器及其运行状态。 */ @Override public List selectList(TDeviceAdapter query) { return mapper.selectDeviceAdapterList(query); } /** 查询适配器详情;实例不存在时抛出稳定业务错误。 */ @Override public TDeviceAdapter selectById(Long adapterId) { TDeviceAdapter adapter = mapper.selectDeviceAdapterById(adapterId); if (adapter == null) throw error("设备适配器实例不存在", DeviceAdapterErrorCodes.NOT_FOUND); return adapter; } /** 幂等注册应用托管的只读适配器;不预写检测柜或通道事实。 */ @Override public TDeviceAdapter ensureManagedAdapter(TDeviceAdapter adapter) { normalizeAndValidate(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); if (mapper.selectDeviceAdapterByCode(adapter.getAdapterCode()) != null) throw error("适配器编码已存在", DeviceAdapterErrorCodes.DUPLICATE_CODE); adapter.setCapabilities(READ_ONLY_CAPABILITIES); adapter.setStatus(DISABLED); adapter.setConnectionStatus("DISCONNECTED"); adapter.setDelFlag("0"); try { return mapper.insertDeviceAdapter(adapter); } 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(current.getCapabilities()); int rows = mapper.updateDeviceAdapter(adapter); if (ENABLED.equals(current.getStatus())) { adapter.setStatus(ENABLED); registry.synchronize(toRuntimeConfig(adapter)); } return rows; } /** 启停适配器并同步其运行时实例。 */ @Override public int changeStatus(Long adapterId, boolean enabled, String updateBy) { 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; } /** 执行一次发现和实时观测,并事务性写入通道事实。 */ @Override public AdapterDiscoveryResult discover(Long adapterId) { TDeviceAdapter adapter = selectById(adapterId); if (!ENABLED.equals(adapter.getStatus())) throw error("设备适配器实例未启用", DeviceAdapterErrorCodes.DISABLED); try { registry.synchronize(toRuntimeConfig(adapter)); DiscoveryObservation observation = registry.execute(adapter.getAdapterCode(), runtime -> { List channels = runtime.discoverChannels(); Set 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); return result; } catch (DeviceAdapterException exception) { return failDiscovery(adapterId, exception.failureCode(), exception); } catch (AdapterRegistryException exception) { return failDiscovery(adapterId, "ADAPTER_FAILURE", exception); } } /** 查询指定适配器已发现且未删除的通道。 */ @Override public List selectChannels(Long adapterId) { selectById(adapterId); return mapper.selectDetectionChannelsByAdapterId(adapterId); } /** 启动时恢复所有已启用适配器的运行时注册。 */ @Override public void synchronizeEnabledAdapters() { mapper.selectEnabledDeviceAdapters().forEach(adapter -> registry.synchronize(toRuntimeConfig(adapter))); } /** 轮询达到周期的已启用适配器,并持久化失败状态后继续调度。 */ @Override public void pollDueAdapters() { Instant now = Instant.now(); for (TDeviceAdapter adapter : mapper.selectEnabledDeviceAdapters()) { Instant lastAttempt = adapter.getLastAttemptAtUtc(); if (lastAttempt != null && Duration.between(lastAttempt, now).toMillis() < adapter.getPollIntervalMs()) continue; try { discover(adapter.getAdapterId()); } catch (ServiceException ignored) { /* 失败状态已持久化,下一轮按周期重试。 */ } } } private AdapterDiscoveryResult failDiscovery(Long adapterId, String errorCode, RuntimeException exception) { mapper.updateDiscoveryFailure(adapterId, errorCode, exception.getMessage()); throw error("设备发现失败:" + exception.getMessage(), DeviceAdapterErrorCodes.DISCOVERY_FAILED); } private void normalizeAndValidate(TDeviceAdapter adapter) { adapter.setAdapterCode(StringUtils.trim(adapter.getAdapterCode())); adapter.setAdapterName(StringUtils.trim(adapter.getAdapterName())); adapter.setAdapterType(StringUtils.trim(adapter.getAdapterType()).toUpperCase(Locale.ROOT)); adapter.setHost(StringUtils.trim(adapter.getHost())); adapter.setTimezoneId(StringUtils.trim(adapter.getTimezoneId())); adapter.setRemark(StringUtils.trim(adapter.getRemark())); if (!"BTS".equals(adapter.getAdapterType())) throw error("不支持的适配器类型", DeviceAdapterErrorCodes.UNSUPPORTED_TYPE); if (!Boolean.TRUE.equals(adapter.getReadOnly())) throw error("当前里程碑只允许只读设备适配器", DeviceAdapterErrorCodes.INVALID_CONFIG); try { if (adapter.getPort() == null || adapter.getPort() < 1 || adapter.getPort() > 65535 || adapter.getPollIntervalMs() == null || adapter.getPollIntervalMs() < 1000 || StringUtils.isBlank(adapter.getAdapterCode()) || StringUtils.isBlank(adapter.getAdapterName()) || StringUtils.isBlank(adapter.getHost())) throw new IllegalArgumentException(); ZoneId.of(adapter.getTimezoneId()); } catch (RuntimeException exception) { throw error("设备适配器配置不正确", 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()), 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); } private record DiscoveryObservation(List channels, Map snapshots) { } }