package config import ( "errors" "fmt" "os" "strings" "github.com/spf13/viper" ) // Config 全局配置 type Config struct { Server ServerConfig `mapstructure:"server"` MySQL MySQLConfig `mapstructure:"mysql"` MinIO MinIOConfig `mapstructure:"minio"` JWT JWTConfig `mapstructure:"jwt"` WeChat WeChatConfig `mapstructure:"wechat"` Auth AuthConfig `mapstructure:"auth"` Admin AdminConfig `mapstructure:"admin"` AI AIConfig `mapstructure:"ai"` } // AIConfig 大模型配置(provider 中立,openai 兼容 base_url) type AIConfig struct { Enabled bool `mapstructure:"enabled"` Provider string `mapstructure:"provider"` // openai(兼容) / mock BaseURL string `mapstructure:"base_url"` // 如 https://api.deepseek.com APIKey string `mapstructure:"api_key"` // 用 PETS_AI_API_KEY 覆盖 Model string `mapstructure:"model"` // 如 deepseek-chat Temperature float64 `mapstructure:"temperature"` MaxTokens int `mapstructure:"max_tokens"` TimeoutSec int `mapstructure:"timeout_sec"` } type ServerConfig struct { Port int `mapstructure:"port"` Mode string `mapstructure:"mode"` } type MySQLConfig struct { Host string `mapstructure:"host"` Port int `mapstructure:"port"` User string `mapstructure:"user"` Password string `mapstructure:"password"` Database string `mapstructure:"database"` Charset string `mapstructure:"charset"` } // DSN 返回 GORM MySQL 连接串 func (m MySQLConfig) DSN() string { return fmt.Sprintf( "%s:%s@tcp(%s:%d)/%s?charset=%s&parseTime=True&loc=Local", m.User, m.Password, m.Host, m.Port, m.Database, m.Charset, ) } type MinIOConfig struct { Endpoint string `mapstructure:"endpoint"` AccessKey string `mapstructure:"access_key"` SecretKey string `mapstructure:"secret_key"` Bucket string `mapstructure:"bucket"` UseSSL bool `mapstructure:"use_ssl"` PublicBaseURL string `mapstructure:"public_base_url"` } type JWTConfig struct { Secret string `mapstructure:"secret"` ExpireHours int `mapstructure:"expire_hours"` } type WeChatConfig struct { AppID string `mapstructure:"app_id"` AppSecret string `mapstructure:"app_secret"` } type AuthConfig struct { DevLogin bool `mapstructure:"dev_login"` } type AdminConfig struct { Username string `mapstructure:"username"` Password string `mapstructure:"password"` } // setDefaults 为每个配置项登记默认值。 // 必须登记:viper 的 AutomaticEnv 只对「已知的 key」生效,未登记的 key 即使设了 // PETS_XXX 环境变量,Unmarshal 时也绑不上(容器里纯 .env 部署会静默取到空值)。 func setDefaults(v *viper.Viper) { v.SetDefault("server.port", 9090) v.SetDefault("server.mode", "release") v.SetDefault("mysql.host", "127.0.0.1") v.SetDefault("mysql.port", 3306) v.SetDefault("mysql.user", "root") v.SetDefault("mysql.password", "") v.SetDefault("mysql.database", "pets") v.SetDefault("mysql.charset", "utf8mb4") v.SetDefault("minio.endpoint", "127.0.0.1:9000") v.SetDefault("minio.access_key", "") v.SetDefault("minio.secret_key", "") v.SetDefault("minio.bucket", "pets") v.SetDefault("minio.use_ssl", false) v.SetDefault("minio.public_base_url", "") v.SetDefault("jwt.secret", "") v.SetDefault("jwt.expire_hours", 168) v.SetDefault("wechat.app_id", "") v.SetDefault("wechat.app_secret", "") v.SetDefault("auth.dev_login", false) v.SetDefault("admin.username", "sundynix") v.SetDefault("admin.password", "") v.SetDefault("ai.enabled", false) v.SetDefault("ai.provider", "openai") v.SetDefault("ai.base_url", "") v.SetDefault("ai.api_key", "") v.SetDefault("ai.model", "") v.SetDefault("ai.temperature", 0.6) v.SetDefault("ai.max_tokens", 1024) v.SetDefault("ai.timeout_sec", 30) } // Load 载入配置:默认值 → configs/config.yaml(可选)→ 环境变量覆盖。 // 环境变量用 PETS_ 前缀、点转下划线,例如 PETS_MYSQL_PASSWORD 覆盖 mysql.password。 // 容器部署不带 config.yaml,全部走 .env 注入的环境变量。 func Load() (*Config, error) { v := viper.New() setDefaults(v) v.SetConfigName("config") v.SetConfigType("yaml") v.AddConfigPath("./configs") v.AddConfigPath("../../configs") v.AddConfigPath(".") v.SetEnvPrefix("PETS") v.SetEnvKeyReplacer(strings.NewReplacer(".", "_")) v.AutomaticEnv() // 配置文件可选:找不到就只用默认值 + 环境变量;文件存在但解析失败仍要报错。 if err := v.ReadInConfig(); err != nil { var notFound viper.ConfigFileNotFoundError if !errors.As(err, ¬Found) && !os.IsNotExist(err) { return nil, fmt.Errorf("read config: %w", err) } } var cfg Config if err := v.Unmarshal(&cfg); err != nil { return nil, fmt.Errorf("unmarshal config: %w", err) } return &cfg, nil }