fix: support tdengine gb32960 raw queries
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful

This commit is contained in:
lingniu
2026-06-29 20:19:44 +08:00
parent 12de83e37f
commit 80e96d8075
13 changed files with 398 additions and 51 deletions

View File

@@ -9,6 +9,11 @@ import com.lingniu.ingest.eventfilestore.EventFileStore;
import com.lingniu.ingest.protocol.gb32960.codec.Gb32960MessageDecoder;
import com.lingniu.ingest.protocol.gb32960.model.Gb32960Message;
import com.lingniu.ingest.protocol.gb32960.model.InfoBlock;
import com.lingniu.ingest.tdenginehistory.TdengineHistoryReader;
import com.lingniu.ingest.tdenginehistory.TdenginePage;
import com.lingniu.ingest.tdenginehistory.TdengineQueryOrder;
import com.lingniu.ingest.tdenginehistory.TdengineRawFrameQuery;
import com.lingniu.ingest.tdenginehistory.TdengineRawFrameRow;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -19,7 +24,9 @@ import java.nio.ByteBuffer;
import java.nio.file.Files;
import java.nio.file.NoSuchFileException;
import java.nio.file.Path;
import java.time.Instant;
import java.time.LocalDate;
import java.time.ZoneId;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.LinkedHashMap;
@@ -43,8 +50,10 @@ public final class Gb32960DecodedFrameService {
private static final TypeReference<Map<String, Object>> MAP_TYPE = new TypeReference<>() {};
private static final int OUTPUT_DOUBLE_SCALE = 6;
private static final String RAW_ARCHIVE_EVENT_TYPE = "RAW_ARCHIVE";
private static final ZoneId DEFAULT_ZONE = ZoneId.of("Asia/Shanghai");
private final EventFileStore store;
private final TdengineHistoryReader tdengineReader;
private final Gb32960MessageDecoder decoder;
private final Path archiveRoot;
private final ObjectMapper objectMapper;
@@ -53,8 +62,25 @@ public final class Gb32960DecodedFrameService {
Gb32960MessageDecoder decoder,
Path archiveRoot,
ObjectMapper objectMapper) {
if (store == null) {
throw new IllegalArgumentException("store must not be null");
this(store, null, decoder, archiveRoot, objectMapper);
}
public Gb32960DecodedFrameService(TdengineHistoryReader tdengineReader,
Gb32960MessageDecoder decoder,
Path archiveRoot,
ObjectMapper objectMapper) {
this(null, tdengineReader, decoder, archiveRoot, objectMapper);
}
public Gb32960DecodedFrameService(EventFileStore store,
TdengineHistoryReader tdengineReader,
Gb32960MessageDecoder decoder,
Path archiveRoot,
ObjectMapper objectMapper) {
if (store == null && tdengineReader == null) {
log.info("gb32960 decoded frame service has no history index backend; rawUri direct lookup remains available");
} else if (store == null) {
log.info("gb32960 decoded frame service using TDengine raw_frames index");
}
if (decoder == null) {
throw new IllegalArgumentException("decoder must not be null");
@@ -63,6 +89,7 @@ public final class Gb32960DecodedFrameService {
throw new IllegalArgumentException("archiveRoot must not be null");
}
this.store = store;
this.tdengineReader = tdengineReader;
this.decoder = decoder;
this.archiveRoot = archiveRoot.toAbsolutePath().normalize();
this.objectMapper = objectMapper == null ? new ObjectMapper() : objectMapper;
@@ -86,6 +113,22 @@ public final class Gb32960DecodedFrameService {
String vin,
String platformAccount) throws IOException {
int frameLimit = Math.max(1, Math.min(limit, 1000));
List<DecodedFrame> fromEventStore = store == null ? List.of() : queryFromEventStore(
dateFrom, dateTo, eventTimeFrom, eventTimeTo, order, frameLimit, vin, platformAccount);
if (!fromEventStore.isEmpty() || tdengineReader == null) {
return fromEventStore;
}
return queryFromTdengine(dateFrom, dateTo, eventTimeFrom, eventTimeTo, order, frameLimit, vin, platformAccount);
}
private List<DecodedFrame> queryFromEventStore(LocalDate dateFrom,
LocalDate dateTo,
java.time.Instant eventTimeFrom,
java.time.Instant eventTimeTo,
EventFileQuery.Order order,
int frameLimit,
String vin,
String platformAccount) throws IOException {
// 只查 RAW_ARCHIVEREALTIME/LOCATION 等派生事件不再作为 32960 历史查询的数据源。
List<EventFileRecord> records = store.query(new EventFileQuery(
ProtocolId.GB32960,
@@ -118,6 +161,40 @@ public final class Gb32960DecodedFrameService {
return out;
}
private List<DecodedFrame> queryFromTdengine(LocalDate dateFrom,
LocalDate dateTo,
java.time.Instant eventTimeFrom,
java.time.Instant eventTimeTo,
EventFileQuery.Order order,
int frameLimit,
String vin,
String platformAccount) throws IOException {
if (vin == null || vin.isBlank()) {
return List.of();
}
TdenginePage<TdengineRawFrameRow> page = tdengineReader.queryRawFrames(new TdengineRawFrameQuery(
"GB32960",
vin.trim(),
fromInstant(dateFrom, eventTimeFrom),
toExclusiveInstant(dateTo, eventTimeTo),
tdengineOrder(order),
frameLimit,
null));
List<DecodedFrame> out = new ArrayList<>(page.items().size());
LinkedHashSet<String> seenUris = new LinkedHashSet<>();
for (TdengineRawFrameRow row : page.items()) {
String rawArchiveUri = row.rawUri();
if (rawArchiveUri == null || rawArchiveUri.isBlank() || !seenUris.add(rawArchiveUri)) {
continue;
}
DecodedFrame frame = decodeIfAvailable(row, platformAccount);
if (frame != null) {
out.add(frame);
}
}
return out;
}
public List<LogicalSnapshot> snapshots(QueryTimeRange range,
EventFileQuery.Order order,
int limit,
@@ -182,9 +259,11 @@ public final class Gb32960DecodedFrameService {
if (rawArchiveUri == null || rawArchiveUri.isBlank()) {
return null;
}
EventFileRecord record = store.findByRawArchiveUri(rawArchiveUri);
if (record != null) {
return decode(record, rawArchiveUri(record), platformAccount);
if (store != null) {
EventFileRecord record = store.findByRawArchiveUri(rawArchiveUri);
if (record != null) {
return decode(record, rawArchiveUri(record), platformAccount);
}
}
return decodeWithoutIndex(rawArchiveUri, platformAccount);
}
@@ -247,6 +326,16 @@ public final class Gb32960DecodedFrameService {
}
}
private DecodedFrame decodeIfAvailable(TdengineRawFrameRow row, String platformAccount) throws IOException {
try {
return decode(row, platformAccount);
} catch (NoSuchFileException e) {
log.warn("skip gb32960 frame because raw archive is missing frameId={} rawArchiveUri={}",
row.frameId(), row.rawUri());
return null;
}
}
private DecodedFrame decode(EventFileRecord record, String rawArchiveUri, String platformAccount) throws IOException {
Path rawPath = archivePath(rawArchiveUri);
byte[] rawBytes = Files.readAllBytes(rawPath);
@@ -270,6 +359,30 @@ public final class Gb32960DecodedFrameService {
blocks);
}
private DecodedFrame decode(TdengineRawFrameRow row, String platformAccount) throws IOException {
Path rawPath = archivePath(row.rawUri());
byte[] rawBytes = Files.readAllBytes(rawPath);
Gb32960Message message = decoder.decode(ByteBuffer.wrap(rawBytes), platformAccount(row, platformAccount));
List<Map<String, Object>> blocks = new ArrayList<>();
for (InfoBlock block : message.infoBlocks()) {
blocks.add(blockJson(block));
}
Instant ingestTime = row.receivedAt() == null ? Files.getLastModifiedTime(rawPath).toInstant() : row.receivedAt();
return new DecodedFrame(
row.frameId(),
message.header().vin(),
message.header().command().name(),
message.header().responseFlag().name(),
message.header().protocolVersion().name(),
message.header().encryptType().name(),
message.header().dataLength(),
message.header().eventTime() == null ? null : message.header().eventTime().toString(),
ingestTime.toString(),
row.rawUri(),
rawBytes.length,
blocks);
}
private DecodedFrame decodeWithoutIndex(String rawArchiveUri, String platformAccount) throws IOException {
Path rawPath = archivePath(rawArchiveUri);
byte[] rawBytes = Files.readAllBytes(rawPath);
@@ -460,6 +573,35 @@ public final class Gb32960DecodedFrameService {
return record.metadata().get("platformAccount");
}
private String platformAccount(TdengineRawFrameRow row, String override) {
if (override != null && !override.isBlank()) {
return override;
}
if (row.metadataJson() == null || row.metadataJson().isBlank()) {
return null;
}
try {
return objectMapper.readValue(row.metadataJson(), MAP_TYPE).getOrDefault("platformAccount", "").toString();
} catch (IOException ignored) {
return null;
}
}
private static Instant fromInstant(LocalDate dateFrom, Instant eventTimeFrom) {
return eventTimeFrom != null ? eventTimeFrom : dateFrom.atStartOfDay(DEFAULT_ZONE).toInstant();
}
private static Instant toExclusiveInstant(LocalDate dateTo, Instant eventTimeTo) {
Instant inclusive = eventTimeTo != null
? eventTimeTo
: dateTo.plusDays(1).atStartOfDay(DEFAULT_ZONE).toInstant().minusMillis(1);
return inclusive.plusMillis(1);
}
private static TdengineQueryOrder tdengineOrder(EventFileQuery.Order order) {
return order == EventFileQuery.Order.ASC ? TdengineQueryOrder.ASC : TdengineQueryOrder.DESC;
}
private static String rawArchiveEventId(String rawArchiveUri) {
if (rawArchiveUri == null || rawArchiveUri.isBlank()) {
return "";

View File

@@ -81,13 +81,15 @@ public class EventHistoryAutoConfiguration {
}
@Bean
@ConditionalOnBean({EventFileStore.class, Gb32960MessageDecoder.class})
@ConditionalOnBean(Gb32960MessageDecoder.class)
@ConditionalOnMissingBean
public Gb32960DecodedFrameService gb32960DecodedFrameService(EventFileStore store,
public Gb32960DecodedFrameService gb32960DecodedFrameService(ObjectProvider<EventFileStore> store,
ObjectProvider<TdengineHistoryReader> reader,
Gb32960MessageDecoder decoder,
SinkArchiveProperties archiveProperties) {
// snapshot/frame 查询通过 EventFileStore 找到 rawArchiveUri再从 archiveRoot 读取 .bin 即时解码
return new Gb32960DecodedFrameService(store, decoder, archiveRoot(archiveProperties.getPath()), null);
// 优先使用 EventFileStore 索引TDengine-only 高吞吐运行时可直接从 raw_frames 找 rawUri
return new Gb32960DecodedFrameService(store.getIfAvailable(), reader.getIfAvailable(),
decoder, archiveRoot(archiveProperties.getPath()), null);
}
@Bean