fix: avoid jt808 fallback ids as vin
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful

This commit is contained in:
lingniu
2026-06-26 11:37:37 +08:00
parent 5e619c3634
commit ec555cd67a
4 changed files with 75 additions and 10 deletions

View File

@@ -177,20 +177,20 @@ public class Jt808ChannelHandler extends SimpleChannelInboundHandler<Object> {
private IdentityResolution resolveIdentity(Jt808Message msg) { private IdentityResolution resolveIdentity(Jt808Message msg) {
String phone = msg.header().phone(); String phone = msg.header().phone();
if (identityResolver == null) { if (identityResolver == null) {
// 没有绑定表时用手机号兜底为 VIN保证会话/下行仍可按 phone 找到连接 // 没有绑定表时不伪造 VINphone 会保留在 metadata/session 中用于下行定位
return new IdentityResolution( return new IdentityResolution(
new VehicleIdentity(phone, false, VehicleIdentitySource.FALLBACK_PHONE), new VehicleIdentity("unknown", false, VehicleIdentitySource.FALLBACK_PHONE),
null); null);
} }
try { try {
// 优先把 JT808 phone/device/plate 映射到内部 VIN后续所有事件都用内部 VIN 做主键。 // 优先把 JT808 phone/device/plate 映射到内部 VIN后续所有事件都用内部 VIN 做主键。
return new IdentityResolution( return new IdentityResolution(
identityResolver.resolve(new VehicleIdentityLookup( normalizeUnresolvedIdentity(identityResolver.resolve(new VehicleIdentityLookup(
ProtocolId.JT808, ProtocolId.JT808,
"", "",
phone, phone,
deviceId(msg.body()), deviceId(msg.body()),
plate(msg.body()))), plate(msg.body())))),
null); null);
} catch (RuntimeException e) { } catch (RuntimeException e) {
return new IdentityResolution( return new IdentityResolution(
@@ -199,6 +199,16 @@ public class Jt808ChannelHandler extends SimpleChannelInboundHandler<Object> {
} }
} }
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) { private void dispatchFrameError(ChannelHandlerContext ctx, Jt808MalformedFrame malformed) {
String peer = malformed.peer() == null || malformed.peer().isBlank() ? addr(ctx) : malformed.peer(); String peer = malformed.peer() == null || malformed.peer().isBlank() ? addr(ctx) : malformed.peer();
dispatchMalformed(peer, malformed.rawBytes(), malformed.reason(), true); dispatchMalformed(peer, malformed.rawBytes(), malformed.reason(), true);

View File

@@ -173,8 +173,8 @@ public final class Jt808EventMapper implements EventMapper<Jt808Message> {
} }
var header = message.header(); var header = message.header();
try { try {
return IdentityResolution.ok(identityResolver.resolve(new VehicleIdentityLookup( return IdentityResolution.ok(normalizeUnresolvedIdentity(identityResolver.resolve(new VehicleIdentityLookup(
ProtocolId.JT808, "", header.phone(), deviceId(message.body()), plate(message.body())))); ProtocolId.JT808, "", header.phone(), deviceId(message.body()), plate(message.body())))));
} catch (RuntimeException e) { } catch (RuntimeException e) {
return new IdentityResolution( return new IdentityResolution(
new VehicleIdentity("unknown", false, VehicleIdentitySource.UNKNOWN), new VehicleIdentity("unknown", false, VehicleIdentitySource.UNKNOWN),
@@ -182,6 +182,16 @@ public final class Jt808EventMapper implements EventMapper<Jt808Message> {
} }
} }
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<String, String> baseMeta(Jt808Header header, IdentityResolution identity) { private static Map<String, String> baseMeta(Jt808Header header, IdentityResolution identity) {
java.util.HashMap<String, String> meta = new java.util.HashMap<>(); java.util.HashMap<String, String> meta = new java.util.HashMap<>();
meta.put("messageId", "0x" + Integer.toHexString(header.messageId())); meta.put("messageId", "0x" + Integer.toHexString(header.messageId()));

View File

@@ -197,6 +197,46 @@ class Jt808ChannelHandlerTest {
eventBus.close(); 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 @Test
void malformedFrameIsArchivedAndDispatchedAsPassthrough() throws Exception { void malformedFrameIsArchivedAndDispatchedAsPassthrough() throws Exception {
RecordingSink sink = new RecordingSink(2); RecordingSink sink = new RecordingSink(2);

View File

@@ -34,10 +34,12 @@ class Jt808EventMapperTest {
assertThat(events).hasSize(1); assertThat(events).hasSize(1);
assertThat(events.get(0)).isInstanceOf(VehicleEvent.Location.class); assertThat(events.get(0)).isInstanceOf(VehicleEvent.Location.class);
assertThat(events.get(0).source()).isEqualTo(ProtocolId.JT808); 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()) assertThat(events.get(0).metadata())
.containsEntry("vin", "123456789012") .containsEntry("vin", "unknown")
.containsEntry("identityResolved", "false"); .containsEntry("phone", "123456789012")
.containsEntry("identityResolved", "false")
.containsEntry("identitySource", "FALLBACK_PHONE");
} }
@Test @Test
@@ -197,7 +199,10 @@ class Jt808EventMapperTest {
assertThat(passthrough.passthroughType()).isEqualTo(0x0F01); assertThat(passthrough.passthroughType()).isEqualTo(0x0F01);
assertThat(passthrough.data()).containsExactly(0x11, 0x22, 0x33); assertThat(passthrough.data()).containsExactly(0x11, 0x22, 0x33);
assertThat(passthrough.metadata()) assertThat(passthrough.metadata())
.containsEntry("vin", "123456789012") .containsEntry("vin", "unknown")
.containsEntry("phone", "123456789012")
.containsEntry("identityResolved", "false")
.containsEntry("identitySource", "FALLBACK_PHONE")
.containsEntry("rawBody", "true"); .containsEntry("rawBody", "true");
}); });
} }