135 lines
3.3 KiB
Go
135 lines
3.3 KiB
Go
package main
|
|
|
|
import (
|
|
"context"
|
|
"log/slog"
|
|
"os"
|
|
"os/signal"
|
|
"strings"
|
|
"sync"
|
|
"syscall"
|
|
"time"
|
|
|
|
"lingniu-vehicle-ingest/go/vehicle-gateway/internal/envelope"
|
|
"lingniu-vehicle-ingest/go/vehicle-gateway/internal/eventbus"
|
|
"lingniu-vehicle-ingest/go/vehicle-gateway/internal/gateway"
|
|
"lingniu-vehicle-ingest/go/vehicle-gateway/internal/observability"
|
|
"lingniu-vehicle-ingest/go/vehicle-gateway/internal/protocol/gb32960"
|
|
"lingniu-vehicle-ingest/go/vehicle-gateway/internal/protocol/jt808"
|
|
)
|
|
|
|
func main() {
|
|
logger := observability.NewLogger("vehicle-gateway")
|
|
ctx, stop := signal.NotifyContext(context.Background(), os.Interrupt, syscall.SIGTERM)
|
|
defer stop()
|
|
|
|
sink, err := buildSink(logger)
|
|
if err != nil {
|
|
logger.Error("build sink failed", "error", err)
|
|
os.Exit(1)
|
|
}
|
|
defer sink.Close()
|
|
|
|
protocols := []gateway.TCPProtocol{
|
|
{
|
|
Protocol: envelope.ProtocolGB32960,
|
|
Addr: env("GB32960_TCP_ADDR", ":32960"),
|
|
Extract: gb32960.ExtractFrames,
|
|
Parse: gb32960.ParseFrame,
|
|
},
|
|
{
|
|
Protocol: envelope.ProtocolJT808,
|
|
Addr: env("JT808_TCP_ADDR", ":808"),
|
|
Extract: jt808.ExtractFrames,
|
|
Parse: jt808.ParseFrame,
|
|
},
|
|
}
|
|
|
|
var wg sync.WaitGroup
|
|
errs := make(chan error, len(protocols))
|
|
for _, protocol := range protocols {
|
|
server, err := gateway.NewTCPServer(gateway.TCPServerConfig{
|
|
Protocol: protocol,
|
|
Sink: sink,
|
|
Logger: logger,
|
|
ReadBufferSize: envInt("TCP_READ_BUFFER_BYTES", 64*1024),
|
|
IdleTimeout: time.Duration(envInt("TCP_IDLE_TIMEOUT_SECONDS", 180)) * time.Second,
|
|
MaxConnections: envInt("TCP_MAX_CONNECTIONS", 20_000),
|
|
})
|
|
if err != nil {
|
|
logger.Error("build tcp server failed", "protocol", protocol.Protocol, "error", err)
|
|
os.Exit(1)
|
|
}
|
|
wg.Add(1)
|
|
go func() {
|
|
defer wg.Done()
|
|
if err := server.ListenAndServe(ctx); err != nil {
|
|
errs <- err
|
|
}
|
|
}()
|
|
}
|
|
|
|
logger.Info("vehicle gateway started")
|
|
select {
|
|
case <-ctx.Done():
|
|
case err := <-errs:
|
|
logger.Error("vehicle gateway listener failed", "error", err)
|
|
stop()
|
|
}
|
|
wg.Wait()
|
|
}
|
|
|
|
func buildSink(logger *slog.Logger) (eventbus.Sink, error) {
|
|
brokers := splitCSV(os.Getenv("KAFKA_BROKERS"))
|
|
if len(brokers) == 0 {
|
|
logger.Warn("KAFKA_BROKERS is empty; using log sink")
|
|
return eventbus.NewLogSink(logger), nil
|
|
}
|
|
return eventbus.NewKafkaSink(eventbus.KafkaConfig{
|
|
Brokers: brokers,
|
|
RawTopics: map[envelope.Protocol]string{
|
|
envelope.ProtocolGB32960: env("KAFKA_TOPIC_GB32960_RAW", "vehicle.raw.gb32960.v1"),
|
|
envelope.ProtocolJT808: env("KAFKA_TOPIC_JT808_RAW", "vehicle.raw.jt808.v1"),
|
|
envelope.ProtocolYutongMQTT: env("KAFKA_TOPIC_YUTONG_MQTT_RAW", "vehicle.raw.yutong-mqtt.v1"),
|
|
},
|
|
UnifiedTopic: env("KAFKA_TOPIC_UNIFIED", "vehicle.event.unified.v1"),
|
|
})
|
|
}
|
|
|
|
func env(key string, fallback string) string {
|
|
value := strings.TrimSpace(os.Getenv(key))
|
|
if value == "" {
|
|
return fallback
|
|
}
|
|
return value
|
|
}
|
|
|
|
func envInt(key string, fallback int) int {
|
|
value := strings.TrimSpace(os.Getenv(key))
|
|
if value == "" {
|
|
return fallback
|
|
}
|
|
var out int
|
|
for _, r := range value {
|
|
if r < '0' || r > '9' {
|
|
return fallback
|
|
}
|
|
out = out*10 + int(r-'0')
|
|
}
|
|
if out <= 0 {
|
|
return fallback
|
|
}
|
|
return out
|
|
}
|
|
|
|
func splitCSV(value string) []string {
|
|
var out []string
|
|
for _, item := range strings.Split(value, ",") {
|
|
item = strings.TrimSpace(item)
|
|
if item != "" {
|
|
out = append(out, item)
|
|
}
|
|
}
|
|
return out
|
|
}
|