diff --git a/modules/inbound/inbound-xinda-push/src/main/java/com/lingniu/ingest/inbound/xinda/XindaPushEventMapper.java b/modules/inbound/inbound-xinda-push/src/main/java/com/lingniu/ingest/inbound/xinda/XindaPushEventMapper.java index d40141fc..1956a727 100644 --- a/modules/inbound/inbound-xinda-push/src/main/java/com/lingniu/ingest/inbound/xinda/XindaPushEventMapper.java +++ b/modules/inbound/inbound-xinda-push/src/main/java/com/lingniu/ingest/inbound/xinda/XindaPushEventMapper.java @@ -10,8 +10,12 @@ import com.lingniu.ingest.api.spi.EventMapper; import com.lingniu.ingest.identity.InMemoryVehicleIdentityService; import com.lingniu.ingest.identity.VehicleIdentity; import com.lingniu.ingest.identity.VehicleIdentityLookup; +import com.lingniu.ingest.identity.VehicleIdentityRegistry; import com.lingniu.ingest.identity.VehicleIdentityResolver; import com.lingniu.ingest.identity.VehicleIdentitySource; +import com.lingniu.ingest.identity.VehicleRegistrationBinding; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import java.nio.charset.StandardCharsets; import java.time.Instant; @@ -22,7 +26,11 @@ import java.util.UUID; public final class XindaPushEventMapper implements EventMapper { + private static final Logger log = LoggerFactory.getLogger(XindaPushEventMapper.class); + private static final VehicleIdentityRegistry NOOP_REGISTRY = binding -> {}; + private final VehicleIdentityResolver identityResolver; + private final VehicleIdentityRegistry identityRegistry; public XindaPushEventMapper() { this(new InMemoryVehicleIdentityService()); @@ -30,6 +38,12 @@ public final class XindaPushEventMapper implements EventMapper public XindaPushEventMapper(VehicleIdentityResolver identityResolver) { this.identityResolver = identityResolver == null ? new InMemoryVehicleIdentityService() : identityResolver; + this.identityRegistry = this.identityResolver instanceof VehicleIdentityRegistry registry ? registry : NOOP_REGISTRY; + } + + public XindaPushEventMapper(VehicleIdentityResolver identityResolver, VehicleIdentityRegistry identityRegistry) { + this.identityResolver = identityResolver == null ? new InMemoryVehicleIdentityService() : identityResolver; + this.identityRegistry = identityRegistry == null ? NOOP_REGISTRY : identityRegistry; } @Override @@ -140,12 +154,14 @@ public final class XindaPushEventMapper implements EventMapper private IdentityResolution resolveIdentity(JSONObject json) { try { - return IdentityResolution.ok(identityResolver.resolve(new VehicleIdentityLookup( + IdentityResolution resolution = IdentityResolution.ok(identityResolver.resolve(new VehicleIdentityLookup( ProtocolId.XINDA_PUSH, firstNonBlank(json, "vin"), - firstNonBlank(json, "sim", "phone", "mobile", "simCode"), - firstNonBlank(json, "deviceId", "terminalId", "imei", "tmnKey", "tmnCode"), + phone(json), + deviceId(json), plateNo(json)))); + registerIdentity(json, resolution); + return resolution; } catch (RuntimeException e) { return new IdentityResolution( new VehicleIdentity("unknown", false, VehicleIdentitySource.UNKNOWN), @@ -172,8 +188,8 @@ public final class XindaPushEventMapper implements EventMapper out.put("rawJson", message.json()); out.put("vin", normalizedVin(identity)); out.put("externalVin", firstNonBlank(json, "vin")); - out.put("phone", firstNonBlank(json, "sim", "phone", "mobile", "simCode")); - out.put("deviceId", firstNonBlank(json, "deviceId", "terminalId", "imei", "tmnKey", "tmnCode")); + out.put("phone", phone(json)); + out.put("deviceId", deviceId(json)); out.put("plateNo", plateNo(json)); out.put("identityResolved", Boolean.toString(identity.identity().resolved())); out.put("identitySource", identity.identity().source().name()); @@ -247,6 +263,40 @@ public final class XindaPushEventMapper implements EventMapper return firstNonBlank(node, "plateNo", "carNo", "carName"); } + private static String phone(JSONObject node) { + return firstNonBlank(node, "sim", "phone", "mobile", "simCode", "tmnKey"); + } + + private static String deviceId(JSONObject node) { + return firstNonBlank(node, "deviceId", "terminalId", "imei", "tmnKey", "tmnCode"); + } + + private void registerIdentity(JSONObject json, IdentityResolution identity) { + String phone = phone(json); + String deviceId = deviceId(json); + String plate = plateNo(json); + if (phone.isBlank() && deviceId.isBlank() && plate.isBlank()) { + return; + } + String vin = identity != null && identity.identity().resolved() ? identity.identity().vin() : "unknown"; + try { + identityRegistry.register(new VehicleRegistrationBinding( + ProtocolId.XINDA_PUSH, + vin, + phone, + deviceId, + plate, + null, + null, + "", + "", + optionalInteger(json, "plateColor", "carColor"))); + } catch (RuntimeException e) { + log.warn("[xinda-push] vehicle identity registration failed plate={} phone={} deviceId={}", + plate, phone, deviceId, e); + } + } + private static double asDouble(JSONObject node, String... keys) { Double value = optionalDouble(node, keys); return value == null ? 0.0 : value; @@ -271,6 +321,11 @@ public final class XindaPushEventMapper implements EventMapper return (int) asDouble(node, keys); } + private static Integer optionalInteger(JSONObject node, String... keys) { + Double value = optionalDouble(node, keys); + return value == null ? null : value.intValue(); + } + private static double normalizeCoord(double value) { if (Math.abs(value) > 1000) { return value / 1_000_000.0; diff --git a/modules/inbound/inbound-xinda-push/src/main/java/com/lingniu/ingest/inbound/xinda/config/XindaPushAutoConfiguration.java b/modules/inbound/inbound-xinda-push/src/main/java/com/lingniu/ingest/inbound/xinda/config/XindaPushAutoConfiguration.java index b85ad49d..e8fd63d0 100644 --- a/modules/inbound/inbound-xinda-push/src/main/java/com/lingniu/ingest/inbound/xinda/config/XindaPushAutoConfiguration.java +++ b/modules/inbound/inbound-xinda-push/src/main/java/com/lingniu/ingest/inbound/xinda/config/XindaPushAutoConfiguration.java @@ -1,6 +1,7 @@ package com.lingniu.ingest.inbound.xinda.config; import com.lingniu.ingest.core.dispatcher.Dispatcher; +import com.lingniu.ingest.identity.VehicleIdentityRegistry; import com.lingniu.ingest.identity.VehicleIdentityResolver; import com.lingniu.ingest.inbound.xinda.XindaPushClient; import com.lingniu.ingest.inbound.xinda.XindaPushEventMapper; @@ -28,8 +29,9 @@ public class XindaPushAutoConfiguration { @Bean @ConditionalOnMissingBean - public XindaPushEventMapper xindaPushEventMapper(VehicleIdentityResolver identityResolver) { - return new XindaPushEventMapper(identityResolver); + public XindaPushEventMapper xindaPushEventMapper(VehicleIdentityResolver identityResolver, + VehicleIdentityRegistry identityRegistry) { + return new XindaPushEventMapper(identityResolver, identityRegistry); } @Bean diff --git a/modules/inbound/inbound-xinda-push/src/test/java/com/lingniu/ingest/inbound/xinda/XindaPushEventMapperTest.java b/modules/inbound/inbound-xinda-push/src/test/java/com/lingniu/ingest/inbound/xinda/XindaPushEventMapperTest.java index 6b9776f6..74da45ed 100644 --- a/modules/inbound/inbound-xinda-push/src/test/java/com/lingniu/ingest/inbound/xinda/XindaPushEventMapperTest.java +++ b/modules/inbound/inbound-xinda-push/src/test/java/com/lingniu/ingest/inbound/xinda/XindaPushEventMapperTest.java @@ -5,6 +5,8 @@ import com.lingniu.ingest.api.event.AlarmPayload; import com.lingniu.ingest.api.event.VehicleEvent; import com.lingniu.ingest.identity.InMemoryVehicleIdentityService; import com.lingniu.ingest.identity.VehicleIdentityBinding; +import com.lingniu.ingest.identity.VehicleIdentityRegistry; +import com.lingniu.ingest.identity.VehicleRegistrationBinding; import org.junit.jupiter.api.Test; import java.util.List; @@ -134,6 +136,49 @@ class XindaPushEventMapperTest { }); } + @Test + void registersXindaIdentityFromCarNamePayload() { + RecordingIdentityRegistry registry = new RecordingIdentityRegistry(); + identity.bind(new VehicleIdentityBinding( + ProtocolId.XINDA_PUSH, "LA9GG68L8PBAF4776", "", "", "浙F00780F")); + XindaPushEventMapper mapper = new XindaPushEventMapper(identity, registry); + + mapper.toEvents(new XindaPushMessage("0200", + "{\"carName\":\"浙F00780F\",\"tmnKey\":\"13307811342\",\"simCode\":\"8986062582008805648\"," + + "\"carColor\":\"93\",\"lng\":121.075223,\"lat\":30.583753}")); + + assertThat(registry.registration) + .isNotNull() + .satisfies(registration -> { + assertThat(registration.protocol()).isEqualTo(ProtocolId.XINDA_PUSH); + assertThat(registration.vin()).isEqualTo("LA9GG68L8PBAF4776"); + assertThat(registration.phone()).isEqualTo("8986062582008805648"); + assertThat(registration.deviceId()).isEqualTo("13307811342"); + assertThat(registration.plate()).isEqualTo("浙F00780F"); + assertThat(registration.plateColor()).isEqualTo(93); + }); + } + + @Test + void registersUnknownVinWhenXindaIdentityIsNotResolved() { + RecordingIdentityRegistry registry = new RecordingIdentityRegistry(); + XindaPushEventMapper mapper = new XindaPushEventMapper(identity, registry); + + mapper.toEvents(new XindaPushMessage("0200", + "{\"carName\":\"浙F00780F\",\"tmnKey\":\"13307811342\"," + + "\"lng\":121.075223,\"lat\":30.583753}")); + + assertThat(registry.registration) + .isNotNull() + .satisfies(registration -> { + assertThat(registration.protocol()).isEqualTo(ProtocolId.XINDA_PUSH); + assertThat(registration.vin()).isEqualTo("unknown"); + assertThat(registration.phone()).isEqualTo("13307811342"); + assertThat(registration.deviceId()).isEqualTo("13307811342"); + assertThat(registration.plate()).isEqualTo("浙F00780F"); + }); + } + @Test void identityResolverFailureStillProducesLocationEventWithUnknownVin() { XindaPushEventMapper mapperWithFailingIdentity = new XindaPushEventMapper(lookup -> { @@ -233,4 +278,17 @@ class XindaPushEventMapperTest { .containsEntry("parseError", "false"); }); } + + private static final class RecordingIdentityRegistry implements VehicleIdentityRegistry { + private VehicleRegistrationBinding registration; + + @Override + public void bind(VehicleIdentityBinding binding) { + } + + @Override + public void register(VehicleRegistrationBinding registration) { + this.registration = registration; + } + } }