| | |
| | | import com.battery.business.domain.TDetectionChannel; |
| | | import com.battery.business.domain.vo.DiscoveredCabinetVo; |
| | | import com.battery.business.mapper.TDetectionCabinetMapper; |
| | | import com.battery.business.service.IDeviceAdapterService; |
| | | import com.battery.business.service.ITDetectionCabinetService; |
| | | import com.battery.device.api.discovery.DeviceDiscoveryCandidateStore; |
| | | import com.battery.device.api.discovery.DeviceDiscoveryCandidateStore.CabinetCandidate; |
| | |
| | | private static final String TESTING = "1"; |
| | | private static final String ONLINE = "0"; |
| | | private static final String OFFLINE = "1"; |
| | | private static final Set<DeviceWorkStatus> ACTIVE_WORK_STATUSES = Set.of(DeviceWorkStatus.WORKING, |
| | | DeviceWorkStatus.PAUSED, DeviceWorkStatus.PROTECTED, DeviceWorkStatus.SYNCHRONIZING, |
| | | DeviceWorkStatus.LIT, DeviceWorkStatus.WAITING_TIMEOUT, DeviceWorkStatus.WAITING_START, |
| | | DeviceWorkStatus.LOCKED); |
| | | |
| | | private final TDetectionCabinetMapper detectionCabinetMapper; |
| | | private final DeviceDiscoveryCandidateStore candidateStore; |
| | | private final IDeviceAdapterService adapterService; |
| | | |
| | | /** |
| | | * 构造检测柜业务服务。 |
| | |
| | | public TDetectionCabinetServiceImpl(TDetectionCabinetMapper detectionCabinetMapper, |
| | | DeviceDiscoveryCandidateStore candidateStore) |
| | | { |
| | | this(detectionCabinetMapper, candidateStore, null); |
| | | } |
| | | |
| | | /** |
| | | * 构造检测柜业务服务。 |
| | | * |
| | | * @param detectionCabinetMapper 检测柜数据访问对象 |
| | | * @param candidateStore 发现候选存储 |
| | | * @param adapterService 适配器实例服务;为空时列表不展示数据来源名称 |
| | | */ |
| | | @org.springframework.beans.factory.annotation.Autowired |
| | | public TDetectionCabinetServiceImpl(TDetectionCabinetMapper detectionCabinetMapper, |
| | | DeviceDiscoveryCandidateStore candidateStore, |
| | | @org.springframework.lang.Nullable IDeviceAdapterService adapterService) |
| | | { |
| | | this.detectionCabinetMapper = detectionCabinetMapper; |
| | | this.candidateStore = candidateStore; |
| | | this.adapterService = adapterService; |
| | | } |
| | | |
| | | /** 数据来源名称按适配器编码解析;代码装配实例没有数据库记录时退回展示编码。 */ |
| | | private Map<String, String> adapterNames() |
| | | { |
| | | if (adapterService == null) return Map.of(); |
| | | return adapterService.selectList(new com.battery.business.domain.TDeviceAdapter()).stream() |
| | | .filter(item -> item.getAdapterCode() != null && item.getAdapterName() != null) |
| | | .collect(java.util.stream.Collectors.toMap( |
| | | com.battery.business.domain.TDeviceAdapter::getAdapterCode, |
| | | com.battery.business.domain.TDeviceAdapter::getAdapterName, (left, right) -> right)); |
| | | } |
| | | |
| | | /** 适配器编码可能缺失(历史登记),缺失时不查询来源名称。 */ |
| | | private void applyAdapterName(TDetectionCabinet cabinet, Map<String, String> adapterNames) |
| | | { |
| | | if (cabinet == null || cabinet.getAdapterCode() == null) return; |
| | | cabinet.setAdapterName(adapterNames.get(cabinet.getAdapterCode())); |
| | | } |
| | | |
| | | /** |
| | |
| | | { |
| | | throw new ServiceException("检测柜不存在或已删除"); |
| | | } |
| | | applyAdapterName(detectionCabinet, adapterNames()); |
| | | return applyRealtimeState(detectionCabinet, candidatesByCode()); |
| | | } |
| | | |
| | |
| | | detectionCabinet.setOnlineStatus(null); |
| | | detectionCabinet.setTestStatus(null); |
| | | Map<String, CabinetCandidate> candidates = candidatesByCode(); |
| | | Map<String, String> adapterNames = adapterNames(); |
| | | return detectionCabinetMapper.selectDetectionCabinetList(detectionCabinet).stream() |
| | | .map(item -> applyRealtimeState(item, candidates)) |
| | | .peek(item -> applyAdapterName(item, adapterNames)) |
| | | .filter(item -> StringUtils.isBlank(requestedOnlineStatus) |
| | | || requestedOnlineStatus.equals(item.getOnlineStatus())) |
| | | .filter(item -> StringUtils.isBlank(requestedTestStatus) |
| | |
| | | } |
| | | |
| | | /** |
| | | * 读取 Redis 在线候选供原选择设备弹窗展示,不生成模拟设备。 |
| | | * 测试占用状态由实时通道工作状态聚合;测试归属留待后续绑定闭环确认。 |
| | | * 读取未登记且未删除的候选,离线设备仍保留身份并可选择。 |
| | | * 候选不按时间过期(厂商存储里的通道登记是长期事实),只按"已登记且未删除"排除; |
| | | * 每条附带最近数据时间并按它倒序,便于操作员判断哪几台真的在产出数据。 |
| | | * 发现只返回设备事实,不从通道工作状态推断系统测试关系,也不自动判定"在用"。 |
| | | * |
| | | * @return 可供选择的检测柜设备摘要 |
| | | */ |
| | |
| | | { |
| | | Set<String> registeredCodes = Set.copyOf(detectionCabinetMapper.selectRegisteredCabinetCodes()); |
| | | return candidateStore.snapshot(Instant.now()).cabinets().stream() |
| | | .filter(CabinetCandidate::online) |
| | | .filter(candidate -> !registeredCodes.contains(candidate.deviceCode())) |
| | | .map(candidate -> { |
| | | boolean active = isActive(candidate); |
| | | return new DiscoveredCabinetVo( |
| | | .map(candidate -> new DiscoveredCabinetVo( |
| | | candidate.deviceCode(), candidate.ip(), candidate.mac(), candidate.name(), |
| | | candidate.manufacturer(), candidate.model(), candidate.channels().size(), "在线", |
| | | active ? "测试中" : "空闲", |
| | | active ? "待确认" : "-"); |
| | | }) |
| | | candidate.manufacturer(), candidate.model(), candidate.channels().size(), |
| | | candidate.online() ? "在线" : "离线", candidate.lastDataAt())) |
| | | .sorted(java.util.Comparator.comparing(DiscoveredCabinetVo::lastDataAt, |
| | | java.util.Comparator.nullsLast(java.util.Comparator.reverseOrder())) |
| | | .thenComparing(DiscoveredCabinetVo::deviceCode)) |
| | | .toList(); |
| | | } |
| | | |
| | |
| | | { |
| | | normalizeBasicFields(detectionCabinet); |
| | | CabinetCandidate candidate = candidateStore.findCabinet(detectionCabinet.getCabinetCode(), Instant.now()) |
| | | .filter(CabinetCandidate::online) |
| | | .orElseThrow(() -> new ServiceException("请先选择在线的发现候选设备")); |
| | | .orElseThrow(() -> new ServiceException("请先选择有效的发现候选设备")); |
| | | detectionCabinet.setAdapterId(candidate.adapterId()); |
| | | detectionCabinet.setAdapterCode(candidate.adapterCode()); |
| | | detectionCabinet.setVendorDeviceType(candidate.devtype()); |
| | |
| | | value.setLastSeenAtUtc(null); |
| | | detectionCabinetMapper.insertDetectionChannel(value); |
| | | } |
| | | // 登记瞬间的候选也是一次有效观测,立即写入正式事实,避免等待下一轮轮询才显示在线。 |
| | | // 登记只确认来源身份,在线与离线事实都按候选原样同步。 |
| | | detectionCabinetMapper.updateRegisteredCabinetObservation(candidate.adapterCode(), candidate.devtype(), |
| | | candidate.devid(), candidate.online() ? "ONLINE" : "OFFLINE", candidate.observedAt(), |
| | | candidate.online()); |
| | |
| | | private String stableCandidateKey(String adapterCode, Integer devtype, Integer devid) |
| | | { |
| | | return adapterCode + "-" + devtype + "-" + devid; |
| | | } |
| | | |
| | | private boolean isActive(CabinetCandidate candidate) |
| | | { |
| | | return candidate.channels().stream().anyMatch(channel -> ACTIVE_WORK_STATUSES.contains(channel.workStatus())); |
| | | } |
| | | |
| | | /** 将设备观测状态转换为最终数据库允许的通道事实状态。 */ |