yeg
2026-08-30 9c0fd80309b277420c492180ae9f98f779f0f5c8
ruoyi-system/src/main/java/com/ruoyi/system/service/impl/TTestServiceImpl.java
@@ -5,6 +5,7 @@
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Set;
import com.alibaba.fastjson2.JSON;
@@ -25,7 +26,6 @@
import com.ruoyi.system.domain.TTestFieldSnapshot;
import com.ruoyi.system.domain.TTestWarningThreshold;
import com.ruoyi.system.domain.TDetectionChannel;
import com.ruoyi.system.domain.TTestReceptionInterval;
import com.ruoyi.system.domain.vo.TestConfigurationOptionsVo;
import com.ruoyi.system.mapper.TTestMapper;
import com.ruoyi.system.service.ITTestService;
@@ -167,7 +167,6 @@
        if (current.getBatteries().isEmpty()) throw new ServiceException("测试未配置电池号");
        Date actionAt = new Date();
        List<TTestReceptionInterval> intervals = new ArrayList<>();
        Set<Long> channelIds = new HashSet<>();
        Set<String> collectorChannels = new HashSet<>();
        boolean runtimeAttach = false;
@@ -181,10 +180,8 @@
            TDetectionChannel channel = testMapper.selectReceptionChannelForUpdate(binding.getChannelId());
            if (channel == null || !isEnabled(channel.getStatus()) || !isOnline(channel.getOnlineStatus()))
                throw new ServiceException("检测柜通道 " + binding.getChannelId() + " 当前离线或不可观测");
            if (channel.getLastSeenAtUtc() == null)
                throw new ServiceException("检测柜通道 " + binding.getChannelId() + " 尚未取得成功观测边界");
            runtimeAttach |= "WORKING".equalsIgnoreCase(channel.getWorkStatus())
                    || "PAUSED".equalsIgnoreCase(channel.getWorkStatus());
            // 只有设备事实为运行中时才标记“运行中接入”;设备暂停属于独立设备状态。
            runtimeAttach |= "WORKING".equalsIgnoreCase(channel.getWorkStatus());
            if (testMapper.selectChannelLeaseForUpdate(binding.getChannelId()) != null)
                throw new ServiceException("检测柜通道 " + binding.getChannelId() + " 已被其他测试占用");
            if (battery.getCollectorId() == null || testMapper.countCollectorEnabled(battery.getCollectorId()) == 0)
@@ -195,14 +192,6 @@
            validateCollectorChannels(battery.getCollectorId(), collectorChannelCount == null ? 0 : collectorChannelCount,
                    battery.getCollectorChannels(), collectorChannels);
            intervals.add(interval(current, binding.getBindingId(), "CABINET_CHANNEL", binding.getChannelId(), null,
                    actionAt, Date.from(channel.getLastSeenAtUtc()), runtimeAttach
                            ? "运行中接入:采用检测柜最近成功观测边界" : "采用检测柜最近成功观测边界"));
            for (TTestCollectorChannel mapping : mutable(battery.getCollectorChannels()))
            {
                intervals.add(interval(current, binding.getBindingId(), "COLLECTOR_CHANNEL", battery.getCollectorId(),
                        mapping.getChannelCode(), actionAt, actionAt, "采集器开始接收确认边界"));
            }
        }
        try
