yeg
2026-09-01 3b0d51d628c772c47845d9fd0b95ebde8a969d07
ruoyi-system/src/main/java/com/ruoyi/system/service/impl/TestConsoleDataServiceImpl.java
@@ -28,6 +28,24 @@
    private TTestMapper mapper;
    @Override
    public List<TTestDataRevisionAction> selectActions(TTestDataRevisionAction query)
    {
        if (query == null) query = new TTestDataRevisionAction();
        if (query.getDataType() != null && !query.getDataType().isBlank()
                && !DATA_TYPES.contains(query.getDataType())) throw new ServiceException("控制台数据类型无效");
        if (query.getOperationType() != null && !query.getOperationType().isBlank()
                && !OPERATIONS.contains(query.getOperationType())) throw new ServiceException("数据操作类型无效");
        return mapper.selectDataRevisionActions(query);
    }
    @Override
    public TTestDataRevisionAction selectActionById(Long actionId)
    {
        if (actionId == null || actionId < 1) throw new ServiceException("修订行为ID无效");
        return mapper.selectDataRevisionActionById(actionId);
    }
    @Override
    public List<TTestDataOverride> selectOverrides(Long testId, String dataType)
    {
        validateType(testId, dataType);
@@ -50,6 +68,7 @@
        }
        if ("EDIT".equals(action.getOperationType())) validateEditableFields(action);
        if (action.getReason() == null || action.getReason().trim().isEmpty()) throw new ServiceException("数据操作原因不能为空");
        validateActionSummaries(action, overrides);
        if (action.getOperatorName() == null || action.getOperatorName().trim().isEmpty()) action.setOperatorName("未知用户");
        action.setReason(action.getReason().trim());
        action.setCreatedAtUtc(new Date());
@@ -95,18 +114,31 @@
                mapper.upsertDataOverride(item);
            }
        }
        if ("DELETE".equals(action.getOperationType()) && "REALTIME".equals(action.getDataType()) && overrides != null)
        if ("DELETE".equals(action.getOperationType()) && overrides != null)
        {
            List<Long> resolvedIds = new ArrayList<>();
            for (TTestDataOverride item : overrides)
            {
                Long[] identity = parseRealtimeIdentity(item.getStableIdentity());
                if (identity == null) continue;
                Long sampleId = mapper.selectBtsSampleIdByChannelSequence(action.getTestId(), identity[0], identity[1]);
                Long[] identity = parseDeleteIdentity(item.getStableIdentity(), action.getDataType());
                if (identity == null) throw new ServiceException("删除数据稳定身份无效");
                switch (action.getDataType())
                {
                    case "REALTIME" -> {
                        Long sampleId = mapper.selectBtsSampleIdByDeviceChannelSequence(action.getTestId(),
                                identity[0], identity[1], identity[2]);
                if (sampleId != null) resolvedIds.add(sampleId);
                    }
                    case "STEP" -> resolvedIds.addAll(mapper.selectBtsSampleIdsByDeviceChannelStep(action.getTestId(),
                            identity[0], identity[1], identity[2].intValue(), identity[3].intValue()));
                    case "CYCLE" -> resolvedIds.addAll(mapper.selectBtsSampleIdsByDeviceChannelCycle(action.getTestId(),
                            identity[0], identity[1], identity[2].intValue()));
                    default -> throw new ServiceException("控制台数据类型无效");
                }
            }
            if (!resolvedIds.isEmpty())
            {
                resolvedIds = new ArrayList<>(new HashSet<>(resolvedIds));
                // 工步和循环是采样聚合视图,物理删除需在同一事务内清理对应底层采样。
                mapper.deleteBtsMeasurementsBySampleIds(resolvedIds);
                mapper.deleteBtsSamplesByIds(action.getTestId(), resolvedIds);
            }
@@ -175,6 +207,43 @@
        catch (Exception exception) { throw new ServiceException("修改字段格式无效"); }
    }
    /** 防止客户端提交与实际覆盖身份不一致的伪行为摘要。 */
    private void validateActionSummaries(TTestDataRevisionAction action, List<TTestDataOverride> overrides)
    {
        if (action.getScopeSummary() == null || action.getScopeSummary().trim().isEmpty())
            throw new ServiceException("数据操作范围不能为空");
        Set<String> summaryIdentities = new HashSet<>();
        try
        {
            Object parsed = JSON.parse(action.getIdentitySummary());
            if (!(parsed instanceof List<?> values)) throw new ServiceException("身份摘要格式无效");
            for (Object value : values)
                if (value == null || !summaryIdentities.add(String.valueOf(value)))
                    throw new ServiceException("身份摘要存在重复或空值");
        }
        catch (ServiceException exception) { throw exception; }
        catch (Exception exception) { throw new ServiceException("身份摘要格式无效"); }
        if (overrides == null) return;
        Set<String> overrideIdentities = new HashSet<>();
        for (TTestDataOverride item : overrides)
            if (item == null || !overrideIdentities.add(item.getStableIdentity())
                    || !summaryIdentities.contains(item.getStableIdentity()))
                throw new ServiceException("身份摘要与数据覆盖明细不一致");
        if (!summaryIdentities.equals(overrideIdentities))
            throw new ServiceException("身份摘要与数据覆盖明细不一致");
        if ("EDIT".equals(action.getOperationType()))
        {
            try
            {
                Object parsed = JSON.parse(action.getChangesSummary());
                if (!(parsed instanceof List<?> changes) || changes.size() < overrides.size())
                    throw new ServiceException("字段变化摘要与数据覆盖明细不一致");
            }
            catch (ServiceException exception) { throw exception; }
            catch (Exception exception) { throw new ServiceException("字段变化摘要格式无效"); }
        }
    }
    private Set<String> editableFields(String dataType)
    {
        return switch (dataType)
@@ -188,11 +257,20 @@
        };
    }
    private Long[] parseRealtimeIdentity(String value)
    private Long[] parseDeleteIdentity(String value, String dataType)
    {
        String[] parts = value.split(":", -1);
        if (parts.length != 3) return null;
        try { return new Long[] { Long.valueOf(parts[0]), Long.valueOf(parts[2]) }; }
        int expectedLength = "STEP".equals(dataType) ? 5 : "CYCLE".equals(dataType) ? 4 : 3;
        if (parts.length != expectedLength) return null;
        int offset = "REALTIME".equals(dataType) ? 0 : 1;
        try
        {
            if ("STEP".equals(dataType))
                return new Long[] { Long.valueOf(parts[offset]), Long.valueOf(parts[offset + 1]),
                        Long.valueOf(parts[offset + 2]), Long.valueOf(parts[offset + 3]) };
            return new Long[] { Long.valueOf(parts[offset]), Long.valueOf(parts[offset + 1]),
                    Long.valueOf(parts[offset + 2]) };
        }
        catch (NumberFormatException exception) { return null; }
    }