yeg
2026-09-03 e35881f24e349e865d7a1dbbebc1a7e55ecc7a16
实现测试详情持久化接口
2个文件已添加
7个文件已修改
115 ■■■■■ 已修改文件
ruoyi-admin/src/main/java/com/ruoyi/web/controller/system/TTestController.java 10 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-admin/src/main/resources/db/migration/V32__test_console_detail.sql 3 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-admin/src/test/java/com/ruoyi/web/controller/system/TTestControllerContractTest.java 5 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-system/src/main/java/com/ruoyi/system/domain/TTest.java 7 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-system/src/main/java/com/ruoyi/system/domain/TTestConsoleDetail.java 23 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-system/src/main/java/com/ruoyi/system/mapper/TTestMapper.java 2 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-system/src/main/java/com/ruoyi/system/service/ITTestService.java 3 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-system/src/main/java/com/ruoyi/system/service/impl/TTestServiceImpl.java 56 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-system/src/main/resources/mapper/system/TTestMapper.xml 6 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-admin/src/main/java/com/ruoyi/web/controller/system/TTestController.java
@@ -21,6 +21,7 @@
import com.ruoyi.system.domain.TTest;
import com.ruoyi.system.domain.TTestResult;
import com.ruoyi.system.domain.TTestLogicStepMapping;
import com.ruoyi.system.domain.TTestConsoleDetail;
import com.ruoyi.system.domain.TTestDataRevisionAction;
import com.ruoyi.system.domain.TTestDataOverride;
import com.ruoyi.system.service.ITTestService;
@@ -157,6 +158,15 @@
        return success(testService.selectTestById(testId));
    }
    /** 保存工步图表测试详情当前最新版,不参与测试配置、统计或审计。 */
    @PreAuthorize("@ss.hasPermi('system:test:edit')")
    @PutMapping("/{testId}/console-detail")
    public AjaxResult saveConsoleDetail(@PathVariable Long testId, @RequestBody TTestConsoleDetail detail)
    {
        testService.saveConsoleDetail(testId, detail, getUserId(), getUsername());
        return success(testService.selectTestById(testId));
    }
    /** 返回当前测试某类数据的人工覆盖和删除状态,供刷新后恢复展示。 */
    @PreAuthorize("@ss.hasPermi('system:test:query')")
    @GetMapping("/{testId}/console-data/{dataType}/overrides")
ruoyi-admin/src/main/resources/db/migration/V32__test_console_detail.sql
New file
@@ -0,0 +1,3 @@
-- Ticket03:工步图表测试详情只保留当前测试的最新版,不建立版本或审计记录。
ALTER TABLE t_test
    ADD COLUMN console_detail_json JSON NULL COMMENT '工步图表测试详情当前记录' AFTER logic_step_mappings_json;
ruoyi-admin/src/test/java/com/ruoyi/web/controller/system/TTestControllerContractTest.java
@@ -4,6 +4,7 @@
import java.lang.reflect.Method;
import org.junit.jupiter.api.Test;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.PutMapping;
import org.springframework.security.access.prepost.PreAuthorize;
class TTestControllerContractTest
@@ -39,6 +40,10 @@
        assertPermission("persistedCycles", "@ss.hasPermi('system:test:query')", Long.class);
        assertEquals("/{testId}/data/cycles", TTestController.class.getMethod("persistedCycles", Long.class)
                .getAnnotation(org.springframework.web.bind.annotation.GetMapping.class).value()[0]);
        assertPermission("saveConsoleDetail", "@ss.hasPermi('system:test:edit')", Long.class,
                com.ruoyi.system.domain.TTestConsoleDetail.class);
        assertEquals("/{testId}/console-detail", TTestController.class.getMethod("saveConsoleDetail", Long.class,
                com.ruoyi.system.domain.TTestConsoleDetail.class).getAnnotation(PutMapping.class).value()[0]);
    }
    private void assertPermission(String methodName, String permission, Class<?>... parameterTypes) throws Exception
ruoyi-system/src/main/java/com/ruoyi/system/domain/TTest.java
@@ -58,6 +58,9 @@
    private List<TTestResult> resultSummary;
    private String logicStepMappingsJson;
    private List<TTestLogicStepMapping> logicStepMappings;
    /** 工步图表中的测试补充详情,仅保存当前最新版。 */
    private String consoleDetailJson;
    private TTestConsoleDetail consoleDetail;
    public Long getTestId() { return testId; }
    public void setTestId(Long value) { testId = value; }
