161 lines
3.7 KiB
Go
161 lines
3.7 KiB
Go
package realtime
|
|
|
|
import (
|
|
"encoding/json"
|
|
"fmt"
|
|
"sort"
|
|
"strconv"
|
|
"strings"
|
|
|
|
"lingniu-vehicle-ingest/go/vehicle-gateway/internal/envelope"
|
|
)
|
|
|
|
type RealtimeKVField struct {
|
|
Protocol envelope.Protocol
|
|
VIN string
|
|
Domain string
|
|
Field string
|
|
Value string
|
|
ValueType string
|
|
EventTimeMS int64
|
|
ReceivedAtMS int64
|
|
EventID string
|
|
}
|
|
|
|
func realtimeKVFields(env envelope.FrameEnvelope, parsed map[string]any) []RealtimeKVField {
|
|
vin := strings.TrimSpace(env.VIN)
|
|
if vin == "" {
|
|
return nil
|
|
}
|
|
eventID := env.StableEventID()
|
|
add := func(rows []RealtimeKVField, domain string, fields any) []RealtimeKVField {
|
|
flat := map[string]any{}
|
|
flattenKV("", fields, flat)
|
|
names := make([]string, 0, len(flat))
|
|
for name := range flat {
|
|
names = append(names, name)
|
|
}
|
|
sort.Strings(names)
|
|
for _, name := range names {
|
|
value, valueType, ok := stringifyKVValue(flat[name])
|
|
if !ok {
|
|
continue
|
|
}
|
|
rows = append(rows, RealtimeKVField{
|
|
Protocol: env.Protocol,
|
|
VIN: vin,
|
|
Domain: domain,
|
|
Field: name,
|
|
Value: value,
|
|
ValueType: valueType,
|
|
EventTimeMS: env.EventTimeMS,
|
|
ReceivedAtMS: env.ReceivedAtMS,
|
|
EventID: eventID,
|
|
})
|
|
}
|
|
return rows
|
|
}
|
|
|
|
var rows []RealtimeKVField
|
|
keys := make([]string, 0, len(parsed))
|
|
for key := range parsed {
|
|
keys = append(keys, key)
|
|
}
|
|
sort.Strings(keys)
|
|
metadata := map[string]any{}
|
|
for _, key := range keys {
|
|
domain := strings.TrimSpace(key)
|
|
if domain == "" {
|
|
continue
|
|
}
|
|
value := parsed[key]
|
|
switch typed := value.(type) {
|
|
case map[string]any:
|
|
rows = add(rows, domain, typed)
|
|
case []any:
|
|
rows = add(rows, domain, typed)
|
|
case []map[string]any:
|
|
rows = add(rows, domain, typed)
|
|
default:
|
|
metadata[domain] = value
|
|
}
|
|
}
|
|
if len(metadata) > 0 {
|
|
rows = add(rows, "metadata", metadata)
|
|
}
|
|
return rows
|
|
}
|
|
|
|
func flattenKV(prefix string, value any, out map[string]any) {
|
|
switch typed := value.(type) {
|
|
case map[string]any:
|
|
keys := make([]string, 0, len(typed))
|
|
for key := range typed {
|
|
keys = append(keys, key)
|
|
}
|
|
sort.Strings(keys)
|
|
for _, key := range keys {
|
|
next := key
|
|
if prefix != "" {
|
|
next = prefix + "." + key
|
|
}
|
|
flattenKV(next, typed[key], out)
|
|
}
|
|
case []any:
|
|
for index, item := range typed {
|
|
itemMap, ok := item.(map[string]any)
|
|
if !ok {
|
|
out[prefix] = typed
|
|
return
|
|
}
|
|
itemKey := strconv.Itoa(index)
|
|
if serial := strings.TrimSpace(strconvAny(itemMap["serial_no"])); serial != "" && serial != "<nil>" {
|
|
itemKey = serial
|
|
}
|
|
next := itemKey
|
|
if prefix != "" {
|
|
next = prefix + "." + itemKey
|
|
}
|
|
flattenKV(next, itemMap, out)
|
|
}
|
|
case []map[string]any:
|
|
items := make([]any, len(typed))
|
|
for index, item := range typed {
|
|
items[index] = item
|
|
}
|
|
flattenKV(prefix, items, out)
|
|
default:
|
|
if prefix != "" && value != nil {
|
|
out[prefix] = value
|
|
}
|
|
}
|
|
}
|
|
|
|
func stringifyKVValue(value any) (string, string, bool) {
|
|
switch typed := value.(type) {
|
|
case string:
|
|
text := strings.TrimSpace(typed)
|
|
return text, "string", text != ""
|
|
case bool:
|
|
return strconv.FormatBool(typed), "bool", true
|
|
case float64:
|
|
return strconv.FormatFloat(typed, 'f', -1, 64), "number", true
|
|
case float32:
|
|
return strconv.FormatFloat(float64(typed), 'f', -1, 64), "number", true
|
|
case json.Number:
|
|
return typed.String(), "number", strings.TrimSpace(typed.String()) != ""
|
|
case int:
|
|
return strconv.Itoa(typed), "number", true
|
|
case int8, int16, int32, int64:
|
|
return fmt.Sprintf("%d", typed), "number", true
|
|
case uint, uint8, uint16, uint32, uint64:
|
|
return fmt.Sprintf("%d", typed), "number", true
|
|
default:
|
|
data, err := json.Marshal(typed)
|
|
if err != nil || string(data) == "null" {
|
|
return "", "", false
|
|
}
|
|
return string(data), "json", true
|
|
}
|
|
}
|