package config import ( "os" "strconv" "strings" "time" ) type Config struct { HTTPAddr string StaticDir string MySQLDSN string RedisAddr string RedisUsername string RedisPassword string RedisDB int TDengineDriver string TDengineDSN string TDengineDatabase string CapacityCheckBin string AuthToken string AuthMode string AuthTokensJSON string BootstrapAdminUsername string BootstrapAdminPassword string SessionTTL time.Duration DataMode string ExportDir string RequestTimeout time.Duration AMapWebJSKey string AMapAPIKey string AMapSecurityCode string AMapServiceHost string PlatformRelease string AlertEvaluationInterval time.Duration AlertStreamMode string AlertStreamKafkaBrokers []string AlertStreamKafkaTopics []string AlertStreamKafkaGroup string AlertStreamBatchSize int AlertStreamBatchWait time.Duration AlertStreamLateness time.Duration } func Load() Config { return Config{ HTTPAddr: env("HTTP_ADDR", ":20300"), StaticDir: env("STATIC_DIR", ""), MySQLDSN: os.Getenv("MYSQL_DSN"), RedisAddr: os.Getenv("REDIS_ADDR"), RedisUsername: os.Getenv("REDIS_USERNAME"), RedisPassword: os.Getenv("REDIS_PASSWORD"), RedisDB: envInt("REDIS_DB", 50), TDengineDriver: env("TDENGINE_DRIVER", "taosWS"), TDengineDSN: os.Getenv("TDENGINE_DSN"), TDengineDatabase: env("TDENGINE_DATABASE", "lingniu_vehicle_ts"), CapacityCheckBin: os.Getenv("CAPACITY_CHECK_BIN"), AuthToken: os.Getenv("AUTH_TOKEN"), AuthMode: authMode(), AuthTokensJSON: os.Getenv("AUTH_TOKENS_JSON"), BootstrapAdminUsername: os.Getenv("BOOTSTRAP_ADMIN_USERNAME"), BootstrapAdminPassword: os.Getenv("BOOTSTRAP_ADMIN_PASSWORD"), SessionTTL: time.Duration(envInt("AUTH_SESSION_TTL_HOURS", 12)) * time.Hour, DataMode: dataMode(), ExportDir: os.Getenv("EXPORT_DIR"), RequestTimeout: time.Duration(envInt("REQUEST_TIMEOUT_MS", 5000)) * time.Millisecond, AMapWebJSKey: os.Getenv("AMAP_WEB_JS_KEY"), AMapAPIKey: os.Getenv("AMAP_API_KEY"), AMapSecurityCode: os.Getenv("AMAP_SECURITY_JS_CODE"), AMapServiceHost: os.Getenv("AMAP_SECURITY_SERVICE_HOST"), PlatformRelease: os.Getenv("PLATFORM_RELEASE"), AlertEvaluationInterval: time.Duration(envInt("ALERT_EVALUATION_INTERVAL_SEC", 10)) * time.Second, AlertStreamMode: strings.ToLower(env("ALERT_STREAM_MODE", "disabled")), AlertStreamKafkaBrokers: splitCSV(firstEnv("ALERT_STREAM_KAFKA_BROKERS", "KAFKA_BROKERS")), AlertStreamKafkaTopics: splitCSV(env("ALERT_STREAM_KAFKA_TOPICS", "vehicle.fields.go.gb32960.v1,vehicle.fields.go.jt808.v1,vehicle.fields.go.yutong-mqtt.v1")), AlertStreamKafkaGroup: env("ALERT_STREAM_KAFKA_GROUP", "vehicle-alert-stream-v1"), AlertStreamBatchSize: envInt("ALERT_STREAM_BATCH_SIZE", 200), AlertStreamBatchWait: time.Duration(envInt("ALERT_STREAM_BATCH_WAIT_MS", 100)) * time.Millisecond, AlertStreamLateness: time.Duration(envInt("ALERT_STREAM_LATENESS_SEC", 120)) * time.Second, } } func firstEnv(keys ...string) string { for _, key := range keys { if value := os.Getenv(key); value != "" { return value } } return "" } func splitCSV(raw string) []string { parts := strings.Split(raw, ",") values := make([]string, 0, len(parts)) seen := map[string]bool{} for _, part := range parts { part = strings.TrimSpace(part) if part != "" && !seen[part] { seen[part] = true values = append(values, part) } } return values } func dataMode() string { if value := os.Getenv("DATA_MODE"); value != "" { return value } if os.Getenv("MYSQL_DSN") != "" { return "production" } return "mock" } func authMode() string { if value := os.Getenv("AUTH_MODE"); value != "" { return value } if os.Getenv("AUTH_TOKEN") != "" || os.Getenv("AUTH_TOKENS_JSON") != "" { return "enforce" } return "disabled" } func env(key, fallback string) string { if value := os.Getenv(key); value != "" { return value } return fallback } func envInt(key string, fallback int) int { raw := os.Getenv(key) if raw == "" { return fallback } value, err := strconv.Atoi(raw) if err != nil { return fallback } return value }