@@ -115,4 +118,8 @@
    public void setLogicStepMappingsJson(String value) { logicStepMappingsJson = value; }
    public List<TTestLogicStepMapping> getLogicStepMappings() { return logicStepMappings; }
    public void setLogicStepMappings(List<TTestLogicStepMapping> value) { logicStepMappings = value; }
    public String getConsoleDetailJson() { return consoleDetailJson; }
    public void setConsoleDetailJson(String value) { consoleDetailJson = value; }
    public TTestConsoleDetail getConsoleDetail() { return consoleDetail; }
    public void setConsoleDetail(TTestConsoleDetail value) { consoleDetail = value; }
}
ruoyi-system/src/main/java/com/ruoyi/system/domain/TTestConsoleDetail.java
New file
@@ -0,0 +1,23 @@
package com.ruoyi.system.domain;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
/** 工步图表中的测试补充详情,仅保留当前测试的一份记录。 */
public class TTestConsoleDetail
{
    private String experimentName;
    private List<String> batteryFields = new java.util.ArrayList<>();
    private List<String> specialFields = new java.util.ArrayList<>();
    private Map<String, Map<String, String>> fieldValues = new LinkedHashMap<>();
    public String getExperimentName() { return experimentName; }
    public void setExperimentName(String value) { experimentName = value; }
    public List<String> getBatteryFields() { return batteryFields; }
    public void setBatteryFields(List<String> value) { batteryFields = value; }
    public List<String> getSpecialFields() { return specialFields; }
    public void setSpecialFields(List<String> value) { specialFields = value; }
    public Map<String, Map<String, String>> getFieldValues() { return fieldValues; }
    public void setFieldValues(Map<String, Map<String, String>> value) { fieldValues = value == null ? new LinkedHashMap<>() : value; }
}
ruoyi-system/src/main/java/com/ruoyi/system/mapper/TTestMapper.java
@@ -75,6 +75,8 @@
    String selectLogicStepMappingsJson(Long testId);
    int updateLogicStepMappings(@Param("testId") Long testId, @Param("mappingsJson") String mappingsJson,
            @Param("updateBy") String updateBy, @Param("updateTime") Date updateTime);
    int updateConsoleDetail(@Param("testId") Long testId, @Param("detailJson") String detailJson,
            @Param("updateBy") String updateBy, @Param("updateTime") Date updateTime);
    int countTestTypeEnabled(Long testTypeId);
    int countUserEnabled(Long userId);
    int countBatteryInEnabledGroup(@Param("groupId") Long groupId, @Param("batteryNo") String batteryNo);
