feat: register xinda vehicle identities

This commit is contained in:
lingniu
2026-06-30 10:13:32 +08:00
parent 4a6c986bf1
commit b977de5519
3 changed files with 122 additions and 7 deletions

View File

@@ -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<XindaPushMessage> {
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<XindaPushMessage>
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<XindaPushMessage>
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<XindaPushMessage>
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<XindaPushMessage>
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<XindaPushMessage>
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;

View File

@@ -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

View File

@@ -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;
}
}
}