yeg
2026-08-29 41cfe20aeb9835315c54b3d6873d4e07ff238cfa
ruoyi-system/src/main/java/com/ruoyi/system/service/impl/TTestServiceImpl.java
@@ -15,30 +15,39 @@
import com.ruoyi.common.exception.ServiceException;
import com.ruoyi.common.utils.StringUtils;
import com.ruoyi.system.domain.TTest;
import com.ruoyi.system.domain.TBatteryGroup;
import com.ruoyi.system.domain.TBatteryGroupRelationField;
import com.ruoyi.system.domain.TTestBattery;
import com.ruoyi.system.domain.TTestBatteryField;
import com.ruoyi.system.domain.TTestChannelBinding;
import com.ruoyi.system.domain.TTestCollectorChannel;
import com.ruoyi.system.domain.TTestFieldSnapshot;
import com.ruoyi.system.domain.TTestPlanStep;
import com.ruoyi.system.domain.TTestResult;
import com.ruoyi.system.domain.TTestWarningThreshold;
import com.ruoyi.system.domain.vo.TestConfigurationOptionsVo;
import com.ruoyi.system.mapper.TTestMapper;
import com.ruoyi.system.service.ITTestService;
import com.ruoyi.system.service.ITBatteryGroupService;
/** 测试配置服务,负责状态边界、关系校验、事务写入和只追加审计。 */
@Service
public class TTestServiceImpl implements ITTestService
{
    private static final String WAITING = "WAITING";
    private static final String NOT_MONITORED = "NOT_MONITORED";
    private static final String NOT_STARTED = "NOT_STARTED";
    private static final Set<String> TEST_STATUSES = Set.of(
            WAITING, "RUNNING", "PAUSED", "RUNNING_ABNORMAL", "COMPLETION_PENDING", "COMPLETED", "TERMINATED");
    private static final Set<String> MONITORING_STATUSES = Set.of(
            NOT_MONITORED, "WAITING_DEVICE_START", "MONITORING", "BACKFILLING", "MONITORING_ENDED", "CANCELLED");
    private static final Set<String> RECEPTION_STATUSES = Set.of(NOT_STARTED, "RECEIVING", "PAUSED", "ENDED");
    private static final Set<String> THRESHOLD_UNITS = Set.of("A", "V", "Ah", "Wh", "℃");
    private static final Set<String> REQUIRED_COLLECTOR_MEASUREMENTS = Set.of(
            "PRIMARY_TEMPERATURE", "PRIMARY_POSITIVE_POTENTIAL", "PRIMARY_NEGATIVE_POTENTIAL");
    @Autowired
    private TTestMapper testMapper;
    @Autowired
    private ITBatteryGroupService batteryGroupService;
    @Override
    public TTest selectTestById(Long testId)
@@ -57,7 +66,7 @@
        test.setTestName(trimToNull(test.getTestName()));
        test.setBatteryNo(trimToNull(test.getBatteryNo()));
        validateQueryStatus(test.getTestStatus(), TEST_STATUSES, "测试状态无效");
        validateQueryStatus(test.getMonitoringStatus(), MONITORING_STATUSES, "监测状态无效");
        validateQueryStatus(test.getReceptionStatus(), RECEPTION_STATUSES, "接收状态无效");
        return testMapper.selectTestList(test);
    }
@@ -83,7 +92,8 @@
        validateBase(test, false);
        validateConfiguration(test);
        test.setTestStatus(WAITING);
        test.setMonitoringStatus(NOT_MONITORED);
        test.setReceptionStatus(NOT_STARTED);
        test.setFieldSnapshots(snapshotGroupFields(test.getBatteries().get(0).getGroupId()));
        test.setDelFlag("0");
        test.setCreateTime(new Date());
        testMapper.insertTest(test);
