yeg
2026-08-27 210663040bf65ee00b5f86545fdc31bd0356fddf
ruoyi-system/src/main/java/com/ruoyi/system/service/impl/TDetectionCabinetServiceImpl.java
@@ -4,6 +4,9 @@
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;
@@ -12,9 +15,13 @@
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 业务层处理
@@ -27,19 +34,26 @@
    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;
    }
    /**
@@ -56,7 +70,7 @@
        {
            throw new ServiceException("检测柜不存在或已删除");
        }
        return detectionCabinet;
        return applyRealtimeState(detectionCabinet, candidatesByCode());
    }
    /**
@@ -68,43 +82,46 @@
    @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);
    }
    /**
@@ -114,26 +131,44 @@
     * @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)
@@ -193,7 +228,7 @@
    }
    /**
     * 批量逻辑删除检测柜;先完成全部目标记录校验,再执行删除,避免部分成功。
     * 批量取消检测柜登记;先完成全部目标记录校验,再执行操作,避免部分成功。
     *
     * @param cabinetIds 检测柜主键数组
     * @param updateBy 更新人
@@ -215,6 +250,7 @@
        int rows = 0;
        for (Long cabinetId : cabinetIds)
        {
            detectionCabinetMapper.deleteDetectionChannelsByCabinetId(cabinetId);
            rows += detectionCabinetMapper.deleteDetectionCabinetById(cabinetId, updateBy);
        }
        return rows;
@@ -331,6 +367,25 @@
        }
    }
    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地址已存在" : "设备编码已存在";