ruoyi-system/src/main/java/com/ruoyi/system/service/ITTestService.java
@@ -5,6 +5,7 @@
import com.ruoyi.system.domain.TTestResult;
import com.ruoyi.system.domain.vo.TestConfigurationOptionsVo;
import com.ruoyi.system.domain.TTestLogicStepMapping;
import com.ruoyi.system.domain.TTestConsoleDetail;
/** 测试配置业务接口。 */
public interface ITTestService
@@ -46,5 +47,7 @@
    List<TTestResult> selectTestResults(TTestResult result);
    /** 保存当前测试的逻辑工步合并规则。 */
    int saveLogicStepMappings(Long testId, List<TTestLogicStepMapping> mappings, Long operatorId, String updateBy);
    /** 保存测试详情当前最新版,允许所有测试状态且不产生审计记录。 */
    int saveConsoleDetail(Long testId, TTestConsoleDetail detail, Long operatorId, String updateBy);
}
ruoyi-system/src/main/java/com/ruoyi/system/service/impl/TTestServiceImpl.java
@@ -26,6 +26,7 @@
import com.ruoyi.system.domain.TTestFieldSnapshot;
import com.ruoyi.system.domain.TTestWarningThreshold;
import com.ruoyi.system.domain.TTestLogicStepMapping;
import com.ruoyi.system.domain.TTestConsoleDetail;
import com.ruoyi.system.domain.TDetectionChannel;
import com.ruoyi.system.domain.vo.TestConfigurationOptionsVo;
import com.ruoyi.system.mapper.TTestMapper;
@@ -330,6 +331,57 @@
        return rows;
    }
    @Override
    @Transactional(rollbackFor = Exception.class)
    public int saveConsoleDetail(Long testId, TTestConsoleDetail detail, Long operatorId, String updateBy)
    {
        if (testId == null || operatorId == null || detail == null) throw new ServiceException("测试详情不能为空");
        TTest current = testMapper.selectTestByIdForUpdate(testId);
        if (current == null) throw new ServiceException("测试不存在");
        String experimentName = trimToNull(detail.getExperimentName());
        if (experimentName != null && experimentName.length() > 200) throw new ServiceException("实验名称长度不能超过200个字符");
        detail.setExperimentName(experimentName);
        validateConsoleFields(detail.getBatteryFields());
        validateConsoleFields(detail.getSpecialFields());
        validateConsoleValues(testId, detail);
        int rows = testMapper.updateConsoleDetail(testId, JSON.toJSONString(detail), updateBy, new Date());
        if (rows != 1) throw new ServiceException("测试详情保存失败,请刷新后重试");
        return rows;
    }
    private void validateConsoleFields(List<String> fields)
    {
        if (fields == null) return;
        Set<String> names = new HashSet<>();
        for (String field : fields)
        {
            String normalized = trimToNull(field);
            if (normalized == null || normalized.length() > 100 || !names.add(normalized))
                throw new ServiceException("测试详情字段不能为空、长度不能超过100且不可重复");
        }
    }
    private void validateConsoleValues(Long testId, TTestConsoleDetail detail)
    {
        Set<String> allowedFields = new HashSet<>();
        if (detail.getBatteryFields() != null) allowedFields.addAll(detail.getBatteryFields());
        if (detail.getSpecialFields() != null) allowedFields.addAll(detail.getSpecialFields());
        Set<String> batteryNos = new HashSet<>();
        List<TTestBattery> batteries = testMapper.selectTestBatteries(testId);
        if (batteries != null) for (TTestBattery battery : batteries) batteryNos.add(battery.getBatteryNo());
        if (detail.getFieldValues() == null) return;
        for (Map.Entry<String, Map<String, String>> entry : detail.getFieldValues().entrySet())
        {
            if (!allowedFields.contains(entry.getKey()) || entry.getValue() == null)
                throw new ServiceException("测试详情字段值结构无效");
            for (Map.Entry<String, String> value : entry.getValue().entrySet())
            {
                if (!batteryNos.contains(value.getKey()) || (value.getValue() != null && value.getValue().length() > 500))
                    throw new ServiceException("测试详情电池号或字段值无效");
            }
        }
    }
    private void loadConfiguration(TTest test)
    {
        Long testId = test.getTestId();
@@ -384,6 +436,10 @@
        try { test.setLogicStepMappings(mappingJson == null || mappingJson.isBlank()
                ? new ArrayList<>() : JSON.parseArray(mappingJson, TTestLogicStepMapping.class)); }
        catch (JSONException exception) { throw new ServiceException("逻辑工步设置无法解析"); }
        String detailJson = test.getConsoleDetailJson();
        try { test.setConsoleDetail(detailJson == null || detailJson.isBlank()
                ? null : JSON.parseObject(detailJson, TTestConsoleDetail.class)); }
        catch (JSONException exception) { throw new ServiceException("测试详情无法解析"); }
    }
    private void validateBase(TTest test, boolean update)
ruoyi-system/src/main/resources/mapper/system/TTestMapper.xml
@@ -26,6 +26,7 @@
        <result property="updateTime" column="update_time" />
        <result property="remark" column="remark" />
        <result property="logicStepMappingsJson" column="logic_step_mappings_json" />
        <result property="consoleDetailJson" column="console_detail_json" />
        <result property="batteryCount" column="battery_count" />
        <result property="batteryNo" column="battery_no" />
    </resultMap>
@@ -120,6 +121,7 @@
               t.started_at_utc, t.ended_at_utc, t.del_flag,
               t.create_by, t.create_time, t.update_by, t.update_time, t.remark,
               cast(t.logic_step_mappings_json as char) logic_step_mappings_json,
               cast(t.console_detail_json as char) console_detail_json,
               (select count(1) from t_test_battery b where b.test_id = t.test_id and b.del_flag = 0) battery_count,
               t.runtime_attach runtime_attach
        from t_test t
@@ -478,6 +480,10 @@
            update_by = #{updateBy}, update_time = #{updateTime}
        where test_id = #{testId} and del_flag = 0
    </update>
    <update id="updateConsoleDetail">
        update t_test set console_detail_json = cast(#{detailJson} as json)
        where test_id = #{testId} and del_flag = 0
    </update>
    <select id="countTestTypeEnabled" resultType="int">
        select count(1) from t_test_type where test_type_id = #{testTypeId} and status = '0' and del_flag = 0