@@ -211,16 +200,20 @@
            {
                TTestChannelBinding binding = battery.getBinding();
                testMapper.insertChannelLease(binding.getChannelId(), testId, binding.getBindingId(), operatorId, actionAt);
                testMapper.insertReceptionInterval(testId, binding.getBindingId(), "CABINET_CHANNEL",
                        binding.getChannelId(), null, actionAt, "RECEPTION_STARTED");
                for (TTestCollectorChannel mapping : mutable(battery.getCollectorChannels()))
                {
                    testMapper.insertCollectorChannelLease(battery.getCollectorId(), mapping.getChannelCode(), testId,
                            binding.getBindingId(), operatorId, actionAt);
                    testMapper.insertReceptionInterval(testId, binding.getBindingId(), "COLLECTOR_CHANNEL",
                            battery.getCollectorId(), mapping.getChannelCode(), actionAt, "RECEPTION_STARTED");
                }
            }
            for (TTestReceptionInterval interval : intervals) testMapper.insertReceptionInterval(interval);
            if (testMapper.updateReceptionStarted(testId, actionAt, updateBy) != 1)
            if (testMapper.updateReceptionStarted(testId, actionAt, runtimeAttach, updateBy) != 1)
                throw new ServiceException("测试状态已变更,请刷新后重试");
            testMapper.insertTestAudit(testId, "RECEPTION_STARTED", operatorId, updateBy, null,
                    JSON.toJSONString(Map.of("receptionStatus", "RECEIVING", "intervalCount", intervals.size(),
                            "runtimeAttach", runtimeAttach, "intervals", intervals)));
                    JSON.toJSONString(Map.of("receptionStatus", "RECEIVING", "runtimeAttach", runtimeAttach)));
            return 1;
        }
        catch (DuplicateKeyException exception)
@@ -229,20 +222,74 @@
        }
    }
    private TTestReceptionInterval interval(TTest test, Long bindingId, String sourceType, Long sourceId,
            String sourceChannelCode, Date actionAt, Date boundaryAt, String note)
    @Override
    @Transactional(rollbackFor = Exception.class)
    public int pauseReception(Long testId, Long operatorId, String updateBy)
    {
        TTestReceptionInterval interval = new TTestReceptionInterval();
        interval.setTestId(test.getTestId());
        interval.setBindingId(bindingId);
        interval.setSourceType(sourceType);
        interval.setSourceId(sourceId);
        interval.setSourceChannelCode(sourceChannelCode);
        interval.setActionAtUtc(actionAt);
        interval.setStartBoundaryAtUtc(boundaryAt);
        interval.setBoundaryNote(note);
        interval.setIntervalStatus("OPEN");
        return interval;
        if (testId == null) throw new ServiceException("测试ID不能为空");
        if (operatorId == null) throw new ServiceException("操作人不能为空");
        TTest current = testMapper.selectTestByIdForUpdate(testId);
        if (current == null) throw new ServiceException("测试不存在");
        if (!"RECEIVING".equals(current.getReceptionStatus()))
            throw new ServiceException("当前测试不是接收中状态");
        Date actionAt = new Date();
        if (testMapper.updateReceptionPaused(testId, updateBy, actionAt) != 1)
            throw new ServiceException("测试状态已变更,请刷新后重试");
        // 暂停边界不属于本次测试的确认接收区间,继续时由新的区间重新开始计时。
        testMapper.closeOpenReceptionIntervals(testId, actionAt, "RECEPTION_PAUSED");
        testMapper.insertTestAudit(testId, "RECEPTION_PAUSED", operatorId, updateBy, null,
                JSON.toJSONString(Map.of("receptionStatus", "PAUSED", "polling", "STOPPED")));
        return 1;
    }
    @Override
    @Transactional(rollbackFor = Exception.class)
    public int resumeReception(Long testId, Long operatorId, String updateBy)
    {
        if (testId == null) throw new ServiceException("测试ID不能为空");
        if (operatorId == null) throw new ServiceException("操作人不能为空");
        TTest current = testMapper.selectTestByIdForUpdate(testId);
        if (current == null) throw new ServiceException("测试不存在");
        if (!"PAUSED".equals(current.getReceptionStatus()))
            throw new ServiceException("当前测试不是暂停接收状态");
        Date actionAt = new Date();
        if (testMapper.updateReceptionResumed(testId, updateBy, actionAt) != 1)
            throw new ServiceException("测试状态已变更,请刷新后重试");
        for (TTestBattery battery : current.getBatteries())
        {
            TTestChannelBinding binding = battery.getBinding();
            testMapper.insertReceptionInterval(testId, binding.getBindingId(), "CABINET_CHANNEL",
                    binding.getChannelId(), null, actionAt, "RECEPTION_RESUMED");
            for (TTestCollectorChannel mapping : mutable(battery.getCollectorChannels()))
                testMapper.insertReceptionInterval(testId, binding.getBindingId(), "COLLECTOR_CHANNEL",
                        battery.getCollectorId(), mapping.getChannelCode(), actionAt, "RECEPTION_RESUMED");
        }
        testMapper.insertTestAudit(testId, "RECEPTION_RESUMED", operatorId, updateBy, null,
                JSON.toJSONString(Map.of("receptionStatus", "RECEIVING", "polling", "RESUMED")));
        return 1;
    }
    @Override
    @Transactional(rollbackFor = Exception.class)
    public int stopTest(Long testId, Long operatorId, String updateBy)
    {
        if (testId == null) throw new ServiceException("测试ID不能为空");
        if (operatorId == null) throw new ServiceException("操作人不能为空");
        TTest current = testMapper.selectTestByIdForUpdate(testId);
        if (current == null) throw new ServiceException("测试不存在");
        if (NOT_STARTED.equals(current.getReceptionStatus()) || "ENDED".equals(current.getReceptionStatus()))
            throw new ServiceException("当前测试不能停止");
        Date actionAt = new Date();
        if (testMapper.updateReceptionStopped(testId, updateBy, actionAt) != 1)
            throw new ServiceException("测试状态已变更,请刷新后重试");
        int closedIntervals = testMapper.closeOpenReceptionIntervals(testId, actionAt, "MANUAL_END");
        testMapper.deleteChannelLeasesByTestId(testId);
        testMapper.deleteCollectorChannelLeasesByTestId(testId);
        testMapper.insertTestAudit(testId, "TEST_STOPPED", operatorId, updateBy, null,
                JSON.toJSONString(Map.of("testStatus", "COMPLETED", "receptionStatus", "ENDED",
                        "polling", "STOPPED", "closedIntervals", closedIntervals, "release", "IMMEDIATE")));
        return 1;
    }
    private boolean isOnline(String status)