@@ -101,26 +111,29 @@
        TTest current = testMapper.selectTestByIdForUpdate(test.getTestId());
        if (current == null) throw new ServiceException("测试不存在");
        loadConfiguration(current);
        if (!NOT_MONITORED.equals(current.getMonitoringStatus()))
            throw new ServiceException("开始监测后不能修改测试配置");
        if (!NOT_STARTED.equals(current.getReceptionStatus()))
            throw new ServiceException("开始接收后不能修改测试配置");
        test.setCreatorId(current.getCreatorId());
        test.setTestStatus(current.getTestStatus());
        test.setMonitoringStatus(current.getMonitoringStatus());
        test.setReceptionStatus(current.getReceptionStatus());
        if (test.getMonitorTimeoutMinutes() == null)
            test.setMonitorTimeoutMinutes(current.getMonitorTimeoutMinutes());
        validateBase(test, true);
        validateConfiguration(test);
        test.setFieldSnapshots(resolveUpdatedFieldSnapshots(test, current));
        test.setUpdateTime(new Date());
        int rows = testMapper.updateTest(test);
        if (rows != 1) throw new ServiceException("测试配置已变更,请刷新后重试");
        // 监测开始前没有原始采集事实,配置关系可在同一事务内整体替换,审计记录仍只追加。
        // 开始接收前没有数据归属事实,配置关系可在同一事务内整体替换,审计记录仍只追加。
        testMapper.deleteTestCollectorChannelsByTestId(test.getTestId());
        testMapper.deleteTestBindingsByTestId(test.getTestId());
        testMapper.deleteTestPlanStepsByTestId(test.getTestId());
        testMapper.deleteTestFieldsByTestId(test.getTestId());
        testMapper.deleteTestBatteriesByTestId(test.getTestId());
        testMapper.deleteTestThresholdsByTestId(test.getTestId());
        testMapper.deleteTestFieldSnapshotsByTestId(test.getTestId());
        saveConfiguration(test);
        testMapper.insertTestAudit(test.getTestId(), "TEST_CONFIGURATION_UPDATED", test.getOperatorId(),
                test.getUpdateBy(), JSON.toJSONString(current), JSON.toJSONString(test));
@@ -130,7 +143,7 @@
    @Override
    public int startTest(Long testId, String updateBy)
    {
        throw new ServiceException("开始监测由后续监测动作处理");
        throw new ServiceException("开始接收由后续原子动作处理");
    }
    @Override
@@ -143,7 +156,7 @@
        {
            TTest current = testMapper.selectTestByIdForUpdate(testId);
            if (current == null) throw new ServiceException("测试不存在");
            if (!NOT_MONITORED.equals(current.getMonitoringStatus())) throw new ServiceException("开始监测后不能删除测试");
            if (!NOT_STARTED.equals(current.getReceptionStatus())) throw new ServiceException("开始接收后不能删除测试");
            loadConfiguration(current);
            int deleted = testMapper.deleteTestById(testId, updateBy);
            if (deleted != 1) throw new ServiceException("测试配置已变更,请刷新后重试");
@@ -180,6 +193,7 @@
        List<TTestBattery> batteries = mutable(testMapper.selectTestBatteries(testId));
        List<TTestBatteryField> fields = mutable(testMapper.selectTestBatteryFields(testId));
        List<TTestCollectorChannel> collectorChannels = mutable(testMapper.selectTestCollectorChannels(testId));
        Map<Long, TTestChannelBinding> bindings = new HashMap<>();
        for (TTestChannelBinding binding : mutable(testMapper.selectTestChannelBindings(testId)))
            bindings.put(binding.getBatteryRelationId(), binding);
@@ -189,10 +203,14 @@
            battery.setFields(fields.stream()
                    .filter(field -> battery.getRelationId().equals(field.getRelationId()))
                    .toList());
            battery.setCollectorChannels(collectorChannels.stream()
                    .filter(mapping -> battery.getRelationId().equals(mapping.getBatteryRelationId()))
                    .toList());
        }
        test.setPlanSteps(planSteps);
        test.setBatteries(batteries);
        test.setThresholds(mutable(testMapper.selectTestThresholds(testId)));
        test.setFieldSnapshots(mutable(testMapper.selectTestFieldSnapshots(testId)));
    }
    private void validateBase(TTest test, boolean update)
