From e38aca8134570513435c003c25dc05f1ae51c9c9 Mon Sep 17 00:00:00 2001
From: yeg <1347747024@qq.com>
Date: 星期四, 08 十月 2026 13:56:28 +0800
Subject: [PATCH] 实现 Ticket04:采集器辅助测量与登记入口,拆除开发模拟器
---
battery-business/src/test/java/com/battery/business/service/impl/PushIngestAdapterContractTest.java | 87 ++++++++++++++++++++++++++++++++++++++++---
1 files changed, 80 insertions(+), 7 deletions(-)
diff --git a/battery-business/src/test/java/com/battery/business/service/impl/PushIngestAdapterContractTest.java b/battery-business/src/test/java/com/battery/business/service/impl/PushIngestAdapterContractTest.java
index acee31c..ec6c1be 100644
--- a/battery-business/src/test/java/com/battery/business/service/impl/PushIngestAdapterContractTest.java
+++ b/battery-business/src/test/java/com/battery/business/service/impl/PushIngestAdapterContractTest.java
@@ -50,11 +50,19 @@
private final FakeIngestLanding landing = new FakeIngestLanding();
private final TDeviceAdapterMapper adapters = Mockito.mock(TDeviceAdapterMapper.class);
private static final Instant NOW = Instant.parse("2026-09-27T09:00:00Z");
+ /**
+ * 环境量的确认结论落在这里:厂商列名只在配置里出现一次,代码里没有任何厂商列名。
+ * 22 所的列语义与量纲仍未现场确认,所以生产配置里默认没有这一项。
+ */
+ private static final Map<String, String> AUXILIARY_METRICS = Map.of("auxiliaryMetrics",
+ "[{\"vendorChannel\":26,\"channelCode\":\"S-01\",\"column\":\"Tempbox\","
+ + "\"metricCode\":\"TEMPERATURE\",\"unit\":\"℃\"}]");
@Test
void topologyPushMakesChannelsCandidatesWithTheirOwnLatestDataTime()
{
- ingest("{\"kind\":\"TOPOLOGY\",\"rows\":[{\"channel\":26},{\"channel\":18}]}");
+ // 拓扑行里可以带 channelNo:来源自己软件里显示的通道号(现场核对:22 所界面号 = 表名号 + 1)
+ ingest("{\"kind\":\"TOPOLOGY\",\"rows\":[{\"channel\":26,\"channelNo\":27},{\"channel\":18}]}");
ingest(BATCH);
PushIngestAdapter adapter = adapter(NOW);
@@ -68,9 +76,15 @@
assertTrue(active.available());
// 候选的机器地址取实例配置:登记时会被带进检测柜的 IP 字段。
assertEquals("127.0.0.1", active.serverIp());
+ // 来源自己的通道叫法随候选一起交出(界面与登记用它显示),身份仍是表名里的号。
+ assertEquals("27", active.sourceDisplayNo());
+ assertEquals(26, active.identity().chlid());
// 登记范围由操作员决定:没有采样行的通道照样出现在候选里,只是没有最近数据时间。
assertNull(channels.stream().filter(channel -> channel.identity().chlid() == 18).findFirst()
.orElseThrow().lastDataAt());
+ // 来源没报叫法的通道保持为空,界面回退到「单元-通道」。
+ assertNull(channels.stream().filter(channel -> channel.identity().chlid() == 18).findFirst()
+ .orElseThrow().sourceDisplayNo());
}
/** 心跳缺失或过期即来源不可达:发现路径明确失败,业务侧据此把已登记设备置为离线并撤下旧快照。 */
@@ -156,9 +170,9 @@
assertTrue(adapter.inquire(Set.of(channel())).isEmpty());
}
- /** 采集器辅助测量按 ADR-0064 逐来源确认;22 所环境量的列语义未确认,因此不交出测量值。 */
+ /** 未声明辅助测量列就不交出测量值:确认之前不按列名推断语义,也不写零占位。 */
@Test
- void collectorAndStepCycleObservationsStayEmptyForThisStorageForm()
+ void collectorAndStepCycleObservationsStayEmptyWithoutDeclaredAuxiliaryMetrics()
{
ingest("{\"kind\":\"TOPOLOGY\",\"rows\":[{\"channel\":26}]}");
ingest(BATCH);
@@ -171,6 +185,61 @@
assertEquals(5000L, adapter.persistenceIntervalMillis(channel()));
AdapterCapabilities capabilities = adapter.capabilities();
assertTrue(capabilities.discovery() && capabilities.realtimeObservation() && !capabilities.control());
+ }
+
+ /** 环境量与量纲确认后落到配置:列名、指标编码、单位、采集器通道都由声明给出,代码里没有厂商列名。 */
+ @Test
+ void declaredAuxiliaryMetricIsProjectedWithItsOwnSourceIdentityValueUnitAndDeviceTime()
+ {
+ ingest("{\"kind\":\"TOPOLOGY\",\"rows\":[{\"channel\":26}]}");
+ ingest("""
+ {"kind":"SAMPLE","database":"DB_20260927_155209","table":"Ws26_IV_0","channel":26,"rows":[
+ {"record_time":"134349692000000000","Data_Point":420,"Voltage":1.26778,"eCurrent":0.5,"Tempbox":26.5}]}
+ """);
+ PushIngestAdapter adapter = adapter(NOW, AUXILIARY_METRICS);
+
+ var collector = adapter.latestCollector(ADAPTER, "collector-1");
+
+ assertTrue(collector.online());
+ var measurement = collector.measurements().get(0);
+ assertEquals("S-01", measurement.channelCode());
+ assertEquals("TEMPERATURE", measurement.metricCode());
+ assertEquals(0, new java.math.BigDecimal("26.5").compareTo(measurement.value()));
+ assertEquals("℃", measurement.unit());
+ // 设备时间与来源身份来自这一行自己:不与检测柜记录共用时间,也不伪造配对。
+ assertEquals(Instant.parse("2026-09-27T07:53:20Z"), measurement.deviceTime());
+ assertEquals("134349692000000000", measurement.deviceTimeRaw());
+ }
+
+ /** 落地行里没有这一列(设备没给)就不写这一项,也不写零占位。 */
+ @Test
+ void declaredAuxiliaryMetricAbsentFromTheRowIsNotFabricated()
+ {
+ ingest("{\"kind\":\"TOPOLOGY\",\"rows\":[{\"channel\":26}]}");
+ ingest(BATCH);
+ PushIngestAdapter adapter = adapter(NOW, AUXILIARY_METRICS);
+
+ var collector = adapter.latestCollector(ADAPTER, "collector-1");
+
+ assertTrue(collector.online());
+ assertTrue(collector.measurements().isEmpty());
+ }
+
+ /** 来源离线(心跳超时)时不交出任何测量值:离线不伪造测量值,业务侧据此也不落采集器行。 */
+ @Test
+ void staleHeartbeatReportsCollectorSourceOfflineWithoutMeasurements()
+ {
+ ingest("{\"kind\":\"TOPOLOGY\",\"rows\":[{\"channel\":26}]}");
+ ingest("""
+ {"kind":"SAMPLE","database":"DB_20260927_155209","table":"Ws26_IV_0","channel":26,"rows":[
+ {"record_time":"134349692000000000","Data_Point":420,"Voltage":1.26778,"eCurrent":0.5,"Tempbox":26.5}]}
+ """);
+ PushIngestAdapter adapter = adapter(NOW.plus(Duration.ofMinutes(10)), AUXILIARY_METRICS);
+
+ var collector = adapter.latestCollector(ADAPTER, "collector-1");
+
+ assertFalse(collector.online());
+ assertTrue(collector.measurements().isEmpty());
}
@Test
@@ -191,14 +260,18 @@
private PushIngestAdapter adapter(Instant now)
{
- return new PushIngestAdapter(config(), landing, Clock.fixed(now, ZoneOffset.UTC));
+ return adapter(now, Map.of("tokenEnv", "BATTERY_INGEST_TOKEN"));
}
- private static AdapterInstanceConfig config()
+ private PushIngestAdapter adapter(Instant now, Map<String, String> sourceOptions)
+ {
+ return new PushIngestAdapter(config(sourceOptions), landing, Clock.fixed(now, ZoneOffset.UTC));
+ }
+
+ private static AdapterInstanceConfig config(Map<String, String> sourceOptions)
{
return new AdapterInstanceConfig(ADAPTER, PushIngestAdapter.ADAPTER_TYPE, "127.0.0.1", 8080, 60000,
- "Asia/Shanghai", true, AdapterCapabilities.observeOnly(),
- Map.of("tokenEnv", "BATTERY_INGEST_TOKEN"));
+ "Asia/Shanghai", true, AdapterCapabilities.observeOnly(), sourceOptions);
}
private void ingest(String body)
--
Gitblit v1.7.1