fix: enrich xinda raw frame metadata
This commit is contained in:
@@ -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();
|
||||
|
||||
@@ -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);
|
||||
|
||||
Reference in New Issue
Block a user