fix: enrich xinda raw frame metadata

This commit is contained in:
lingniu
2026-06-30 10:34:53 +08:00
parent 68cd3d0d06
commit b6cb661869
4 changed files with 116 additions and 7 deletions

View File

@@ -198,11 +198,12 @@ public class XindaPushClient extends PushClient {
// 保留外部字段和内部 VIN 的双轨元数据,方便定位绑定错误或第三方字段变更。
meta.put("source", "xinda-push");
meta.put("cmd", payload.command());
putRawFields(meta, identityFields);
meta.put("vin", identity.identity().vin());
meta.put("externalVin", firstNonBlank(identityFields, "vin"));
meta.put("phone", firstNonBlank(identityFields, "sim", "phone", "mobile"));
meta.put("deviceId", firstNonBlank(identityFields, "deviceId", "terminalId", "imei"));
meta.put("plateNo", firstNonBlank(identityFields, "plateNo", "carNo"));
meta.put("phone", phone(identityFields));
meta.put("deviceId", deviceId(identityFields));
meta.put("plateNo", plateNo(identityFields));
meta.put("identityResolved", Boolean.toString(identity.identity().resolved()));
meta.put("identitySource", identity.identity().source().name());
if (identity.errorMessage() != null) {
@@ -298,9 +299,9 @@ public class XindaPushClient extends PushClient {
return new IdentityResolution(identityResolver.resolve(new VehicleIdentityLookup(
ProtocolId.XINDA_PUSH,
firstNonBlank(json, "vin"),
firstNonBlank(json, "sim", "phone", "mobile"),
firstNonBlank(json, "deviceId", "terminalId", "imei"),
firstNonBlank(json, "plateNo", "carNo"))), null);
phone(json),
deviceId(json),
plateNo(json))), null);
} catch (RuntimeException e) {
return new IdentityResolution(
new VehicleIdentity("unknown", false, VehicleIdentitySource.UNKNOWN),
@@ -308,6 +309,60 @@ public class XindaPushClient extends PushClient {
}
}
private static String plateNo(JSONObject node) {
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 static void putRawFields(Map<String, String> meta, JSONObject json) {
if (meta == null || json == null || json.isEmpty()) {
return;
}
for (Map.Entry<String, Object> entry : json.entrySet()) {
if (entry.getKey() == null || entry.getKey().isBlank() || entry.getValue() == null) {
continue;
}
Object value = entry.getValue();
if (value instanceof JSONObject object) {
for (Map.Entry<String, Object> nested : object.entrySet()) {
if (nested.getKey() != null && !nested.getKey().isBlank() && nested.getValue() != null) {
meta.put("raw." + entry.getKey() + "." + nested.getKey(), nested.getValue().toString());
}
}
} else {
String text = value.toString();
JSONObject nestedJson = parseObjectOrNull(text);
if (nestedJson != null) {
for (Map.Entry<String, Object> nested : nestedJson.entrySet()) {
if (nested.getKey() != null && !nested.getKey().isBlank() && nested.getValue() != null) {
meta.put("raw." + entry.getKey() + "." + nested.getKey(), nested.getValue().toString());
}
}
} else {
meta.put("raw." + entry.getKey(), text);
}
}
}
}
private static JSONObject parseObjectOrNull(String value) {
if (value == null || value.isBlank() || !value.trim().startsWith("{")) {
return null;
}
try {
return JSON.parseObject(value);
} catch (Exception ignored) {
return null;
}
}
private static JSONObject parseQuietly(String rawJson) {
if (rawJson == null || rawJson.isBlank()) {
return new JSONObject();

View File

@@ -100,6 +100,36 @@ class XindaPushClientTest {
.containsEntry("identitySource", "BOUND_DEVICE_ID");
}
@Test
void businessMessageRawFrameResolvesIdentityByCarNamePlateAlias() throws Exception {
Dispatcher dispatcher = mock(Dispatcher.class);
InMemoryVehicleIdentityService identities = new InMemoryVehicleIdentityService();
identities.bind(new VehicleIdentityBinding(
ProtocolId.XINDA_PUSH,
"LB9A32A24P0LS1270",
"",
"",
"粤BD12345"));
XindaPushClient client = new XindaPushClient(new XindaPushProperties(), identities, dispatcher);
PushMsg msg = new PushMsg();
msg.init("0200", "{\"carName\":\"粤BD12345\",\"tmnKey\":\"XD-TMN-001\",\"lng\":113.1,\"lat\":23.1}");
client.messageReceived(null, msg);
ArgumentCaptor<RawFrame> captor = ArgumentCaptor.forClass(RawFrame.class);
verify(dispatcher).dispatch(captor.capture());
RawFrame frame = captor.getValue();
assertThat(frame.sourceMeta())
.containsEntry("vin", "LB9A32A24P0LS1270")
.containsEntry("plateNo", "粤BD12345")
.containsEntry("deviceId", "XD-TMN-001")
.containsEntry("raw.carName", "粤BD12345")
.containsEntry("raw.tmnKey", "XD-TMN-001")
.containsEntry("raw.lng", "113.1")
.containsEntry("identityResolved", "true")
.containsEntry("identitySource", "BOUND_PLATE");
}
@Test
void malformedCommandIsStillDispatchedForArchiveAndPassthrough() throws Exception {
Dispatcher dispatcher = mock(Dispatcher.class);