feat(go): reuse parsed fields across projections

This commit is contained in:
lingniu
2026-07-03 17:38:50 +08:00
parent 368b18fc96
commit 378a5d5e57
11 changed files with 620 additions and 64 deletions

View File

@@ -26,33 +26,27 @@ func BuildFieldsEnvelope(env envelope.FrameEnvelope) (envelope.FrameEnvelope, bo
if !envelope.IsRealtimeTelemetryFrame(env) {
return envelope.FrameEnvelope{}, false
}
rows := realtimeKVFields(env, env.Parsed)
fields := make(map[string]any, len(rows))
for _, row := range rows {
key := row.Domain
if strings.TrimSpace(row.Field) != "" {
key += "." + row.Field
}
fields[key] = row.Value
}
if len(fields) == 0 {
fields, fieldTypes, ok := ParsedFieldsForEnvelope(env)
if !ok {
return envelope.FrameEnvelope{}, false
}
out := envelope.FrameEnvelope{
EventID: env.StableEventID() + ":fields",
TraceID: env.TraceID,
Protocol: env.Protocol,
MessageID: env.MessageID,
Sequence: env.Sequence,
VIN: env.VIN,
VehicleKeyHint: env.VehicleKeyHint,
Phone: env.Phone,
DeviceID: env.DeviceID,
Plate: env.Plate,
SourceEndpoint: env.SourceEndpoint,
EventTimeMS: env.EventTimeMS,
ReceivedAtMS: env.ReceivedAtMS,
Fields: fields,
EventID: env.StableEventID() + ":fields",
TraceID: env.TraceID,
Protocol: env.Protocol,
MessageID: env.MessageID,
Sequence: env.Sequence,
VIN: env.VIN,
VehicleKeyHint: env.VehicleKeyHint,
Phone: env.Phone,
DeviceID: env.DeviceID,
Plate: env.Plate,
SourceEndpoint: env.SourceEndpoint,
EventTimeMS: env.EventTimeMS,
ReceivedAtMS: env.ReceivedAtMS,
Fields: fields,
ParsedFields: fields,
ParsedFieldTypes: fieldTypes,
Parsed: map[string]any{
"source_event_id": env.StableEventID(),
"field_mapping": realtimeFieldMappingVersion,
@@ -62,6 +56,91 @@ func BuildFieldsEnvelope(env envelope.FrameEnvelope) (envelope.FrameEnvelope, bo
return out, true
}
func EnsureParsedFields(env *envelope.FrameEnvelope) bool {
if env == nil {
return false
}
if len(env.ParsedFields) > 0 {
if env.ParsedFieldTypes == nil {
env.ParsedFieldTypes = inferParsedFieldTypes(env.ParsedFields)
}
return true
}
fields, fieldTypes, ok := ParsedFieldsForEnvelope(*env)
if !ok {
return false
}
env.ParsedFields = fields
env.ParsedFieldTypes = fieldTypes
return true
}
func ParsedFieldsForEnvelope(env envelope.FrameEnvelope) (map[string]any, map[string]string, bool) {
if len(env.ParsedFields) > 0 {
fields := cloneAnyMap(env.ParsedFields)
fieldTypes := cloneStringMap(env.ParsedFieldTypes)
if len(fieldTypes) == 0 {
fieldTypes = inferParsedFieldTypes(fields)
}
return fields, fieldTypes, true
}
rows := realtimeKVFields(env, env.Parsed)
if len(rows) == 0 {
return nil, nil, false
}
fields := make(map[string]any, len(rows))
fieldTypes := make(map[string]string, len(rows))
for _, row := range rows {
key := realtimeKVFieldPath(row.Domain, row.Field)
if key == "" {
continue
}
fields[key] = row.Value
fieldTypes[key] = row.ValueType
}
if len(fields) == 0 {
return nil, nil, false
}
return fields, fieldTypes, true
}
func inferParsedFieldTypes(fields map[string]any) map[string]string {
if len(fields) == 0 {
return nil
}
out := make(map[string]string, len(fields))
for key, value := range fields {
_, valueType, ok := stringifyKVValue(value)
if !ok {
continue
}
out[key] = valueType
}
return out
}
func cloneAnyMap(in map[string]any) map[string]any {
if len(in) == 0 {
return nil
}
out := make(map[string]any, len(in))
for key, value := range in {
out[key] = value
}
return out
}
func cloneStringMap(in map[string]string) map[string]string {
if len(in) == 0 {
return nil
}
out := make(map[string]string, len(in))
for key, value := range in {
out[key] = value
}
return out
}
type protocolFieldMapping struct {
Prefix string
GBDataUnits bool