fix: resolve xinda vehicles by mysql plate binding

This commit is contained in:
lingniu
2026-06-30 10:02:15 +08:00
parent f3ec20c26c
commit 4a6c986bf1
4 changed files with 59 additions and 12 deletions

View File

@@ -131,12 +131,7 @@ public final class MySqlVehicleIdentityService implements VehicleIdentityResolve
PreparedStatement statement = connection.prepareStatement(sql); PreparedStatement statement = connection.prepareStatement(sql);
ResultSet resultSet = statement.executeQuery()) { ResultSet resultSet = statement.executeQuery()) {
while (resultSet.next()) { while (resultSet.next()) {
loaded.bind(new VehicleIdentityBinding( bindGlobalPlate(loaded, resultSet.getString("vin"), resultSet.getString("plate"));
ProtocolId.JT808,
resultSet.getString("vin"),
"",
"",
resultSet.getString("plate")));
} }
loadRegistrationIndex(loaded, connection); loadRegistrationIndex(loaded, connection);
return loaded; return loaded;
@@ -145,6 +140,17 @@ public final class MySqlVehicleIdentityService implements VehicleIdentityResolve
} }
} }
private static void bindGlobalPlate(InMemoryVehicleIdentityService loaded, String vin, String plate) {
for (ProtocolId protocol : new ProtocolId[]{
ProtocolId.GB32960,
ProtocolId.JT808,
ProtocolId.MQTT_YUTONG,
ProtocolId.XINDA_PUSH
}) {
loaded.bind(new VehicleIdentityBinding(protocol, vin, "", "", plate));
}
}
private void loadRegistrationIndex(InMemoryVehicleIdentityService loaded, Connection connection) throws SQLException { private void loadRegistrationIndex(InMemoryVehicleIdentityService loaded, Connection connection) throws SQLException {
String sql = "SELECT r.protocol, r.phone, r.device_id, r.plate, b.vin FROM " + registrationTable + " r " String sql = "SELECT r.protocol, r.phone, r.device_id, r.plate, b.vin FROM " + registrationTable + " r "
+ "JOIN " + table + " b ON b.plate = r.plate " + "JOIN " + table + " b ON b.plate = r.plate "

View File

@@ -83,6 +83,22 @@ class MySqlVehicleIdentityServiceTest {
assertThat(jdbc.lookupSelects).isZero(); assertThat(jdbc.lookupSelects).isZero();
} }
@Test
void resolvesGlobalPlateBindingForXindaPush() {
RecordingIdentityJdbc jdbc = new RecordingIdentityJdbc();
jdbc.seed("浙F00780F", "LA9GG68L8PBAF4776");
MySqlVehicleIdentityService service = new MySqlVehicleIdentityService(jdbc.dataSource(), "vehicle_identity_binding");
VehicleIdentity byPlate = service.resolve(new VehicleIdentityLookup(
ProtocolId.XINDA_PUSH, "", "", "", "浙F00780F"));
assertThat(byPlate.vin()).isEqualTo("LA9GG68L8PBAF4776");
assertThat(byPlate.resolved()).isTrue();
assertThat(byPlate.source()).isEqualTo(VehicleIdentitySource.BOUND_PLATE);
assertThat(jdbc.loadAllSelects).isEqualTo(1);
assertThat(jdbc.lookupSelects).isZero();
}
@Test @Test
void refreshLoadsExternallyInsertedBindingWithoutPerFrameDatabaseLookup() { void refreshLoadsExternallyInsertedBindingWithoutPerFrameDatabaseLookup() {
RecordingIdentityJdbc jdbc = new RecordingIdentityJdbc(); RecordingIdentityJdbc jdbc = new RecordingIdentityJdbc();

View File

@@ -143,9 +143,9 @@ public final class XindaPushEventMapper implements EventMapper<XindaPushMessage>
return IdentityResolution.ok(identityResolver.resolve(new VehicleIdentityLookup( return IdentityResolution.ok(identityResolver.resolve(new VehicleIdentityLookup(
ProtocolId.XINDA_PUSH, ProtocolId.XINDA_PUSH,
firstNonBlank(json, "vin"), firstNonBlank(json, "vin"),
firstNonBlank(json, "sim", "phone", "mobile"), firstNonBlank(json, "sim", "phone", "mobile", "simCode"),
firstNonBlank(json, "deviceId", "terminalId", "imei"), firstNonBlank(json, "deviceId", "terminalId", "imei", "tmnKey", "tmnCode"),
firstNonBlank(json, "plateNo", "carNo")))); plateNo(json))));
} catch (RuntimeException e) { } catch (RuntimeException e) {
return new IdentityResolution( return new IdentityResolution(
new VehicleIdentity("unknown", false, VehicleIdentitySource.UNKNOWN), new VehicleIdentity("unknown", false, VehicleIdentitySource.UNKNOWN),
@@ -172,9 +172,9 @@ public final class XindaPushEventMapper implements EventMapper<XindaPushMessage>
out.put("rawJson", message.json()); out.put("rawJson", message.json());
out.put("vin", normalizedVin(identity)); out.put("vin", normalizedVin(identity));
out.put("externalVin", firstNonBlank(json, "vin")); out.put("externalVin", firstNonBlank(json, "vin"));
out.put("phone", firstNonBlank(json, "sim", "phone", "mobile")); out.put("phone", firstNonBlank(json, "sim", "phone", "mobile", "simCode"));
out.put("deviceId", firstNonBlank(json, "deviceId", "terminalId", "imei")); out.put("deviceId", firstNonBlank(json, "deviceId", "terminalId", "imei", "tmnKey", "tmnCode"));
out.put("plateNo", firstNonBlank(json, "plateNo", "carNo")); out.put("plateNo", plateNo(json));
out.put("identityResolved", Boolean.toString(identity.identity().resolved())); out.put("identityResolved", Boolean.toString(identity.identity().resolved()));
out.put("identitySource", identity.identity().source().name()); out.put("identitySource", identity.identity().source().name());
if (identity.errorMessage() != null) { if (identity.errorMessage() != null) {
@@ -243,6 +243,10 @@ public final class XindaPushEventMapper implements EventMapper<XindaPushMessage>
return ""; return "";
} }
private static String plateNo(JSONObject node) {
return firstNonBlank(node, "plateNo", "carNo", "carName");
}
private static double asDouble(JSONObject node, String... keys) { private static double asDouble(JSONObject node, String... keys) {
Double value = optionalDouble(node, keys); Double value = optionalDouble(node, keys);
return value == null ? 0.0 : value; return value == null ? 0.0 : value;

View File

@@ -113,6 +113,27 @@ class XindaPushEventMapperTest {
}); });
} }
@Test
void resolvesVehicleIdentityByCarNamePlateFromXindaPayload() {
identity.bind(new VehicleIdentityBinding(
ProtocolId.XINDA_PUSH, "LNVIN000000000203", "", "", "浙F00780F"));
List<VehicleEvent> events = mapper.toEvents(new XindaPushMessage("0200",
"{\"carName\":\"浙F00780F\",\"tmnKey\":\"13307811342\",\"simCode\":\"8986062582008805648\","
+ "\"lng\":121.075223,\"lat\":30.583753}"));
assertThat(events).singleElement()
.satisfies(event -> {
assertThat(event.vin()).isEqualTo("LNVIN000000000203");
assertThat(event.metadata())
.containsEntry("vin", "LNVIN000000000203")
.containsEntry("plateNo", "浙F00780F")
.containsEntry("phone", "8986062582008805648")
.containsEntry("deviceId", "13307811342")
.containsEntry("identityResolved", "true");
});
}
@Test @Test
void identityResolverFailureStillProducesLocationEventWithUnknownVin() { void identityResolverFailureStillProducesLocationEventWithUnknownVin() {
XindaPushEventMapper mapperWithFailingIdentity = new XindaPushEventMapper(lookup -> { XindaPushEventMapper mapperWithFailingIdentity = new XindaPushEventMapper(lookup -> {