diff --git a/modules/protocols/protocol-jt808/src/main/java/com/lingniu/ingest/protocol/jt808/inbound/Jt808ChannelHandler.java b/modules/protocols/protocol-jt808/src/main/java/com/lingniu/ingest/protocol/jt808/inbound/Jt808ChannelHandler.java index 1057136a..a84e8202 100644 --- a/modules/protocols/protocol-jt808/src/main/java/com/lingniu/ingest/protocol/jt808/inbound/Jt808ChannelHandler.java +++ b/modules/protocols/protocol-jt808/src/main/java/com/lingniu/ingest/protocol/jt808/inbound/Jt808ChannelHandler.java @@ -177,20 +177,20 @@ public class Jt808ChannelHandler extends SimpleChannelInboundHandler { private IdentityResolution resolveIdentity(Jt808Message msg) { String phone = msg.header().phone(); if (identityResolver == null) { - // 没有绑定表时用手机号兜底为 VIN,保证会话/下行仍可按 phone 找到连接。 + // 没有绑定表时不伪造 VIN;phone 会保留在 metadata/session 中用于下行定位。 return new IdentityResolution( - new VehicleIdentity(phone, false, VehicleIdentitySource.FALLBACK_PHONE), + new VehicleIdentity("unknown", false, VehicleIdentitySource.FALLBACK_PHONE), null); } try { // 优先把 JT808 phone/device/plate 映射到内部 VIN,后续所有事件都用内部 VIN 做主键。 return new IdentityResolution( - identityResolver.resolve(new VehicleIdentityLookup( + normalizeUnresolvedIdentity(identityResolver.resolve(new VehicleIdentityLookup( ProtocolId.JT808, "", phone, deviceId(msg.body()), - plate(msg.body()))), + plate(msg.body())))), null); } catch (RuntimeException e) { return new IdentityResolution( @@ -199,6 +199,16 @@ public class Jt808ChannelHandler extends SimpleChannelInboundHandler { } } + private static VehicleIdentity normalizeUnresolvedIdentity(VehicleIdentity identity) { + if (identity == null) { + return new VehicleIdentity("unknown", false, VehicleIdentitySource.UNKNOWN); + } + if (identity.resolved()) { + return identity; + } + return new VehicleIdentity("unknown", false, identity.source()); + } + private void dispatchFrameError(ChannelHandlerContext ctx, Jt808MalformedFrame malformed) { String peer = malformed.peer() == null || malformed.peer().isBlank() ? addr(ctx) : malformed.peer(); dispatchMalformed(peer, malformed.rawBytes(), malformed.reason(), true); diff --git a/modules/protocols/protocol-jt808/src/main/java/com/lingniu/ingest/protocol/jt808/mapper/Jt808EventMapper.java b/modules/protocols/protocol-jt808/src/main/java/com/lingniu/ingest/protocol/jt808/mapper/Jt808EventMapper.java index afbd58aa..1ca5fdea 100644 --- a/modules/protocols/protocol-jt808/src/main/java/com/lingniu/ingest/protocol/jt808/mapper/Jt808EventMapper.java +++ b/modules/protocols/protocol-jt808/src/main/java/com/lingniu/ingest/protocol/jt808/mapper/Jt808EventMapper.java @@ -173,8 +173,8 @@ public final class Jt808EventMapper implements EventMapper { } var header = message.header(); try { - return IdentityResolution.ok(identityResolver.resolve(new VehicleIdentityLookup( - ProtocolId.JT808, "", header.phone(), deviceId(message.body()), plate(message.body())))); + return IdentityResolution.ok(normalizeUnresolvedIdentity(identityResolver.resolve(new VehicleIdentityLookup( + ProtocolId.JT808, "", header.phone(), deviceId(message.body()), plate(message.body()))))); } catch (RuntimeException e) { return new IdentityResolution( new VehicleIdentity("unknown", false, VehicleIdentitySource.UNKNOWN), @@ -182,6 +182,16 @@ public final class Jt808EventMapper implements EventMapper { } } + private static VehicleIdentity normalizeUnresolvedIdentity(VehicleIdentity identity) { + if (identity == null) { + return new VehicleIdentity("unknown", false, VehicleIdentitySource.UNKNOWN); + } + if (identity.resolved()) { + return identity; + } + return new VehicleIdentity("unknown", false, identity.source()); + } + private static Map baseMeta(Jt808Header header, IdentityResolution identity) { java.util.HashMap meta = new java.util.HashMap<>(); meta.put("messageId", "0x" + Integer.toHexString(header.messageId())); diff --git a/modules/protocols/protocol-jt808/src/test/java/com/lingniu/ingest/protocol/jt808/inbound/Jt808ChannelHandlerTest.java b/modules/protocols/protocol-jt808/src/test/java/com/lingniu/ingest/protocol/jt808/inbound/Jt808ChannelHandlerTest.java index e4b8505e..ede2811d 100644 --- a/modules/protocols/protocol-jt808/src/test/java/com/lingniu/ingest/protocol/jt808/inbound/Jt808ChannelHandlerTest.java +++ b/modules/protocols/protocol-jt808/src/test/java/com/lingniu/ingest/protocol/jt808/inbound/Jt808ChannelHandlerTest.java @@ -197,6 +197,46 @@ class Jt808ChannelHandlerTest { eventBus.close(); } + @Test + void unboundLocationArchiveUsesUnknownVinInsteadOfPhoneFallback() throws Exception { + RecordingSink sink = new RecordingSink(1); + DisruptorEventBus eventBus = new DisruptorEventBus(1024, "blocking", List.of(sink)); + AsyncBatchExecutor batchExecutor = new AsyncBatchExecutor(eventBus::publish); + Dispatcher dispatcher = new Dispatcher( + new HandlerRegistry(), + new InterceptorChain(List.of()), + new HandlerInvoker(), + eventBus, + batchExecutor); + InMemoryVehicleIdentityService identity = new InMemoryVehicleIdentityService(); + Jt808ChannelHandler handler = new Jt808ChannelHandler( + new Jt808MessageDecoder(new BodyParserRegistry(List.of(new LocationBodyParser()))), + dispatcher, + new InMemorySessionStore(), + identity, + new Jt808ChannelRegistry(), + new Jt808PendingRequests()); + EmbeddedChannel channel = new EmbeddedChannel(handler); + byte[] frame = buildFrame(Jt808MessageId.TERMINAL_LOCATION, "123456789012", 1, buildLocationBody()); + + channel.writeInbound(frame); + + assertThat(sink.await()).isTrue(); + assertThat(sink.events).singleElement().satisfies(event -> { + assertThat(event).isInstanceOf(VehicleEvent.RawArchive.class); + VehicleEvent.RawArchive archive = (VehicleEvent.RawArchive) event; + assertThat(archive.vin()).isEqualTo("unknown"); + assertThat(archive.metadata()) + .containsEntry("vin", "unknown") + .containsEntry("phone", "123456789012") + .containsEntry("identityResolved", "false") + .containsEntry("identitySource", "FALLBACK_PHONE"); + }); + + batchExecutor.close(); + eventBus.close(); + } + @Test void malformedFrameIsArchivedAndDispatchedAsPassthrough() throws Exception { RecordingSink sink = new RecordingSink(2); diff --git a/modules/protocols/protocol-jt808/src/test/java/com/lingniu/ingest/protocol/jt808/mapper/Jt808EventMapperTest.java b/modules/protocols/protocol-jt808/src/test/java/com/lingniu/ingest/protocol/jt808/mapper/Jt808EventMapperTest.java index 6c2563e1..d0341046 100644 --- a/modules/protocols/protocol-jt808/src/test/java/com/lingniu/ingest/protocol/jt808/mapper/Jt808EventMapperTest.java +++ b/modules/protocols/protocol-jt808/src/test/java/com/lingniu/ingest/protocol/jt808/mapper/Jt808EventMapperTest.java @@ -34,10 +34,12 @@ class Jt808EventMapperTest { assertThat(events).hasSize(1); assertThat(events.get(0)).isInstanceOf(VehicleEvent.Location.class); assertThat(events.get(0).source()).isEqualTo(ProtocolId.JT808); - assertThat(events.get(0).vin()).isEqualTo("123456789012"); + assertThat(events.get(0).vin()).isEqualTo("unknown"); assertThat(events.get(0).metadata()) - .containsEntry("vin", "123456789012") - .containsEntry("identityResolved", "false"); + .containsEntry("vin", "unknown") + .containsEntry("phone", "123456789012") + .containsEntry("identityResolved", "false") + .containsEntry("identitySource", "FALLBACK_PHONE"); } @Test @@ -197,7 +199,10 @@ class Jt808EventMapperTest { assertThat(passthrough.passthroughType()).isEqualTo(0x0F01); assertThat(passthrough.data()).containsExactly(0x11, 0x22, 0x33); assertThat(passthrough.metadata()) - .containsEntry("vin", "123456789012") + .containsEntry("vin", "unknown") + .containsEntry("phone", "123456789012") + .containsEntry("identityResolved", "false") + .containsEntry("identitySource", "FALLBACK_PHONE") .containsEntry("rawBody", "true"); }); }