| | |
| | | import java.net.InetAddress; |
| | | import java.util.List; |
| | | import java.util.Locale; |
| | | import java.util.Map; |
| | | import java.util.Set; |
| | | import java.time.Instant; |
| | | import org.springframework.dao.DuplicateKeyException; |
| | | import org.springframework.stereotype.Service; |
| | | import org.springframework.transaction.annotation.Transactional; |
| | |
| | | import com.ruoyi.common.utils.StringUtils; |
| | | import com.ruoyi.common.utils.ip.IpUtils; |
| | | import com.ruoyi.system.domain.TDetectionCabinet; |
| | | import com.ruoyi.system.domain.TDetectionChannel; |
| | | import com.ruoyi.system.domain.vo.DiscoveredCabinetVo; |
| | | import com.ruoyi.system.mapper.TDetectionCabinetMapper; |
| | | import com.ruoyi.system.service.ITDetectionCabinetService; |
| | | import com.ruoyi.device.discovery.DeviceDiscoveryCandidateStore; |
| | | import com.ruoyi.device.discovery.DeviceDiscoveryCandidateStore.CabinetCandidate; |
| | | import com.ruoyi.device.model.DeviceWorkStatus; |
| | | |
| | | /** |
| | | * 检测柜 Service 业务层处理 |
| | |
| | | private static final String NORMAL = "0"; |
| | | private static final String DISABLED = "1"; |
| | | private static final String TESTING = "1"; |
| | | private static final String ACTIVE = "ACTIVE"; |
| | | private static final String DISCOVERED = "DISCOVERED"; |
| | | 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; |
| | | |
| | | /** |
| | | * 构造检测柜业务服务。 |
| | | * |
| | | * @param detectionCabinetMapper 检测柜数据访问对象 |
| | | */ |
| | | public TDetectionCabinetServiceImpl(TDetectionCabinetMapper detectionCabinetMapper) |
| | | public TDetectionCabinetServiceImpl(TDetectionCabinetMapper detectionCabinetMapper, |
| | | DeviceDiscoveryCandidateStore candidateStore) |
| | | { |
| | | this.detectionCabinetMapper = detectionCabinetMapper; |
| | | this.candidateStore = candidateStore; |
| | | } |
| | | |
| | | /** |
| | |
| | | { |
| | | throw new ServiceException("检测柜不存在或已删除"); |
| | | } |
| | | return detectionCabinet; |
| | | return applyRealtimeState(detectionCabinet, candidatesByCode()); |
| | | } |
| | | |
| | | /** |
| | |
| | | @Override |
| | | public List<TDetectionCabinet> selectDetectionCabinetList(TDetectionCabinet detectionCabinet) |
| | | { |
| | | String requestedOnlineStatus = detectionCabinet.getOnlineStatus(); |
| | | String requestedTestStatus = detectionCabinet.getTestStatus(); |
| | | if (StringUtils.isNotBlank(detectionCabinet.getMacAddress())) |
| | | { |
| | | detectionCabinet.setMacAddress(normalizeMacAddress(detectionCabinet.getMacAddress())); |
| | | } |
| | | return detectionCabinetMapper.selectDetectionCabinetList(detectionCabinet); |
| | | detectionCabinet.setOnlineStatus(null); |
| | | detectionCabinet.setTestStatus(null); |
| | | Map<String, CabinetCandidate> candidates = candidatesByCode(); |
| | | return detectionCabinetMapper.selectDetectionCabinetList(detectionCabinet).stream() |
| | | .map(item -> applyRealtimeState(item, candidates)) |
| | | .filter(item -> StringUtils.isBlank(requestedOnlineStatus) |
| | | || requestedOnlineStatus.equals(item.getOnlineStatus())) |
| | | .filter(item -> StringUtils.isBlank(requestedTestStatus) |
| | | || requestedTestStatus.equals(item.getTestStatus())) |
| | | .toList(); |
| | | } |
| | | |
| | | /** |
| | | * 复用已持久化检测柜事实供原选择设备弹窗展示,不生成模拟设备。 |
| | | * 测试占用状态由已持久化通道工作状态聚合;测试归属留待后续绑定闭环确认。 |
| | | * 读取 Redis 在线候选供原选择设备弹窗展示,不生成模拟设备。 |
| | | * 测试占用状态由实时通道工作状态聚合;测试归属留待后续绑定闭环确认。 |
| | | * |
| | | * @return 可供选择的检测柜设备摘要 |
| | | */ |
| | | @Override |
| | | public List<DiscoveredCabinetVo> selectDiscoveredCabinetList() |
| | | { |
| | | return detectionCabinetMapper.selectDiscoveredCabinetList().stream() |
| | | .map(cabinet -> { |
| | | boolean active = ACTIVE.equals(cabinet.getObservedWorkStatus()); |
| | | 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( |
| | | cabinet.getCabinetCode(), |
| | | cabinet.getIpAddress(), |
| | | cabinet.getMacAddress(), |
| | | cabinet.getCabinetName(), |
| | | cabinet.getManufacturer(), |
| | | cabinet.getModel(), |
| | | cabinet.getChannelCount(), |
| | | isOnline(cabinet.getOnlineStatus()) ? "在线" : "离线", |
| | | candidate.deviceCode(), candidate.ip(), candidate.mac(), candidate.name(), |
| | | candidate.manufacturer(), candidate.model(), candidate.channels().size(), "在线", |
| | | active ? "测试中" : "空闲", |
| | | active ? "待确认" : "-"); |
| | | }) |
| | | .toList(); |
| | | } |
| | | |
| | | private boolean isOnline(String onlineStatus) |
| | | { |
| | | return NORMAL.equals(onlineStatus) || "ONLINE".equalsIgnoreCase(onlineStatus); |
| | | } |
| | | |
| | | /** |
| | |
| | | * @return 影响行数 |
| | | */ |
| | | @Override |
| | | @Transactional(rollbackFor = Exception.class) |
| | | public int insertDetectionCabinet(TDetectionCabinet detectionCabinet) |
| | | { |
| | | normalizeBasicFields(detectionCabinet); |
| | | CabinetCandidate candidate = candidateStore.findCabinet(detectionCabinet.getCabinetCode(), Instant.now()) |
| | | .filter(CabinetCandidate::online) |
| | | .orElseThrow(() -> new ServiceException("请先选择在线的发现候选设备")); |
| | | detectionCabinet.setAdapterId(candidate.adapterId()); |
| | | detectionCabinet.setAdapterCode(candidate.adapterCode()); |
| | | detectionCabinet.setVendorDeviceType(candidate.devtype()); |
| | | detectionCabinet.setVendorDeviceId(candidate.devid()); |
| | | detectionCabinet.setIpAddress(candidate.ip()); |
| | | detectionCabinet.setMacAddress(candidate.mac() == null ? detectionCabinet.getMacAddress() : candidate.mac()); |
| | | detectionCabinet.setChannelCount(candidate.channels().size()); |
| | | validateIpAddress(detectionCabinet.getIpAddress()); |
| | | TDetectionCabinet candidate = detectionCabinetMapper.selectByCabinetCode(detectionCabinet.getCabinetCode()); |
| | | if (candidate == null) |
| | | { |
| | | throw new ServiceException("请先选择发现候选设备"); |
| | | } |
| | | if (!DISCOVERED.equals(candidate.getRegistrationStatus())) |
| | | { |
| | | throw new ServiceException("设备编码已存在"); |
| | | } |
| | | detectionCabinet.setCabinetId(candidate.getCabinetId()); |
| | | validateUniqueFields(detectionCabinet); |
| | | detectionCabinet.setOnlineStatus("OFFLINE"); |
| | | detectionCabinet.setLastSeenAtUtc(null); |
| | | detectionCabinet.setStatus(NORMAL); |
| | | try |
| | | { |
| | | int rows = detectionCabinetMapper.registerDiscoveredCabinet(detectionCabinet); |
| | | if (rows == 0) throw new ServiceException("发现候选设备状态已变化,请重新选择"); |
| | | int rows = detectionCabinetMapper.insertDetectionCabinet(detectionCabinet); |
| | | for (var channel : candidate.channels()) |
| | | { |
| | | TDetectionChannel value = new TDetectionChannel(); |
| | | value.setAdapterId(candidate.adapterId()); |
| | | value.setAdapterCode(candidate.adapterCode()); |
| | | value.setCabinetId(detectionCabinet.getCabinetId()); |
| | | 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("UNKNOWN"); |
| | | value.setWorkStatus("UNKNOWN"); |
| | | value.setLastSeenAtUtc(null); |
| | | detectionCabinetMapper.insertDetectionChannel(value); |
| | | } |
| | | return rows; |
| | | } |
| | | catch (DuplicateKeyException exception) |
| | |
| | | } |
| | | |
| | | /** |
| | | * 批量逻辑删除检测柜;先完成全部目标记录校验,再执行删除,避免部分成功。 |
| | | * 批量取消检测柜登记;先完成全部目标记录校验,再执行操作,避免部分成功。 |
| | | * |
| | | * @param cabinetIds 检测柜主键数组 |
| | | * @param updateBy 更新人 |
| | |
| | | int rows = 0; |
| | | for (Long cabinetId : cabinetIds) |
| | | { |
| | | detectionCabinetMapper.deleteDetectionChannelsByCabinetId(cabinetId); |
| | | rows += detectionCabinetMapper.deleteDetectionCabinetById(cabinetId, updateBy); |
| | | } |
| | | return rows; |
| | |
| | | } |
| | | } |
| | | |
| | | private Map<String, CabinetCandidate> candidatesByCode() |
| | | { |
| | | return candidateStore.snapshot(Instant.now()).cabinets().stream() |
| | | .collect(java.util.stream.Collectors.toMap(CabinetCandidate::deviceCode, item -> item)); |
| | | } |
| | | |
| | | private TDetectionCabinet applyRealtimeState(TDetectionCabinet cabinet, |
| | | Map<String, CabinetCandidate> candidates) |
| | | { |
| | | CabinetCandidate candidate = candidates.get(cabinet.getCabinetCode()); |
| | | cabinet.setOnlineStatus(candidate != null && candidate.online() ? ONLINE : OFFLINE); |
| | | return cabinet; |
| | | } |
| | | |
| | | private boolean isActive(CabinetCandidate candidate) |
| | | { |
| | | return candidate.channels().stream().anyMatch(channel -> ACTIVE_WORK_STATUSES.contains(channel.workStatus())); |
| | | } |
| | | |
| | | private ServiceException buildDuplicateException(TDetectionCabinet detectionCabinet, DuplicateKeyException cause) |
| | | { |
| | | String message = checkCabinetCodeUnique(detectionCabinet) ? "MAC地址已存在" : "设备编码已存在"; |