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/PushIngestReceptionContractTest.java | 73 +++++++++++++++++++++++++++++++++++-
1 files changed, 71 insertions(+), 2 deletions(-)
diff --git a/battery-business/src/test/java/com/battery/business/service/impl/PushIngestReceptionContractTest.java b/battery-business/src/test/java/com/battery/business/service/impl/PushIngestReceptionContractTest.java
index 5f83d84..fa11347 100644
--- a/battery-business/src/test/java/com/battery/business/service/impl/PushIngestReceptionContractTest.java
+++ b/battery-business/src/test/java/com/battery/business/service/impl/PushIngestReceptionContractTest.java
@@ -50,6 +50,16 @@
{"record_time":"134349698000000000","Data_Point":422,"Step_Time":600.0,"Step_Index":4,
"Cycle_Index":1,"Voltage":1.2702,"eCurrent":0.5,"ChargeCapacity":0.122,"ChargeEnergy":0.32}]}
""";
+ /** 同一批里带上已声明的环境量列:辅助测量的值取自这一行自己,不做任何缩放。 */
+ private static final String AUXILIARY_BATCH = """
+ {"kind":"SAMPLE","database":"DB_20260927_155209","table":"Ws26_IV_0","channel":26,"rows":[
+ {"record_time":"134349698000000000","Data_Point":421,"Step_Time":600.0,"Step_Index":4,
+ "Cycle_Index":1,"Voltage":1.2701,"eCurrent":0.5,"ChargeCapacity":0.121,"ChargeEnergy":0.31,
+ "Tempbox":26.5},
+ {"record_time":"134349704000000000","Data_Point":422,"Step_Time":660.0,"Step_Index":4,
+ "Cycle_Index":1,"Voltage":1.2702,"eCurrent":0.5,"ChargeCapacity":0.122,"ChargeEnergy":0.32,
+ "Tempbox":26.5}]}
+ """;
private final FakeIngestLanding landing = new FakeIngestLanding();
private final TDeviceAdapterMapper adapters = Mockito.mock(TDeviceAdapterMapper.class);
@@ -100,19 +110,78 @@
verify(mapper, times(2)).insertRealtimeRecord(any());
}
+ /**
+ * 采集器辅助测量按 ADR-0064 由同一来源提供:它作为扩展测量记录进入实时记录,
+ * **不跟检测柜记录伪造配对**——检测柜走各自的设备数据行(槽位为空),采集器走轮询槽位行(柜端字段为空),
+ * 两边各自保留自己的设备时间,绑定角色决定它进哪个指标。
+ */
+ @Test
+ void collectorAuxiliaryMeasurementBecomesItsOwnRealtimeRowWithoutFakingAPairing()
+ {
+ PushIngestAdapter adapter = ingestPushSource("{\"tokenEnv\":\"BATTERY_INGEST_TOKEN\",\"auxiliaryMetrics\":"
+ + "[{\"vendorChannel\":26,\"channelCode\":\"S-01\",\"column\":\"Tempbox\","
+ + "\"metricCode\":\"TEMPERATURE\",\"unit\":\"℃\"}]}");
+ TTestMapper mapper = mock(TTestMapper.class);
+ ITTestService tests = mock(ITTestService.class);
+ TTest test = receivingTest();
+ TTestBattery battery = test.getBatteries().get(0);
+ battery.setCollectorId(7L);
+ com.battery.business.domain.TTestCollectorChannel collectorChannel =
+ new com.battery.business.domain.TTestCollectorChannel();
+ collectorChannel.setChannelCode("S-01");
+ collectorChannel.setMeasurementCode("PRIMARY_TEMPERATURE");
+ battery.setCollectorChannels(List.of(collectorChannel));
+ when(mapper.selectReceivingTestIds()).thenReturn(List.of(1L));
+ when(mapper.selectTestByIdForUpdate(1L)).thenReturn(test);
+ when(tests.selectTestById(1L)).thenReturn(test);
+ when(mapper.selectCollectorCode(7L)).thenReturn("j22-collector");
+ TestDataReceptionServiceImpl service = new TestDataReceptionServiceImpl(mapper, tests, adapter, clock);
+
+ landing.insertIngestRecord(boundaryRow("DB_20260927_155209/Ws26_IV_0:420", 420L));
+ service.captureReceivedData();
+ ingest(AUXILIARY_BATCH);
+ clock.advance(Duration.ofSeconds(60));
+ service.captureReceivedData();
+
+ ArgumentCaptor<TTestRealtimeRecord> rows = ArgumentCaptor.forClass(TTestRealtimeRecord.class);
+ verify(mapper, times(3)).insertRealtimeRecord(rows.capture());
+ List<TTestRealtimeRecord> cabinetRows = rows.getAllValues().subList(0, 2);
+ TTestRealtimeRecord collectorRow = rows.getAllValues().get(2);
+ // 检测柜事实不占轮询槽位,采集器事实占槽位:两条路径互不覆盖。
+ assertTrue(cabinetRows.stream().allMatch(row -> row.getCaptureSlotUtc() == null));
+ assertTrue(collectorRow.getCaptureSlotUtc() != null);
+ // 采集器行不携带检测柜设备时间与检测柜身份,来源行身份里也没有 cabinet 段。
+ assertEquals(null, collectorRow.getCabinetDeviceTimeUtc());
+ assertEquals(null, collectorRow.getCabinetChlid());
+ assertTrue(collectorRow.getSourceRecordId().indexOf("cabinet:") < 0);
+ // 测量值取自这一行自己的设备时间(该通道落地表里最新一行),指标编码与单位按配置声明,
+ // 固定指标落到自己的列。
+ assertEquals(Instant.parse("2026-09-27T08:13:20Z"), collectorRow.getCollectorDeviceTimeUtc().toInstant());
+ assertEquals("S-01", collectorRow.getCollectorChannelCode());
+ assertEquals(0, new java.math.BigDecimal("26.5").compareTo(collectorRow.getTemperature()));
+ assertEquals(Long.valueOf(7L), collectorRow.getCollectorId());
+ }
+
private PushIngestAdapter ingestPushSource()
+ {
+ return ingestPushSource("{\"tokenEnv\":\"BATTERY_INGEST_TOKEN\"}");
+ }
+
+ private PushIngestAdapter ingestPushSource(String sourceConfig)
{
TDeviceAdapter source = new TDeviceAdapter();
source.setAdapterCode(ADAPTER);
source.setAdapterType(PushIngestAdapter.ADAPTER_TYPE);
source.setStatus("0");
source.setTimezoneId("Asia/Shanghai");
- source.setSourceConfig("{\"tokenEnv\":\"BATTERY_INGEST_TOKEN\"}");
+ source.setSourceConfig(sourceConfig);
when(adapters.selectDeviceAdapterByCode(ADAPTER)).thenReturn(source);
ingest("{\"kind\":\"TOPOLOGY\",\"rows\":[{\"channel\":26}]}");
return new PushIngestAdapter(new AdapterInstanceConfig(ADAPTER, PushIngestAdapter.ADAPTER_TYPE,
"127.0.0.1", 8080, 60000, "Asia/Shanghai", true, AdapterCapabilities.observeOnly(),
- java.util.Map.of("tokenEnv", "BATTERY_INGEST_TOKEN")), landing, clock);
+ com.alibaba.fastjson2.JSON.parseObject(sourceConfig)
+ .toJavaObject(new com.alibaba.fastjson2.TypeReference<java.util.Map<String, String>>() { })),
+ landing, clock);
}
private void ingest(String body)
--
Gitblit v1.7.1