From c2dd019c1fddaeae412e2b6f44fb86ff0dc635d4 Mon Sep 17 00:00:00 2001
From: yeg <1347747024@qq.com>
Date: 星期三, 02 九月 2026 22:08:16 +0800
Subject: [PATCH] 实现主动轮询完整实时快照
---
ruoyi-system/src/main/java/com/ruoyi/system/service/impl/TestDataQueryServiceImpl.java | 210 +++++++++++++++++++++++++++++++++++++++++++++++++++
1 files changed, 207 insertions(+), 3 deletions(-)
diff --git a/ruoyi-system/src/main/java/com/ruoyi/system/service/impl/TestDataQueryServiceImpl.java b/ruoyi-system/src/main/java/com/ruoyi/system/service/impl/TestDataQueryServiceImpl.java
index 10226fb..06ee3c8 100644
--- a/ruoyi-system/src/main/java/com/ruoyi/system/service/impl/TestDataQueryServiceImpl.java
+++ b/ruoyi-system/src/main/java/com/ruoyi/system/service/impl/TestDataQueryServiceImpl.java
@@ -1,9 +1,20 @@
package com.ruoyi.system.service.impl;
import java.util.List;
+import java.util.ArrayList;
+import java.util.Comparator;
+import java.util.Date;
+import java.time.Instant;
+import java.time.OffsetDateTime;
+import java.time.LocalDateTime;
+import java.time.ZoneId;
+import java.util.LinkedHashMap;
import java.util.Map;
+import java.util.HashMap;
+import com.alibaba.fastjson2.JSON;
import org.springframework.stereotype.Service;
import com.ruoyi.common.exception.ServiceException;
+import com.ruoyi.system.domain.TTestDataOverride;
import com.ruoyi.system.mapper.TTestMapper;
import com.ruoyi.system.service.ITTestService;
import com.ruoyi.system.service.ITestDataQueryService;
@@ -21,22 +32,215 @@
this.tests = tests;
}
+ /** 查询测试当前有效的检测柜与采集器实时观测,并补充可筛选的工步上下文。 */
@Override public List<Map<String, Object>> records(Long testId)
{
requireTest(testId);
- return mapper.selectPersistedRecords(testId);
+ List<Map<String, Object>> source = new java.util.ArrayList<>();
+ List<Map<String, Object>> snapshots = mapper.selectPersistedRealtimeSnapshots(testId);
+ if (snapshots != null && !snapshots.isEmpty()) source.addAll(snapshots);
+ else {
+ // 迁移完成前兼容旧数据;新采集只从统一快照读取。
+ List<Map<String, Object>> cabinet = mapper.selectPersistedRecords(testId);
+ List<Map<String, Object>> collector = mapper.selectPersistedCollectorRecords(testId);
+ if (cabinet != null) source.addAll(cabinet);
+ if (collector != null) source.addAll(collector);
+ }
+ List<Map<String, Object>> effective = applyEffectiveData(testId, "REALTIME", source);
+ attachCollectorStepContext(effective);
+ return effective;
}
@Override public List<Map<String, Object>> steps(Long testId)
{
requireTest(testId);
- return mapper.selectPersistedSteps(testId);
+ return applyEffectiveData(testId, "STEP", mapper.selectPersistedSteps(testId));
}
@Override public List<Map<String, Object>> cycles(Long testId)
{
requireTest(testId);
- return mapper.selectPersistedCycles(testId);
+ return applyEffectiveData(testId, "CYCLE", mapper.selectPersistedCycles(testId));
+ }
+
+ /** 将最新人工覆盖应用到设备事实,删除只影响当前有效视图,不抹除原始采样。 */
+ private List<Map<String, Object>> applyEffectiveData(Long testId, String dataType,
+ List<Map<String, Object>> source)
+ {
+ List<Map<String, Object>> effective = new java.util.ArrayList<>(
+ source == null ? List.<Map<String, Object>>of() : source);
+ List<Map<String, Object>> imports = mapper.selectConsoleImportRecords(testId, dataType);
+ for (Map<String, Object> imported : imports == null ? List.<Map<String, Object>>of() : imports)
+ {
+ try
+ {
+ @SuppressWarnings("unchecked")
+ Map<String, Object> row = (Map<String, Object>) (Map<?, ?>) JSON.parseObject(
+ String.valueOf(imported.get("rowValues")), Map.class);
+ row.put("stableIdentity", imported.get("stableIdentity"));
+ effective.add(row);
+ }
+ catch (RuntimeException ignored) { }
+ }
+ List<TTestDataOverride> overrides = mapper.selectDataOverrides(testId, dataType);
+ if (effective.isEmpty() || overrides == null || overrides.isEmpty()) return effective;
+ Map<String, TTestDataOverride> byIdentity = new HashMap<>();
+ for (TTestDataOverride override : overrides)
+ if (override != null && override.getStableIdentity() != null)
+ byIdentity.put(override.getStableIdentity(), override);
+ List<Map<String, Object>> filtered = new java.util.ArrayList<>(effective.size());
+ for (Map<String, Object> row : effective)
+ {
+ String identity = row.get("stableIdentity") == null ? stableIdentity(dataType, row)
+ : String.valueOf(row.get("stableIdentity"));
+ TTestDataOverride override = byIdentity.get(identity);
+ if (override != null && Boolean.TRUE.equals(override.getDeleted())) continue;
+ Map<String, Object> copy = new LinkedHashMap<>(row);
+ if (override != null && override.getFieldValues() != null
+ && !override.getFieldValues().isBlank())
+ {
+ try
+ {
+ Map<String, Object> values = JSON.parseObject(override.getFieldValues(), Map.class);
+ if (values != null) copy.putAll(values);
+ }
+ catch (RuntimeException ignored)
+ {
+ // 覆盖已由修订服务校验;解析异常时保留原始事实,避免查询整体失败。
+ }
+ }
+ filtered.add(copy);
+ }
+ return filtered;
+ }
+
+ private String stableIdentity(String dataType, Map<String, Object> row)
+ {
+ if ("REALTIME".equals(dataType) && "SNAPSHOT".equals(String.valueOf(row.get("sourceType"))))
+ return String.valueOf(row.getOrDefault("stableIdentity", "snapshot:" + value(row, "snapshotId")));
+ if ("REALTIME".equals(dataType) && "COLLECTOR".equals(String.valueOf(row.get("sourceType"))))
+ return String.join(":", "collector", identityPart(row, "collectorId"),
+ identityPart(row, "channelCode"), value(row, "sequenceId"), identityPart(row, "metricCode"));
+ String adapterId = identityPart(row, "adapterId");
+ String devtype = identityPart(row, "devtype");
+ String deviceId = identityPart(row, "deviceId");
+ String subdevid = identityPart(row, "subdevid");
+ String channelId = identityPart(row, "channelId");
+ String cycleNo = value(row, "cycleNo");
+ String channelIdentity = String.join(":", adapterId, devtype, deviceId, subdevid, channelId);
+ if ("REALTIME".equals(dataType)) return channelIdentity + ":" + value(row, "sequenceId");
+ if ("STEP".equals(dataType)) return "step:" + channelIdentity + ":" + cycleNo + ":" + value(row, "stepNo");
+ return "cycle:" + channelIdentity + ":" + cycleNo;
+ }
+
+ /** 按检测柜实际工步的设备时间边界给采集器样本补充筛选上下文,不配对采样点。 */
+ private void attachCollectorStepContext(List<Map<String, Object>> rows)
+ {
+ Map<String, StepWindowBuilder> builders = new LinkedHashMap<>();
+ for (Map<String, Object> row : rows)
+ {
+ if ("COLLECTOR".equals(String.valueOf(row.get("sourceType")))) continue;
+ Instant time = toInstant(row.get("deviceTime"));
+ if (time == null || row.get("stepNo") == null) continue;
+ String key = String.join("|", text(row, "batteryNo"), text(row, "cycleNo"), text(row, "stepNo"));
+ StepWindowBuilder builder = builders.computeIfAbsent(key, ignored -> new StepWindowBuilder(row, time));
+ builder.start = builder.start.isAfter(time) ? time : builder.start;
+ builder.end = builder.end == null || builder.end.isBefore(time) ? time : builder.end;
+ }
+ 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())
+ {
+ 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;
+ 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);
+ if (match == null) continue;
+ row.put("cycleNo", match.cycleNo());
+ row.put("stepNo", match.stepNo());
+ row.put("stepName", match.stepName());
+ row.put("actualStepLabel", match.stepName());
+ }
+ }
+
+ private String text(Map<String, Object> row, String key)
+ {
+ Object value = row.get(key);
+ return value == null ? "" : String.valueOf(value);
+ }
+
+ private Instant toInstant(Object value)
+ {
+ if (value instanceof Instant instant) return instant;
+ if (value instanceof Date date) return date.toInstant();
+ if (value == null) return null;
+ String text = String.valueOf(value);
+ try { return Instant.parse(text); }
+ catch (RuntimeException ignored) { }
+ try { return OffsetDateTime.parse(text).toInstant(); }
+ catch (RuntimeException ignored) { }
+ try { return LocalDateTime.parse(text).atZone(ZoneId.of("Asia/Hong_Kong")).toInstant(); }
+ catch (RuntimeException ignored) { return null; }
+ }
+
+ private static final class StepWindowBuilder
+ {
+ private final String batteryNo;
+ private final String cycleNo;
+ private final String stepNo;
+ private final String stepName;
+ private Instant start;
+ private Instant end;
+
+ private StepWindowBuilder(Map<String, Object> row, Instant time)
+ {
+ batteryNo = row.get("batteryNo") == null ? "" : String.valueOf(row.get("batteryNo"));
+ cycleNo = row.get("cycleNo") == null ? "" : String.valueOf(row.get("cycleNo"));
+ stepNo = row.get("stepNo") == null ? "" : String.valueOf(row.get("stepNo"));
+ stepName = row.get("stepName") == null ? "-" : String.valueOf(row.get("stepName"));
+ start = time;
+ end = time;
+ }
+
+ private StepWindow build()
+ {
+ return new StepWindow(batteryNo, cycleNo, stepNo, stepName, start, end, null);
+ }
+ }
+
+ private record StepWindow(String batteryNo, String cycleNo, String stepNo, String stepName,
+ Instant start, Instant end, Instant nextStart)
+ {
+ private StepWindow withNextStart(Instant value)
+ {
+ return new StepWindow(batteryNo, cycleNo, stepNo, stepName, start, end, value);
+ }
+ }
+
+ private String identityPart(Map<String, Object> row, String key)
+ {
+ Object value = row == null ? null : row.get(key);
+ return value == null ? "" : String.valueOf(value);
+ }
+
+ private String value(Map<String, Object> row, String key)
+ {
+ return identityPart(row, key);
}
private void requireTest(Long testId)
--
Gitblit v1.7.1