yeg
2026-09-02 12962e1c1c3b51123b39b40154823b0585e8a6dd
修正实时数据有效视图与工步归属
2个文件已修改
55 ■■■■■ 已修改文件
ruoyi-admin/src/test/java/com/ruoyi/system/service/impl/TestDataQueryServiceEffectiveDataTest.java 21 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-system/src/main/java/com/ruoyi/system/service/impl/TestDataQueryServiceImpl.java 34 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-admin/src/test/java/com/ruoyi/system/service/impl/TestDataQueryServiceEffectiveDataTest.java
@@ -88,6 +88,27 @@
        assertEquals("恒流充电", assigned.get("stepName"));
    }
    @Test
    void doesNotAssignCollectorObservationToDeletedCabinetStep()
    {
        when(tests.selectTestById(10L)).thenReturn(new TTest());
        Map<String, Object> cabinet = row("sourceType", "CABINET", "batteryNo", "B-01", "cycleNo", 1,
                "stepNo", 2, "stepName", "恒流充电", "deviceTime", Instant.parse("2026-09-02T00:00:00Z"),
                "adapterId", 1L, "devtype", 22, "deviceId", 22L, "subdevid", 0, "channelId", 8L, "sequenceId", 1L);
        Map<String, Object> collector = row("sourceType", "COLLECTOR", "batteryNo", "B-01", "metricCode", "temperature",
                "deviceTime", Instant.parse("2026-09-02T00:00:01Z"), "collectorId", 3L, "channelCode", "S-01", "sequenceId", 2L);
        when(mapper.selectPersistedRecords(10L)).thenReturn(List.of(cabinet));
        when(mapper.selectPersistedCollectorRecords(10L)).thenReturn(List.of(collector));
        when(mapper.selectDataOverrides(10L, "REALTIME")).thenReturn(List.of(
                override("1:22:22:0:8:1", null, true)));
        List<Map<String, Object>> result = new TestDataQueryServiceImpl(mapper, tests).records(10L);
        Map<String, Object> assigned = result.stream().filter(item -> "COLLECTOR".equals(item.get("sourceType"))).findFirst().orElseThrow();
        assertTrue(!assigned.containsKey("stepNo"));
        assertTrue(!assigned.containsKey("cycleNo"));
    }
    private static TTestDataOverride override(String identity, String values, boolean deleted)
    {
        TTestDataOverride result = new TTestDataOverride();
ruoyi-system/src/main/java/com/ruoyi/system/service/impl/TestDataQueryServiceImpl.java
@@ -7,7 +7,7 @@
import java.time.Instant;
import java.time.OffsetDateTime;
import java.time.LocalDateTime;
import java.time.ZoneOffset;
import java.time.ZoneId;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.HashMap;
@@ -32,6 +32,7 @@
        this.tests = tests;
    }
    /** 查询测试当前有效的检测柜与采集器实时观测,并补充可筛选的工步上下文。 */
    @Override public List<Map<String, Object>> records(Long testId)
    {
        requireTest(testId);
@@ -40,8 +41,9 @@
        List<Map<String, Object>> collector = mapper.selectPersistedCollectorRecords(testId);
        if (cabinet != null) source.addAll(cabinet);
        if (collector != null) source.addAll(collector);
        attachCollectorStepContext(source);
        return applyEffectiveData(testId, "REALTIME", source);
        List<Map<String, Object>> effective = applyEffectiveData(testId, "REALTIME", source);
        attachCollectorStepContext(effective);
        return effective;
    }
    @Override public List<Map<String, Object>> steps(Long testId)
@@ -127,7 +129,6 @@
    /** 按检测柜实际工步的设备时间边界给采集器样本补充筛选上下文,不配对采样点。 */
    private void attachCollectorStepContext(List<Map<String, Object>> rows)
    {
        List<StepWindow> windows = new ArrayList<>();
        Map<String, StepWindowBuilder> builders = new LinkedHashMap<>();
        for (Map<String, Object> row : rows)
        {
@@ -139,23 +140,26 @@
            builder.start = builder.start.isAfter(time) ? time : builder.start;
            builder.end = builder.end == null || builder.end.isBefore(time) ? time : builder.end;
        }
        for (StepWindowBuilder builder : builders.values()) windows.add(builder.build());
        windows.sort(Comparator.comparing(StepWindow::start));
        for (int index = 0; index < windows.size(); index++)
        Map<String, List<StepWindow>> windowsByBattery = new LinkedHashMap<>();
        for (StepWindowBuilder builder : builders.values())
            windowsByBattery.computeIfAbsent(builder.batteryNo, ignored -> new ArrayList<>()).add(builder.build());
        for (List<StepWindow> batteryWindows : windowsByBattery.values())
        {
            StepWindow current = windows.get(index);
            Instant nextStart = windows.stream()
                    .filter(window -> window.batteryNo().equals(current.batteryNo()) && window.start().isAfter(current.start()))
                    .map(StepWindow::start).min(Comparator.naturalOrder()).orElse(null);
            windows.set(index, current.withNextStart(nextStart));
            batteryWindows.sort(Comparator.comparing(StepWindow::start));
            for (int index = 0; index < batteryWindows.size(); index++)
            {
                StepWindow current = batteryWindows.get(index);
                Instant nextStart = index + 1 < batteryWindows.size() ? batteryWindows.get(index + 1).start() : null;
                batteryWindows.set(index, current.withNextStart(nextStart));
            }
        }
        for (Map<String, Object> row : rows)
        {
            if (!"COLLECTOR".equals(String.valueOf(row.get("sourceType")))) continue;
            Instant time = toInstant(row.get("deviceTime"));
            if (time == null) continue;
            StepWindow match = windows.stream()
                    .filter(window -> text(row, "batteryNo").equals(window.batteryNo()))
            List<StepWindow> batteryWindows = windowsByBattery.getOrDefault(text(row, "batteryNo"), List.of());
            StepWindow match = batteryWindows.stream()
                    .filter(window -> !time.isBefore(window.start())
                            && (window.nextStart() == null || time.isBefore(window.nextStart())))
                    .findFirst().orElse(null);
@@ -183,7 +187,7 @@
        catch (RuntimeException ignored) { }
        try { return OffsetDateTime.parse(text).toInstant(); }
        catch (RuntimeException ignored) { }
        try { return LocalDateTime.parse(text).toInstant(ZoneOffset.UTC); }
        try { return LocalDateTime.parse(text).atZone(ZoneId.of("Asia/Hong_Kong")).toInstant(); }
        catch (RuntimeException ignored) { return null; }
    }