package config import ( "crypto/md5" "encoding/hex" "fmt" "github.com/spf13/viper" ) type AppConfig struct { AppEnv string AppPort string LogLevel string RedisAddr string MySQLDSN string PostgresDSN string MongoDSN string MinIoEndpoint string MinioAccessKey string MinioSecretKey string MinioBucketName string AsynqmonPort string } type FwhApiConfig struct { FwhEnergy string FwhOss string FwhUser string FwhPassword string FwhTotpSecret string FwhTotpField string } type FwhMqttConfig struct { FwhMqttBroker string FwhMqttCaUrl string FwhMqttCertUrl string FwhMqttKeyUrl string } // FwhEsConfig Elasticsearch(iotcore-stat) 连接配置 type FwhEsConfig struct { // EsHost 形如 "alb-prod.franklinwh.click:5603"(不含 scheme) EsHost string // EsUser / EsPassword basic auth 凭证 EsUser string EsPassword string // EsIndexPrefix 索引前缀,形如 "iotcore-stat", // 实际索引名会拼接日期,如 "iotcore-stat-2026.09.07" EsIndexPrefix string // EsInsecureSkipVerify 是否跳过 TLS 证书校验(对应 curl -k) EsInsecureSkipVerify bool } type PlaneApiConfig struct { Host string ApiKey string WorkspaceSlug string ProjectId string LabelBugId string LabelFeatureId string } type PlaneWebApiConfig struct { Host string Email string Password string WorkspaceSlug string ProjectId string LabelBugId string LabelFeatureId string LabelFeedbackId string } type KiroConfig struct { // RunAsUser 指定拉起 kiro-cli 子进程时降权到的系统用户名。 // 为空时表示不降权,沿用后台服务自身的运行用户。 RunAsUser string // Home 指定拉起 kiro-cli 子进程时使用的 HOME 目录(即 $HOME 环境变量)。 // kiro-cli 依赖 HOME 定位其配置与凭证。为空时回退到降权用户的家目录, // 若也无法推导则使用默认值。 Home string // DebugLog 为调试日志总开关(宏开关)。 // 开启后,拉起 kiro-cli 子进程时注入 KIRO_LOG_LEVEL=debug, // 让 kiro-cli 输出 debug 级日志,便于问题排查。 // 由环境变量 KIRO_DEBUG_LOG 控制(true/1/on 开启,默认关闭)。 DebugLog bool } type Config struct { App AppConfig FwhApi FwhApiConfig FwhTestApi FwhApiConfig FwhMqtt FwhMqttConfig FwhTestMqtt FwhMqttConfig FwhEs FwhEsConfig PlaneApi PlaneApiConfig PlaneWebApi PlaneWebApiConfig Kiro KiroConfig } // 需先执行LoadConfig后才能读取ENV // var APP_ENV string = "dev" // var FWH_ENERGY string = "energy.franklinwh.com" // var FWH_OSS string = "oss.franklinwh.com" // var FWH_USER string = "" // var FWH_PASSWORD string = "" // MD5 - PASSWORD // LoadConfig 读取配置文件和环境变量。 // 可选参数 configPaths 指定候选配置文件路径(按顺序尝试,命中第一个存在的即停)。 // 不传时使用默认路径 ["./.env", "./config.env"](后端主服务用)。 // MCP 等独立进程可传入自己的配置文件,例如 LoadConfig("./config.mcp.env")。 func LoadConfig(configPaths ...string) (*Config, error) { v := viper.New() // 默认值(避免缺省时崩溃) // APP v.SetDefault("APP_ENV", "dev") v.SetDefault("APP_PORT", "8080") v.SetDefault("REDIS_ADDR", "localhost:6379") v.SetDefault("MYSQL_DSN", "") v.SetDefault("POSTGRES_DSN", "host=localhost user=fwh_toolbox password=fwh_toolbox dbname=fwh_toolbox_db port=5432 sslmode=disable") v.SetDefault("MONGO_DSN", "mongodb://localhost:27017/FwhToolboxDbDev") v.SetDefault("MINIO_ENDPOINT", "localhost:9198") v.SetDefault("MINIO_ACCESS_KEY", "fwh-toolbox") v.SetDefault("MINIO_SECRET_KEY", "fwh-toolbox") v.SetDefault("MINIO_BUCKET_NAME", "fwh-toolbox") v.SetDefault("ASYNQMON_PORT", "8081") // FWH API v.SetDefault("FWH_ENERGY", "energy.franklinwh.com") v.SetDefault("FWH_OSS", "oss.franklinwh.com") // FWH MQTT // FWH ES(iotcore-stat) v.SetDefault("FWH_ES_INDEX_PREFIX", "iotcore-stat") v.SetDefault("FWH_ES_INSECURE_SKIP_VERIFY", true) // 多文件路径支持:优先使用调用方传入的路径,否则回退默认 paths := configPaths if len(paths) == 0 { paths = []string{"./.env", "./config.env"} } var readErr error for _, path := range paths { v.SetConfigFile(path) if err := v.ReadInConfig(); err == nil { fmt.Printf("✅ Using config file: %s\n", path) readErr = nil break } else { readErr = err } } // 环境变量覆盖文件配置 v.AutomaticEnv() // 如果两个文件都没读到,也没关系,直接用默认值 if readErr != nil { fmt.Println("⚠️ No config file found, using default values & environment variables") } // APP_ENV = v.GetString("APP_ENV") // FWH_ENERGY = v.GetString("FWH_ENERGY") // FWH_OSS = v.GetString("FWH_OSS") // FWH_USER = v.GetString("FWH_USER") // FWH_PASSWORD = v.GetString("FWH_PASSWORD") h := md5.New() h.Write([]byte(v.GetString("FWH_PASSWORD"))) fwhApiPassword := hex.EncodeToString(h.Sum(nil)) h = md5.New() h.Write([]byte(v.GetString("FWH_TEST_PASSWORD"))) fwhTestApiPassword := hex.EncodeToString(h.Sum(nil)) return &Config{ App: AppConfig{ AppEnv: v.GetString("APP_ENV"), AppPort: v.GetString("APP_PORT"), LogLevel: v.GetString("LOG_LEVEL"), RedisAddr: v.GetString("REDIS_ADDR"), MySQLDSN: v.GetString("MYSQL_DSN"), PostgresDSN: v.GetString("POSTGRES_DSN"), MongoDSN: v.GetString("MONGO_DSN"), MinIoEndpoint: v.GetString("MINIO_ENDPOINT"), MinioAccessKey: v.GetString("MINIO_ACCESS_KEY"), MinioSecretKey: v.GetString("MINIO_SECRET_KEY"), MinioBucketName: v.GetString("MINIO_BUCKET_NAME"), AsynqmonPort: v.GetString("ASYNQMON_PORT"), }, FwhApi: FwhApiConfig{ FwhEnergy: v.GetString("FWH_ENERGY"), FwhOss: v.GetString("FWH_OSS"), FwhUser: v.GetString("FWH_USER"), FwhPassword: fwhApiPassword, FwhTotpSecret: v.GetString("FWH_TOTP_SECRET"), FwhTotpField: v.GetString("FWH_TOTP_FIELD"), }, FwhTestApi: FwhApiConfig{ FwhEnergy: v.GetString("FWH_TEST_ENERGY"), FwhOss: v.GetString("FWH_TEST_OSS"), FwhUser: v.GetString("FWH_TEST_USER"), FwhPassword: fwhTestApiPassword, }, FwhMqtt: FwhMqttConfig{ FwhMqttBroker: v.GetString("FWH_MQTT_BROKER"), FwhMqttCaUrl: v.GetString("FWH_MQTT_CA"), FwhMqttCertUrl: v.GetString("FWH_MQTT_CERT"), FwhMqttKeyUrl: v.GetString("FWH_MQTT_KEY"), }, FwhTestMqtt: FwhMqttConfig{ FwhMqttBroker: v.GetString("FWH_TEST_MQTT_BROKER"), FwhMqttCaUrl: v.GetString("FWH_TEST_MQTT_CA"), FwhMqttCertUrl: v.GetString("FWH_TEST_MQTT_CERT"), FwhMqttKeyUrl: v.GetString("FWH_TEST_MQTT_KEY"), }, FwhEs: FwhEsConfig{ EsHost: v.GetString("FWH_ES_HOST"), EsUser: v.GetString("FWH_ES_USER"), EsPassword: v.GetString("FWH_ES_PASSWORD"), EsIndexPrefix: v.GetString("FWH_ES_INDEX_PREFIX"), EsInsecureSkipVerify: v.GetBool("FWH_ES_INSECURE_SKIP_VERIFY"), }, PlaneApi: PlaneApiConfig{ Host: v.GetString("PLANE_HOST"), ApiKey: v.GetString("PLANE_API_KEY"), WorkspaceSlug: v.GetString("PLANE_WORKSPACE_SLUG"), ProjectId: v.GetString("PLANE_PROJECT_ID"), LabelBugId: v.GetString("PLANE_LABEL_BUG"), LabelFeatureId: v.GetString("PLANE_LABEL_FEATURE"), }, PlaneWebApi: PlaneWebApiConfig{ Host: v.GetString("PLANE_WEB_HOST"), Email: v.GetString("PLANE_WEB_EMAIL"), Password: v.GetString("PLANE_WEB_PASSWORD"), WorkspaceSlug: v.GetString("PLANE_WEB_WORKSPACE_SLUG"), ProjectId: v.GetString("PLANE_WEB_PROJECT_ID"), LabelBugId: v.GetString("PLANE_WEB_LABEL_BUG"), LabelFeatureId: v.GetString("PLANE_WEB_LABEL_FEATURE"), LabelFeedbackId: v.GetString("PLANE_WEB_LABEL_FEEDBACK"), }, Kiro: KiroConfig{ RunAsUser: v.GetString("KIRO_RUN_AS_USER"), Home: v.GetString("KIRO_HOME"), DebugLog: v.GetBool("KIRO_DEBUG_LOG"), }, }, nil }