@@ -220,7 +238,7 @@
    private void validateConfiguration(TTest test)
    {
        // 新建测试只建立业务身份和通道关系,计划工步可在开始监测前补充。
        // 新建测试只建立业务身份和通道关系,计划工步可在开始接收前补充。
        if (test.getPlanSteps() == null) test.setPlanSteps(new ArrayList<>());
        validateBatteries(test.getBatteries());
        validatePlanSteps(test.getPlanSteps());
@@ -232,6 +250,8 @@
        if (batteries == null || batteries.isEmpty()) throw new ServiceException("测试至少绑定一个电池号");
        Set<String> batteryNos = new HashSet<>();
        Set<Long> channelIds = new HashSet<>();
        Set<String> collectorChannelIdentities = new HashSet<>();
        Map<Long, Integer> collectorChannelCounts = new HashMap<>();
        Long groupId = null;
        for (TTestBattery battery : batteries)
        {
@@ -252,10 +272,45 @@
            binding.setExpectedBarcode(trimToNull(binding.getExpectedBarcode()));
            if (binding.getExpectedBarcode() != null && binding.getExpectedBarcode().length() > 100)
                throw new ServiceException("预期条码长度不能超过100个字符");
            if (battery.getCollectorId() != null && testMapper.countCollectorEnabled(battery.getCollectorId()) == 0)
            if (battery.getCollectorId() == null) throw new ServiceException("采集器不能为空");
            if (testMapper.countCollectorEnabled(battery.getCollectorId()) == 0)
                throw new ServiceException("采集器不存在或未启用");
            Integer channelCount = collectorChannelCounts.computeIfAbsent(battery.getCollectorId(),
                    testMapper::selectCollectorChannelCount);
            if (channelCount == null || channelCount < 1) throw new ServiceException("采集器通道配置无效");
            validateCollectorChannels(battery.getCollectorId(), channelCount, battery.getCollectorChannels(),
                    collectorChannelIdentities);
            validateBatteryFields(battery.getFields());
        }
    }
    private void validateCollectorChannels(Long collectorId, int channelCount, List<TTestCollectorChannel> mappings,
            Set<String> collectorChannelIdentities)
    {
        if (mappings == null || mappings.isEmpty())
            throw new ServiceException("每个电池号必须绑定主温度、正电位主通道和负电位主通道");
        Set<String> measurementCodes = new HashSet<>();
        for (TTestCollectorChannel mapping : mappings)
        {
            if (mapping == null) throw new ServiceException("采集器通道映射不完整");
            mapping.setMeasurementCode(trimToNull(mapping.getMeasurementCode()));
            mapping.setMeasurementName(trimToNull(mapping.getMeasurementName()));
            mapping.setChannelCode(normalizeCollectorChannel(mapping.getChannelCode()));
            if (mapping.getMeasurementCode() == null || mapping.getMeasurementName() == null
                    || mapping.getChannelCode() == null)
                throw new ServiceException("采集器通道映射不完整");
            if (mapping.getMeasurementCode().length() > 100 || mapping.getMeasurementName().length() > 100)
                throw new ServiceException("采集器测量项长度不能超过100个字符");
            int channelNumber = Integer.parseInt(mapping.getChannelCode().substring(2));
            if (channelNumber < 1 || channelNumber > channelCount)
                throw new ServiceException("采集器通道不存在或超出登记范围");
            if (!measurementCodes.add(mapping.getMeasurementCode()))
                throw new ServiceException("同一电池号的采集器测量项不可重复");
            if (!collectorChannelIdentities.add(collectorId + ":" + mapping.getChannelCode()))
                throw new ServiceException("同一采集器通道不可重复映射");
        }
        if (!measurementCodes.containsAll(REQUIRED_COLLECTOR_MEASUREMENTS))
            throw new ServiceException("每个电池号必须绑定主温度、正电位主通道和负电位主通道");
    }
    private void validateBatteryFields(List<TTestBatteryField> fields)
