feat: persist raw frames and publish raw facts

This commit is contained in:
lingniu
2026-06-29 13:10:14 +08:00
parent 9c2779f82b
commit feb9552234
14 changed files with 300 additions and 71 deletions

View File

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

View File

@@ -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:");
}
}