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.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;
|
}
|
|
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)
|
{
|
TDeviceAdapter adapter = mapper.selectDeviceAdapterById(adapterId);
|
if (adapter == null) throw error("设备适配器实例不存在", DeviceAdapterErrorCodes.NOT_FOUND);
|
return adapter;
|
}
|
|
@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(READ_ONLY_CAPABILITIES);
|
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<DetectedChannel> channels = runtime.discoverChannels();
|
Set<ChannelIdentity> available = channels.stream()
|
.filter(DetectedChannel::available)
|
.map(DetectedChannel::identity)
|
.collect(Collectors.toCollection(LinkedHashSet::new));
|
return new DiscoveryObservation(channels, 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<TDetectionChannel> 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()), AdapterCapabilities.observeOnly());
|
}
|
|
private ServiceException error(String message, int code) { return new ServiceException(message, code); }
|
|
private record DiscoveryObservation(List<DetectedChannel> channels,
|
Map<ChannelIdentity, RealtimeSnapshot> snapshots)
|
{
|
}
|
}
|