@@ -332,6 +379,8 @@
        test.setBatteryNo(joinBatteryNos(batteries));
        List<TTestWarningThreshold> loadedThresholds = testMapper.selectTestThresholds(testId);
        test.setThresholds(loadedThresholds == null ? mutable(test.getThresholds()) : mutable(loadedThresholds));
        List<com.ruoyi.system.domain.TTestReceptionInterval> intervals = testMapper.selectTestReceptionIntervals(testId);
        test.setReceptionIntervals(intervals == null ? List.of() : intervals);
        List<TTestFieldSnapshot> loadedSnapshots = sharedFieldSnapshotService.selectByTestId(testId);
        test.setFieldSnapshots(loadedSnapshots == null ? mutable(test.getFieldSnapshots()) : loadedSnapshots);
    }
@@ -416,7 +465,7 @@
        for (TTestCollectorChannel mapping : mappings)
        {
            if (mapping == null) throw new ServiceException("采集器通道映射不完整");
            mapping.setMeasurementCode(trimToNull(mapping.getMeasurementCode()));
            mapping.setMeasurementCode(normalizeMeasurementCode(mapping.getMeasurementCode()));
            mapping.setMeasurementName(trimToNull(mapping.getMeasurementName()));
            mapping.setChannelCode(normalizeCollectorChannel(mapping.getChannelCode()));
            if (mapping.getMeasurementCode() == null || mapping.getMeasurementName() == null
@@ -575,6 +624,13 @@
        }
    }
    /** 测量编码与数据库不区分大小写排序保持一致,避免校验通过后插入冲突。 */
    private String normalizeMeasurementCode(String value)
    {
        String normalized = trimToNull(value);
        return normalized == null ? null : normalized.toUpperCase(Locale.ROOT);
    }
    private <T> List<T> mutable(List<T> values)
    {
        return values == null ? new ArrayList<>() : new ArrayList<>(values);