feat: add optional parsed fields to raw frame query
This commit is contained in:
@@ -3,11 +3,13 @@ package com.lingniu.ingest.eventhistory;
|
||||
import com.fasterxml.jackson.core.type.TypeReference;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import com.lingniu.ingest.tdenginehistory.TdengineHistoryReader;
|
||||
import com.lingniu.ingest.tdenginehistory.TdengineLocationRow;
|
||||
import com.lingniu.ingest.tdenginehistory.TdenginePage;
|
||||
import com.lingniu.ingest.tdenginehistory.TdenginePageCursor;
|
||||
import com.lingniu.ingest.tdenginehistory.TdengineQueryOrder;
|
||||
import com.lingniu.ingest.tdenginehistory.TdengineRawFrameQuery;
|
||||
import com.lingniu.ingest.tdenginehistory.TdengineRawFrameRow;
|
||||
import com.lingniu.ingest.tdenginehistory.TdengineTelemetryFieldRow;
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
import io.swagger.v3.oas.annotations.Parameter;
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
@@ -22,6 +24,7 @@ import org.springframework.web.server.ResponseStatusException;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.time.Instant;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.Map;
|
||||
@@ -35,6 +38,8 @@ public final class RawFrameHistoryController {
|
||||
|
||||
private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper();
|
||||
private static final TypeReference<Map<String, Object>> MAP_TYPE = new TypeReference<>() {};
|
||||
private static final int LOCATION_ENRICH_LIMIT = 1;
|
||||
private static final int TELEMETRY_ENRICH_LIMIT = 500;
|
||||
|
||||
private final TdengineHistoryReader reader;
|
||||
|
||||
@@ -71,6 +76,8 @@ public final class RawFrameHistoryController {
|
||||
@RequestParam(defaultValue = "DESC") TdengineQueryOrder order,
|
||||
@Parameter(description = "返回原始帧数量上限,最大 1000。", example = "100")
|
||||
@RequestParam(defaultValue = "100") int limit,
|
||||
@Parameter(description = "是否按 rawUri 补充 location/telemetry 解析字段。默认 false,避免 raw 列表产生额外 TDengine 查询。", example = "false")
|
||||
@RequestParam(defaultValue = "false") boolean includeParsedFields,
|
||||
@Parameter(description = "上一页返回的 nextCursor.ts。", example = "2026-06-29T05:00:01Z")
|
||||
@RequestParam(required = false) String cursorTs,
|
||||
@Parameter(description = "上一页返回的 nextCursor.id。", example = "frame-xxx")
|
||||
@@ -93,7 +100,7 @@ public final class RawFrameHistoryController {
|
||||
order,
|
||||
limit,
|
||||
cursor(cursorTs, cursorId)));
|
||||
return RawFramePageResponse.from(page);
|
||||
return RawFramePageResponse.from(reader, page, includeParsedFields);
|
||||
}
|
||||
|
||||
private static Integer parseMessageId(String messageId) {
|
||||
@@ -144,12 +151,18 @@ public final class RawFrameHistoryController {
|
||||
List<RawFrameResponse> items,
|
||||
CursorResponse nextCursor) {
|
||||
|
||||
private static RawFramePageResponse from(TdenginePage<TdengineRawFrameRow> page) {
|
||||
private static RawFramePageResponse from(TdengineHistoryReader reader,
|
||||
TdenginePage<TdengineRawFrameRow> page,
|
||||
boolean includeParsedFields) throws IOException {
|
||||
CursorResponse next = page.nextCursor()
|
||||
.map(cursor -> new CursorResponse(cursor.ts().toString(), cursor.tieBreaker()))
|
||||
.orElse(null);
|
||||
List<RawFrameResponse> items = new java.util.ArrayList<>(page.items().size());
|
||||
for (TdengineRawFrameRow row : page.items()) {
|
||||
items.add(RawFrameResponse.from(reader, row, includeParsedFields));
|
||||
}
|
||||
return new RawFramePageResponse(
|
||||
page.items().stream().map(RawFrameResponse::from).toList(),
|
||||
items,
|
||||
next);
|
||||
}
|
||||
}
|
||||
@@ -172,12 +185,16 @@ public final class RawFrameHistoryController {
|
||||
String peer,
|
||||
String metadataJson,
|
||||
Map<String, Object> metadata,
|
||||
Map<String, Object> parsedFields,
|
||||
String protocol,
|
||||
String vehicleKey,
|
||||
String vin,
|
||||
String phone) {
|
||||
|
||||
private static RawFrameResponse from(TdengineRawFrameRow row) {
|
||||
private static RawFrameResponse from(TdengineHistoryReader reader,
|
||||
TdengineRawFrameRow row,
|
||||
boolean includeParsedFields) throws IOException {
|
||||
Map<String, Object> metadata = metadata(row.metadataJson());
|
||||
return new RawFrameResponse(
|
||||
row.ts().toString(),
|
||||
row.receivedAt().toString(),
|
||||
@@ -192,7 +209,8 @@ public final class RawFrameHistoryController {
|
||||
row.parseError(),
|
||||
row.peer(),
|
||||
row.metadataJson(),
|
||||
metadata(row.metadataJson()),
|
||||
metadata,
|
||||
parsedFields(reader, row, metadata, includeParsedFields),
|
||||
row.protocol(),
|
||||
row.vehicleKey(),
|
||||
row.vin(),
|
||||
@@ -209,5 +227,63 @@ public final class RawFrameHistoryController {
|
||||
return Map.of("_raw", metadataJson);
|
||||
}
|
||||
}
|
||||
|
||||
private static Map<String, Object> parsedFields(TdengineHistoryReader reader,
|
||||
TdengineRawFrameRow row,
|
||||
Map<String, Object> metadata,
|
||||
boolean includeParsedFields) throws IOException {
|
||||
Map<String, Object> out = new LinkedHashMap<>();
|
||||
for (Map.Entry<String, Object> entry : metadata.entrySet()) {
|
||||
String key = entry.getKey();
|
||||
if (key == null || key.isBlank()) {
|
||||
continue;
|
||||
}
|
||||
if (key.startsWith("raw.") || key.startsWith("jt808.")) {
|
||||
out.put(key, entry.getValue());
|
||||
} else {
|
||||
out.put("raw." + key, entry.getValue());
|
||||
}
|
||||
}
|
||||
if (!includeParsedFields) {
|
||||
return Map.copyOf(out);
|
||||
}
|
||||
for (TdengineLocationRow location : reader.queryLocationsByRawUri(
|
||||
row.protocol(), row.rawUri(), LOCATION_ENRICH_LIMIT)) {
|
||||
addLocation(out, location);
|
||||
}
|
||||
for (TdengineTelemetryFieldRow field : reader.queryTelemetryFieldsByRawUri(
|
||||
row.protocol(), row.rawUri(), TELEMETRY_ENRICH_LIMIT)) {
|
||||
out.put(field.fieldKey(), typedValue(field));
|
||||
out.put(field.fieldKey() + "._type", field.valueType());
|
||||
if (field.unit() != null && !field.unit().isBlank()) {
|
||||
out.put(field.fieldKey() + "._unit", field.unit());
|
||||
}
|
||||
}
|
||||
return Map.copyOf(out);
|
||||
}
|
||||
|
||||
private static void addLocation(Map<String, Object> out, TdengineLocationRow row) {
|
||||
out.put("location.longitude", row.longitude());
|
||||
out.put("location.latitude", row.latitude());
|
||||
out.put("location.altitudeM", row.altitudeM());
|
||||
out.put("location.speedKmh", row.speedKmh());
|
||||
out.put("location.directionDeg", row.directionDeg());
|
||||
out.put("location.alarmFlag", row.alarmFlag());
|
||||
out.put("location.statusFlag", row.statusFlag());
|
||||
if (row.totalMileageKm() != null) {
|
||||
out.put("location.totalMileageKm", row.totalMileageKm());
|
||||
}
|
||||
}
|
||||
|
||||
private static Object typedValue(TdengineTelemetryFieldRow field) {
|
||||
if (field.valueDouble() != null) {
|
||||
return field.valueDouble();
|
||||
}
|
||||
if (field.valueLong() != null) {
|
||||
return field.valueLong();
|
||||
}
|
||||
return field.valueText();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user