fix: keep jt808 raw history consumer current

This commit is contained in:
lingniu
2026-06-29 15:56:41 +08:00
parent 57ef82f563
commit 98b37e71e9
13 changed files with 274 additions and 54 deletions

View File

@@ -10,6 +10,7 @@ import com.lingniu.ingest.tdenginehistory.TdengineEnvelopeRows;
import com.lingniu.ingest.tdenginehistory.TdengineHistoryWriter;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
/**
@@ -47,30 +48,57 @@ public final class EventHistoryEnvelopeIngestor implements EnvelopeIngestor {
VehicleEnvelope envelope = parse(kafkaValue);
EventFileRecord record = mapper.toRecord(envelope);
store.append(record);
writeTdengineFacts(envelope);
writeTdengineFacts(List.of(envelope));
}
@Override
public EnvelopeIngestResult tryIngest(byte[] kafkaValue) {
VehicleEnvelope envelope = null;
try {
envelope = parse(kafkaValue);
EventFileRecord record = mapper.toRecord(envelope);
store.append(record);
writeTdengineFacts(envelope);
return EnvelopeIngestResult.stored(record.eventId(), record.vin());
} catch (IllegalArgumentException ex) {
// 协议不可解析或缺少必要字段属于不可重试问题,避免 Kafka consumer 无限重放。
return envelope == null
? EnvelopeIngestResult.invalid(ex.getMessage())
: EnvelopeIngestResult.skipped(envelope.getEventId(), envelope.getVin(), ex.getMessage());
} catch (IOException ex) {
// 文件库写入失败可能是临时 I/O 问题,交给上层 worker 按失败处理。
return EnvelopeIngestResult.failed(
envelope == null ? "" : envelope.getEventId(),
envelope == null ? "" : envelope.getVin(),
ex.getMessage());
return tryIngestAll(List.of(kafkaValue)).getFirst();
}
@Override
public List<EnvelopeIngestResult> tryIngestAll(List<byte[]> kafkaValues) {
if (kafkaValues == null || kafkaValues.isEmpty()) {
return List.of();
}
List<EnvelopeIngestResult> results = new ArrayList<>(kafkaValues.size());
List<BatchEntry> valid = new ArrayList<>(kafkaValues.size());
for (byte[] kafkaValue : kafkaValues) {
VehicleEnvelope envelope = null;
try {
envelope = parse(kafkaValue);
EventFileRecord record = mapper.toRecord(envelope);
results.add(null);
valid.add(new BatchEntry(results.size() - 1, envelope, record));
} catch (IllegalArgumentException ex) {
results.add(envelope == null
? EnvelopeIngestResult.invalid(ex.getMessage())
: EnvelopeIngestResult.skipped(envelope.getEventId(), envelope.getVin(), ex.getMessage()));
}
}
if (valid.isEmpty()) {
return List.copyOf(results);
}
try {
store.appendAll(valid.stream().map(BatchEntry::record).toList());
writeTdengineFacts(valid.stream().map(BatchEntry::envelope).toList());
for (BatchEntry entry : valid) {
results.set(entry.index(), EnvelopeIngestResult.stored(
entry.record().eventId(),
entry.record().vin()));
}
} catch (IOException ex) {
for (BatchEntry entry : valid) {
results.set(entry.index(), EnvelopeIngestResult.failed(
entry.envelope().getEventId(),
entry.envelope().getVin(),
ex.getMessage()));
}
}
return List.copyOf(results);
}
private record BatchEntry(int index, VehicleEnvelope envelope, EventFileRecord record) {
}
private static VehicleEnvelope parse(byte[] kafkaValue) {
@@ -84,19 +112,25 @@ public final class EventHistoryEnvelopeIngestor implements EnvelopeIngestor {
}
}
private void writeTdengineFacts(VehicleEnvelope envelope) throws IOException {
private void writeTdengineFacts(List<VehicleEnvelope> envelopes) throws IOException {
if (tdengineWriter == null) {
return;
}
var rawFrame = TdengineEnvelopeRows.rawFrame(envelope);
if (rawFrame.isPresent()) {
tdengineWriter.appendRawFrames(List.of(rawFrame.get()));
var rawFrames = envelopes.stream()
.flatMap(envelope -> TdengineEnvelopeRows.rawFrame(envelope).stream())
.toList();
if (!rawFrames.isEmpty()) {
tdengineWriter.appendRawFrames(rawFrames);
}
var location = TdengineEnvelopeRows.location(envelope);
if (location.isPresent()) {
tdengineWriter.appendLocations(List.of(location.get()));
var locations = envelopes.stream()
.flatMap(envelope -> TdengineEnvelopeRows.location(envelope).stream())
.toList();
if (!locations.isEmpty()) {
tdengineWriter.appendLocations(locations);
}
var telemetryFields = TdengineEnvelopeRows.telemetryFields(envelope);
var telemetryFields = envelopes.stream()
.flatMap(envelope -> TdengineEnvelopeRows.telemetryFields(envelope).stream())
.toList();
if (!telemetryFields.isEmpty()) {
tdengineWriter.appendTelemetryFields(telemetryFields);
}

View File

@@ -186,6 +186,38 @@ class EventHistoryEnvelopeIngestorTest {
assertThat(tdengineWriter.telemetryFields.getFirst().valueDouble()).isEqualTo(42.5);
}
@Test
void tryIngestAllBatchesFileStoreAndTdengineWrites() throws Exception {
CapturingStore store = new CapturingStore();
CapturingTdengineWriter tdengineWriter = new CapturingTdengineWriter();
EventHistoryEnvelopeIngestor ingestor = new EventHistoryEnvelopeIngestor(
store, new TelemetryEnvelopeRecordMapper(), tdengineWriter);
VehicleEnvelope first = jt808LocationEnvelope("jt808-location-1", "frame-jt808-1", "013800000001");
VehicleEnvelope second = jt808LocationEnvelope("jt808-location-2", "frame-jt808-2", "013800000002");
List<EnvelopeIngestResult> results = ingestor.tryIngestAll(List.of(
first.toByteArray(),
new byte[]{0x01, 0x02},
second.toByteArray()));
assertThat(results).extracting(EnvelopeIngestResult::status)
.containsExactly(
EnvelopeIngestResult.Status.STORED,
EnvelopeIngestResult.Status.INVALID_ENVELOPE,
EnvelopeIngestResult.Status.STORED);
assertThat(store.appendAllCalls).isEqualTo(1);
assertThat(store.records).extracting(EventFileRecord::eventId)
.containsExactly("jt808-location-1", "jt808-location-2");
assertThat(tdengineWriter.rawFrames).extracting(TdengineRawFrameRow::frameId)
.containsExactly("frame-jt808-1", "frame-jt808-2");
assertThat(tdengineWriter.locations).extracting(TdengineLocationRow::factId)
.containsExactly("jt808-location-1", "jt808-location-2");
assertThat(tdengineWriter.telemetryFields)
.extracting(TdengineTelemetryFieldRow::fieldKey)
.containsExactly("location.speedKmh", "location.speedKmh");
}
private static VehicleEnvelope envelope(String eventId) {
return VehicleEnvelope.newBuilder()
.setEventId(eventId)
@@ -204,11 +236,52 @@ class EventHistoryEnvelopeIngestorTest {
.build();
}
private static VehicleEnvelope jt808LocationEnvelope(String eventId, String frameId, String phone) {
return VehicleEnvelope.newBuilder()
.setSchemaVersion("1.0")
.setEventId(eventId)
.setVin("VINJT808001")
.setSource("JT808")
.setEventTimeMs(1_782_112_400_000L)
.setIngestTimeMs(1_782_112_401_000L)
.putMetadata("vehicle_key", "jt808:" + phone)
.putMetadata("frame_id", frameId)
.setRawArchive(RawArchiveRef.newBuilder()
.setUri("archive://jt808/2026/06/29/" + frameId + ".bin")
.setSizeBytes(68))
.setRawFrameFact(RawFrameFactPayload.newBuilder()
.setFrameId(frameId)
.setVehicleKey("jt808:" + phone)
.setVin("VINJT808001")
.setPhone(phone)
.setMessageId(0x0200)
.setRawUri("archive://jt808/2026/06/29/" + frameId + ".bin")
.setRawSizeBytes(68)
.setParseStatus(ParseStatusProto.PARSE_STATUS_SUCCEEDED))
.setLocation(LocationPayload.newBuilder()
.setLongitude(113.12)
.setLatitude(23.45)
.setSpeedKmh(42.5))
.setTelemetrySnapshot(TelemetrySnapshot.newBuilder()
.setEventType("LOCATION")
.setRawArchiveUri("archive://jt808/2026/06/29/" + frameId + ".bin")
.addFields(TelemetryField.newBuilder()
.setKey("location.speedKmh")
.setValueType("DOUBLE")
.setValue("42.5")
.setUnit("km/h")
.setQuality("GOOD")
.setSourcePath("JT808.0x0200.speed")))
.build();
}
private static final class CapturingStore implements EventFileStore {
private final List<EventFileRecord> records = new ArrayList<>();
private int appendAllCalls;
@Override
public void appendAll(List<EventFileRecord> records) {
appendAllCalls++;
this.records.addAll(records);
}