feat: persist raw frames and publish raw facts
This commit is contained in:
@@ -8,10 +8,17 @@ import com.lingniu.ingest.api.event.TelemetryFieldValue;
|
||||
import com.lingniu.ingest.api.event.TelemetrySnapshot;
|
||||
import com.lingniu.ingest.api.event.VehicleEvent;
|
||||
import com.lingniu.ingest.api.event.VehicleEventTelemetrySnapshotMapper;
|
||||
import com.lingniu.ingest.facts.FactIds;
|
||||
import com.lingniu.ingest.facts.VehicleKey;
|
||||
import com.lingniu.ingest.sink.mq.proto.ParseStatusProto;
|
||||
import com.lingniu.ingest.sink.mq.proto.TelemetryField;
|
||||
import com.lingniu.ingest.sink.mq.proto.TelemetrySnapshot.Builder;
|
||||
import com.lingniu.ingest.sink.mq.proto.VehicleEnvelope;
|
||||
|
||||
import java.security.MessageDigest;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.util.HexFormat;
|
||||
|
||||
/**
|
||||
* 领域事件 → Protobuf Envelope 的纯函数映射。通过 switch 模式匹配穷尽处理,
|
||||
* 新增 {@link VehicleEvent} 子类型时编译期就会提示补齐分支。
|
||||
@@ -68,17 +75,39 @@ public final class EnvelopeMapper {
|
||||
p.data() == null ? new byte[0] : p.data()))
|
||||
.build());
|
||||
case VehicleEvent.RawArchive ra -> {
|
||||
int size = ra.rawBytes() == null ? 0 : ra.rawBytes().length;
|
||||
byte[] rawBytes = ra.rawBytes() == null ? new byte[0] : ra.rawBytes();
|
||||
int size = rawBytes.length;
|
||||
String key = rawArchiveKey(ra);
|
||||
String uri = rawArchiveUri(ra, key);
|
||||
String phone = metadataValue(ra, "phone");
|
||||
String vehicleKey = VehicleKey.derive(ra.source(), ra.vin(), phone, ra.eventId());
|
||||
String checksum = sha256(rawBytes);
|
||||
String frameId = FactIds.rawFrameId(
|
||||
ra.source(), vehicleKey, ra.command(), ra.infoType(), ra.ingestTime(), checksum);
|
||||
// RAW envelope 只携带 URI/size 引用信息,不把完整原始字节塞进 Kafka,避免 topic 膨胀。
|
||||
b.putMetadata(RawArchiveKeys.META_KEY, key);
|
||||
b.putMetadata(RawArchiveKeys.META_URI, uri);
|
||||
b.putMetadata(RawArchiveKeys.META_EVENT_ID, ra.eventId());
|
||||
b.setRawArchive(com.lingniu.ingest.sink.mq.proto.RawArchiveRef.newBuilder()
|
||||
.setUri(uri)
|
||||
.setChecksum(checksum)
|
||||
.setSizeBytes(size)
|
||||
.build());
|
||||
b.setRawFrameFact(com.lingniu.ingest.sink.mq.proto.RawFrameFactPayload.newBuilder()
|
||||
.setFrameId(frameId)
|
||||
.setVehicleKey(vehicleKey)
|
||||
.setVin(nullToEmpty(ra.vin()))
|
||||
.setPhone(phone)
|
||||
.setMessageId(ra.command())
|
||||
.setSubType(ra.infoType())
|
||||
.setRawUri(uri)
|
||||
.setChecksum(checksum)
|
||||
.setRawSizeBytes(size)
|
||||
.setParseStatus(parseStatus(ra))
|
||||
.setParseError(parseError(ra))
|
||||
.setPeer(metadataValue(ra, "peer"))
|
||||
.putAllMetadata(ra.metadata() == null ? java.util.Map.of() : ra.metadata())
|
||||
.build());
|
||||
}
|
||||
}
|
||||
return b.build();
|
||||
@@ -166,4 +195,45 @@ public final class EnvelopeMapper {
|
||||
String uri = raw.metadata() == null ? "" : raw.metadata().getOrDefault(RawArchiveKeys.META_URI, "");
|
||||
return uri == null || uri.isBlank() ? RawArchiveKeys.logicalUri(key) : uri;
|
||||
}
|
||||
|
||||
private static String metadataValue(VehicleEvent event, String key) {
|
||||
if (event.metadata() == null) {
|
||||
return "";
|
||||
}
|
||||
return nullToEmpty(event.metadata().get(key));
|
||||
}
|
||||
|
||||
private static ParseStatusProto parseStatus(VehicleEvent.RawArchive raw) {
|
||||
if (hasTruthyMetadata(raw, "frameError")
|
||||
|| hasTruthyMetadata(raw, "parseError")
|
||||
|| hasTruthyMetadata(raw, "processingError")) {
|
||||
return ParseStatusProto.PARSE_STATUS_FAILED;
|
||||
}
|
||||
return ParseStatusProto.PARSE_STATUS_NOT_PARSED;
|
||||
}
|
||||
|
||||
private static String parseError(VehicleEvent.RawArchive raw) {
|
||||
String frameError = metadataValue(raw, "frameErrorMessage");
|
||||
if (!frameError.isBlank()) {
|
||||
return frameError;
|
||||
}
|
||||
String parseError = metadataValue(raw, "parseErrorMessage");
|
||||
if (!parseError.isBlank()) {
|
||||
return parseError;
|
||||
}
|
||||
return metadataValue(raw, "processingErrorMessage");
|
||||
}
|
||||
|
||||
private static boolean hasTruthyMetadata(VehicleEvent event, String key) {
|
||||
return Boolean.parseBoolean(metadataValue(event, key));
|
||||
}
|
||||
|
||||
private static String sha256(byte[] bytes) {
|
||||
try {
|
||||
MessageDigest digest = MessageDigest.getInstance("SHA-256");
|
||||
return "sha256:" + HexFormat.of().formatHex(digest.digest(bytes == null ? new byte[0] : bytes));
|
||||
} catch (NoSuchAlgorithmException e) {
|
||||
throw new IllegalStateException("SHA-256 is unavailable", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -90,5 +90,13 @@ class EnvelopeMapperTelemetrySnapshotTest {
|
||||
assertThat(envelope.getRawArchive().getUri()).isEqualTo(RawArchiveKeys.logicalUri(key));
|
||||
assertThat(envelope.getRawArchive().getSizeBytes()).isEqualTo(4);
|
||||
assertThat(envelope.getMetadataMap()).containsEntry(RawArchiveKeys.META_URI, RawArchiveKeys.logicalUri(key));
|
||||
assertThat(envelope.hasRawFrameFact()).isTrue();
|
||||
assertThat(envelope.getRawFrameFact().getFrameId()).startsWith("rf_");
|
||||
assertThat(envelope.getRawFrameFact().getVehicleKey()).isEqualTo("VIN-JT808-001");
|
||||
assertThat(envelope.getRawFrameFact().getVin()).isEqualTo("VIN-JT808-001");
|
||||
assertThat(envelope.getRawFrameFact().getMessageId()).isEqualTo(0x0200);
|
||||
assertThat(envelope.getRawFrameFact().getRawUri()).isEqualTo(RawArchiveKeys.logicalUri(key));
|
||||
assertThat(envelope.getRawFrameFact().getRawSizeBytes()).isEqualTo(4);
|
||||
assertThat(envelope.getRawFrameFact().getChecksum()).startsWith("sha256:");
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user