@@ -269,6 +324,41 @@
            if (field.getFieldName() == null || !fieldNames.add(field.getFieldName()))
                throw new ServiceException("扩展属性名称不能为空且不可重复");
        }
    }
    private List<TTestFieldSnapshot> resolveUpdatedFieldSnapshots(TTest test, TTest current)
    {
        Long nextGroupId = test.getBatteries().get(0).getGroupId();
        Long currentGroupId = current.getBatteries().isEmpty() ? null : current.getBatteries().get(0).getGroupId();
        if (nextGroupId.equals(currentGroupId))
            return mutable(current.getFieldSnapshots()).stream().map(this::copySnapshot).toList();
        return snapshotGroupFields(nextGroupId);
    }
    private List<TTestFieldSnapshot> snapshotGroupFields(Long groupId)
    {
        TBatteryGroup group = batteryGroupService.selectBatteryGroupById(groupId);
        List<TTestFieldSnapshot> snapshots = new ArrayList<>();
        for (TBatteryGroupRelationField field : mutable(group.getTemplateFields()))
        {
            TTestFieldSnapshot snapshot = new TTestFieldSnapshot();
            snapshot.setFieldDefinitionId(field.getBatteryGroupFieldId());
            snapshot.setFieldName(field.getFieldName());
            snapshot.setFieldType(field.getFieldType());
            snapshot.setFieldValue(field.getFieldValue());
            snapshots.add(snapshot);
        }
        return snapshots;
    }
    private TTestFieldSnapshot copySnapshot(TTestFieldSnapshot source)
    {
        TTestFieldSnapshot copy = new TTestFieldSnapshot();
        copy.setFieldDefinitionId(source.getFieldDefinitionId());
        copy.setFieldName(source.getFieldName());
        copy.setFieldType(source.getFieldType());
        copy.setFieldValue(source.getFieldValue());
        return copy;
    }
    private void validatePlanSteps(List<TTestPlanStep> planSteps)
@@ -338,6 +428,18 @@
            binding.setBatteryRelationId(battery.getRelationId());
            binding.setClaimStatus("PENDING");
            testMapper.insertTestChannelBinding(binding);
            for (TTestCollectorChannel mapping : battery.getCollectorChannels())
            {
                mapping.setTestId(test.getTestId());
                mapping.setBatteryRelationId(battery.getRelationId());
                mapping.setCollectorId(battery.getCollectorId());
                testMapper.insertTestCollectorChannel(mapping);
            }
        }
        for (TTestFieldSnapshot snapshot : mutable(test.getFieldSnapshots()))
        {
            snapshot.setTestId(test.getTestId());
            testMapper.insertTestFieldSnapshot(snapshot);
        }
        for (TTestWarningThreshold threshold : test.getThresholds())
        {
@@ -357,6 +459,22 @@
        return StringUtils.isEmpty(trimmed) ? null : trimmed;
    }
    private String normalizeCollectorChannel(String value)
    {
        String normalized = trimToNull(value);
        if (normalized == null || !normalized.toUpperCase().matches("S-\\d+"))
            throw new ServiceException("采集器通道必须使用S-01...S-N演示编码");
        try
        {
            int channelNumber = Integer.parseInt(normalized.substring(2));
            return String.format("S-%02d", channelNumber);
        }
        catch (NumberFormatException exception)
        {
            throw new ServiceException("采集器通道必须使用S-01...S-N演示编码");
        }
    }
    private <T> List<T> mutable(List<T> values)
    {
        return values == null ? new ArrayList<>() : new ArrayList<>(values);