修复离线设备无法发现和登记的问题
移除发现列表及登记接口残留的在线限制,未登记离线设备保留候选并展示真实状态,已登记设备继续过滤。补充离线发现、登记和登记过滤回归,全量236项测试及打包通过。
| | |
| | | if (collector == null) throw new ServiceException("采集器信息不能为空"); |
| | | collector.setCollectorCode(trimToNull(collector.getCollectorCode())); |
| | | CollectorCandidate candidate = candidateStore.findCollector(collector.getCollectorCode(), Instant.now()) |
| | | .filter(CollectorCandidate::online) |
| | | .orElseThrow(() -> new ServiceException("请先选择在线的发现候选设备")); |
| | | .orElseThrow(() -> new ServiceException("请先选择有效的发现候选设备")); |
| | | collector.setIpAddress(candidate.ip()); |
| | | collector.setMacAddress(candidate.mac()); |
| | | collector.setChannelCount(candidate.channelCount()); |
| | |
| | | collector.setDelFlag("0"); |
| | | collector.setCreateTime(new Date()); |
| | | int rows = collectorMapper.insertCollector(collector); |
| | | // 登记瞬间同步候选事实,避免正式记录暂时显示为离线且没有最近观测时间。 |
| | | // 登记只确认来源身份,在线与离线事实都按候选原样同步。 |
| | | collectorMapper.updateRegisteredObservation(collector.getCollectorCode(), |
| | | candidate.online() ? "ONLINE" : "OFFLINE"); |
| | | return rows; |
| | |
| | | } |
| | | |
| | | /** |
| | | * 读取 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); |
| | | boolean active = candidate.online() && isActive(candidate); |
| | | return new DiscoveredCabinetVo( |
| | | candidate.deviceCode(), candidate.ip(), candidate.mac(), candidate.name(), |
| | | candidate.manufacturer(), candidate.model(), candidate.channels().size(), "在线", |
| | | candidate.manufacturer(), candidate.model(), candidate.channels().size(), candidate.online() ? "在线" : "离线", |
| | | active ? "测试中" : "空闲", |
| | | active ? "待确认" : "-"); |
| | | }) |
| | |
| | | { |
| | | 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()); |
| | |
| | | this.collectorService = collectorService; |
| | | } |
| | | |
| | | /** 返回在线且未登记的独立采集器快照,不混入 BTS 检测柜设备。 */ |
| | | /** 返回未过期且未登记的采集器,离线事实不影响发现展示或登记资格。 */ |
| | | @Override |
| | | public List<DiscoveredCollectorVo> selectDiscoveredCollectorList() |
| | | { |
| | | Set<String> registeredCodes = collectorService.selectRegisteredCollectorCodes(); |
| | | return candidateStore.snapshot(Instant.now()).collectors().stream() |
| | | .filter(collector -> collector.online() |
| | | && !registeredCodes.contains(collector.deviceCode())) |
| | | .filter(collector -> !registeredCodes.contains(collector.deviceCode())) |
| | | .map(collector -> new DiscoveredCollectorVo(collector.deviceCode(), collector.ip(), collector.mac(), |
| | | collector.name(), collector.manufacturer(), collector.model(), collector.channelCount(), |
| | | "在线", "空闲", "-")) |
| | | collector.online() ? "在线" : "离线", "空闲", "-")) |
| | | .toList(); |
| | | } |
| | | } |
| | |
| | | } |
| | | |
| | | @Test |
| | | public void shouldHideOfflineCollectors() throws Exception |
| | | public void shouldReturnOfflineUnregisteredCollectors() throws Exception |
| | | { |
| | | try (BtsDevelopmentSimulator simulator = new BtsDevelopmentSimulator(0)) |
| | | { |
| | |
| | | |
| | | List<DiscoveredCollectorVo> result = service.selectDiscoveredCollectorList(); |
| | | |
| | | assertEquals(0, result.size()); |
| | | assertEquals(1, result.size()); |
| | | assertEquals("dev-sim-collector-1", result.get(0).getDeviceCode()); |
| | | assertEquals("离线", result.get(0).getOnlineStatus()); |
| | | } |
| | | } |
| | | |
| | |
| | | DevelopmentCollectorDiscoveryService service = |
| | | new DevelopmentCollectorDiscoveryService(store, collectorService); |
| | | TCollectorMapper collectorMapper = mock(TCollectorMapper.class); |
| | | simulator.updateCollector(1, "127.0.1.2", 4, false); |
| | | new DevelopmentCollectorStatusSynchronizer(simulator, store, collectorMapper).synchronizeOnlineStatus(); |
| | | when(collectorService.selectRegisteredCollectorCodes()) |
| | | .thenReturn(Set.of("dev-sim-collector-1")) |
| | |
| | | } |
| | | |
| | | @Test |
| | | void offlineCollectorCanBeRegisteredWithoutChangingOnlineFact() |
| | | { |
| | | java.time.Instant now = java.time.Instant.now(); |
| | | candidateStore.replaceCollectors("dev-sim", java.util.List.of(new CollectorCandidate( |
| | | "dev-sim", "COL-001", "192.168.1.10", "AA:BB:CC:DD:EE:FF", "一号采集器", |
| | | "开发模拟器", "Virtual Collector", 8, false, now, now.plusSeconds(60)))); |
| | | when(collectorMapper.insertCollector(any(TCollector.class))).thenReturn(1); |
| | | assertEquals(1, collectorService.insertCollector(collector)); |
| | | verify(collectorMapper).updateRegisteredObservation("COL-001", "OFFLINE"); |
| | | } |
| | | |
| | | @Test |
| | | void insertCollectorShouldRejectDuplicateCode() |
| | | { |
| | | collector.setCollectorId(null); |
| | |
| | | assertEquals(8, devices.get(0).getChannelCount()); |
| | | } |
| | | |
| | | @Test |
| | | void offlineCabinetRemainsDiscoverableAndCanBeRegistered() |
| | | { |
| | | var store = new InMemoryDeviceDiscoveryCandidateStore(); |
| | | store.recordCabinets(List.of(candidate("CABINET-001", false, DeviceWorkStatus.UNOBSERVABLE))); |
| | | var service = new TDetectionCabinetServiceImpl(detectionCabinetMapper, store); |
| | | when(detectionCabinetMapper.selectRegisteredCabinetCodes()).thenReturn(List.of()); |
| | | var devices = service.selectDiscoveredCabinetList(); |
| | | assertEquals(1, devices.size()); |
| | | assertEquals("离线", devices.get(0).getOnlineStatus()); |
| | | when(detectionCabinetMapper.insertDetectionCabinet(any(TDetectionCabinet.class))).thenReturn(1); |
| | | assertEquals(1, service.insertDetectionCabinet(detectionCabinet)); |
| | | assertEquals("OFFLINE", detectionCabinet.getOnlineStatus()); |
| | | when(detectionCabinetMapper.selectRegisteredCabinetCodes()).thenReturn(List.of("CABINET-001")); |
| | | assertEquals(0, service.selectDiscoveredCabinetList().size()); |
| | | } |
| | | |
| | | /** |
| | | * 验证重复设备编码会阻止新增且不会执行写库操作。 |
| | | */ |
| | |
| | | ServiceException exception = assertThrows(ServiceException.class, |
| | | () -> detectionCabinetService.insertDetectionCabinet(detectionCabinet)); |
| | | |
| | | assertEquals("请先选择在线的发现候选设备", exception.getMessage()); |
| | | assertEquals("请先选择有效的发现候选设备", exception.getMessage()); |
| | | verify(detectionCabinetMapper, never()).insertDetectionCabinet(any()); |
| | | } |
| | | |