package config import ( "fmt" "os" "strconv" "strings" "time" "github.com/joho/godotenv" "github.com/spf13/viper" ) // Config 应用配置结构体 type Config struct { Environment string `mapstructure:"environment"` Site SiteConfig `mapstructure:"site"` Server ServerConfig `mapstructure:"server"` Database DatabaseConfig `mapstructure:"database"` Redis RedisConfig `mapstructure:"redis"` RustFS RustFSConfig `mapstructure:"rustfs"` JWT JWTConfig `mapstructure:"jwt"` Casbin CasbinConfig `mapstructure:"casbin"` Log LogConfig `mapstructure:"log"` Email EmailConfig `mapstructure:"email"` Security SecurityConfig `mapstructure:"security"` } // SiteConfig 站点配置 type SiteConfig struct { Name string `mapstructure:"name"` Description string `mapstructure:"description"` RegistrationEnabled bool `mapstructure:"registration_enabled"` DefaultAvatar string `mapstructure:"default_avatar"` MaxTexturesPerUser int `mapstructure:"max_textures_per_user"` MaxProfilesPerUser int `mapstructure:"max_profiles_per_user"` CheckinReward int `mapstructure:"checkin_reward"` TextureDownloadReward int `mapstructure:"texture_download_reward"` } // ServerConfig 服务器配置 type ServerConfig struct { Port string `mapstructure:"port"` Mode string `mapstructure:"mode"` ReadTimeout time.Duration `mapstructure:"read_timeout"` WriteTimeout time.Duration `mapstructure:"write_timeout"` } // DatabaseConfig 数据库配置 type DatabaseConfig struct { Driver string `mapstructure:"driver"` Host string `mapstructure:"host"` Port int `mapstructure:"port"` Username string `mapstructure:"username"` Password string `mapstructure:"password"` Database string `mapstructure:"database"` SSLMode string `mapstructure:"ssl_mode"` Timezone string `mapstructure:"timezone"` MaxIdleConns int `mapstructure:"max_idle_conns"` MaxOpenConns int `mapstructure:"max_open_conns"` ConnMaxLifetime time.Duration `mapstructure:"conn_max_lifetime"` ConnMaxIdleTime time.Duration `mapstructure:"conn_max_idle_time"` // 连接最大空闲时间 } // RedisConfig Redis配置 type RedisConfig struct { Host string `mapstructure:"host"` Port int `mapstructure:"port"` Password string `mapstructure:"password"` Database int `mapstructure:"database"` PoolSize int `mapstructure:"pool_size"` // 连接池大小 MinIdleConns int `mapstructure:"min_idle_conns"` // 最小空闲连接数 MaxRetries int `mapstructure:"max_retries"` // 最大重试次数 DialTimeout time.Duration `mapstructure:"dial_timeout"` // 连接超时 ReadTimeout time.Duration `mapstructure:"read_timeout"` // 读取超时 WriteTimeout time.Duration `mapstructure:"write_timeout"` // 写入超时 PoolTimeout time.Duration `mapstructure:"pool_timeout"` // 连接池超时 ConnMaxIdleTime time.Duration `mapstructure:"conn_max_idle_time"` // 连接最大空闲时间 } // RustFSConfig RustFS对象存储配置 (S3兼容) type RustFSConfig struct { Endpoint string `mapstructure:"endpoint"` PublicURL string `mapstructure:"public_url"` // 公开访问URL (用于生成文件访问链接) AccessKey string `mapstructure:"access_key"` SecretKey string `mapstructure:"secret_key"` UseSSL bool `mapstructure:"use_ssl"` Buckets map[string]string `mapstructure:"buckets"` } // JWTConfig JWT配置 type JWTConfig struct { Secret string `mapstructure:"secret"` ExpireHours int `mapstructure:"expire_hours"` } // CasbinConfig Casbin权限配置 type CasbinConfig struct { ModelPath string `mapstructure:"model_path"` PolicyAdapter string `mapstructure:"policy_adapter"` } // LogConfig 日志配置 type LogConfig struct { Level string `mapstructure:"level"` Format string `mapstructure:"format"` Output string `mapstructure:"output"` MaxSize int `mapstructure:"max_size"` MaxBackups int `mapstructure:"max_backups"` MaxAge int `mapstructure:"max_age"` Compress bool `mapstructure:"compress"` } // EmailConfig 邮件配置 type EmailConfig struct { Enabled bool `mapstructure:"enabled"` SMTPHost string `mapstructure:"smtp_host"` SMTPPort int `mapstructure:"smtp_port"` Username string `mapstructure:"username"` Password string `mapstructure:"password"` FromName string `mapstructure:"from_name"` } // SecurityConfig 安全配置 type SecurityConfig struct { AllowedOrigins []string `mapstructure:"allowed_origins"` // 允许的CORS来源 AllowedDomains []string `mapstructure:"allowed_domains"` // 允许的头像/材质URL域名 } // Load 加载配置 - 完全从环境变量加载,不依赖YAML文件 func Load() (*Config, error) { // 加载.env文件(如果存在) _ = godotenv.Load(".env") // 设置默认值 setDefaults() // 设置环境变量前缀 viper.SetEnvPrefix("CARROTSKIN") viper.AutomaticEnv() // 手动设置环境变量映射 setupEnvMappings() // 直接从环境变量解析配置 var config Config if err := viper.Unmarshal(&config); err != nil { return nil, fmt.Errorf("解析配置失败: %w", err) } // 从环境变量中覆盖配置 overrideFromEnv(&config) return &config, nil } // setDefaults 设置默认配置值 func setDefaults() { // 服务器默认配置 viper.SetDefault("server.port", ":8080") viper.SetDefault("server.mode", "debug") viper.SetDefault("server.read_timeout", "30s") viper.SetDefault("server.write_timeout", "30s") // 数据库默认配置 viper.SetDefault("database.driver", "postgres") viper.SetDefault("database.host", "localhost") viper.SetDefault("database.port", 5432) viper.SetDefault("database.ssl_mode", "disable") viper.SetDefault("database.timezone", "Asia/Shanghai") viper.SetDefault("database.max_idle_conns", 10) viper.SetDefault("database.max_open_conns", 100) viper.SetDefault("database.conn_max_lifetime", "1h") viper.SetDefault("database.conn_max_idle_time", "10m") // Redis默认配置 viper.SetDefault("redis.host", "localhost") viper.SetDefault("redis.port", 6379) viper.SetDefault("redis.database", 0) viper.SetDefault("redis.pool_size", 10) viper.SetDefault("redis.min_idle_conns", 5) viper.SetDefault("redis.max_retries", 3) viper.SetDefault("redis.dial_timeout", "5s") viper.SetDefault("redis.read_timeout", "3s") viper.SetDefault("redis.write_timeout", "3s") viper.SetDefault("redis.pool_timeout", "4s") viper.SetDefault("redis.conn_max_idle_time", "30m") // RustFS默认配置 viper.SetDefault("rustfs.endpoint", "127.0.0.1:9000") viper.SetDefault("rustfs.public_url", "") // 为空时使用 endpoint 构建 URL viper.SetDefault("rustfs.use_ssl", false) // JWT默认配置 viper.SetDefault("jwt.expire_hours", 168) // Casbin默认配置 viper.SetDefault("casbin.model_path", "configs/casbin/rbac_model.conf") viper.SetDefault("casbin.policy_adapter", "gorm") // 日志默认配置 viper.SetDefault("log.level", "info") viper.SetDefault("log.format", "json") viper.SetDefault("log.output", "logs/app.log") viper.SetDefault("log.max_size", 100) viper.SetDefault("log.max_backups", 3) viper.SetDefault("log.max_age", 28) viper.SetDefault("log.compress", true) // 邮件默认配置 viper.SetDefault("email.enabled", false) viper.SetDefault("email.smtp_port", 587) // 安全默认配置 viper.SetDefault("security.allowed_origins", []string{"*"}) viper.SetDefault("security.allowed_domains", []string{"localhost", "127.0.0.1"}) // 站点默认配置 viper.SetDefault("site.name", "CarrotSkin") viper.SetDefault("site.description", "一个优秀的Minecraft皮肤站") viper.SetDefault("site.registration_enabled", true) viper.SetDefault("site.default_avatar", "") viper.SetDefault("site.max_textures_per_user", 50) viper.SetDefault("site.max_profiles_per_user", 5) viper.SetDefault("site.checkin_reward", 10) viper.SetDefault("site.texture_download_reward", 1) } // setupEnvMappings 设置环境变量映射 func setupEnvMappings() { // 服务器配置 viper.BindEnv("server.port", "SERVER_PORT") viper.BindEnv("server.mode", "SERVER_MODE") viper.BindEnv("server.read_timeout", "SERVER_READ_TIMEOUT") viper.BindEnv("server.write_timeout", "SERVER_WRITE_TIMEOUT") // 数据库配置 viper.BindEnv("database.driver", "DATABASE_DRIVER") viper.BindEnv("database.host", "DATABASE_HOST") viper.BindEnv("database.port", "DATABASE_PORT") viper.BindEnv("database.username", "DATABASE_USERNAME") viper.BindEnv("database.password", "DATABASE_PASSWORD") viper.BindEnv("database.database", "DATABASE_NAME") viper.BindEnv("database.ssl_mode", "DATABASE_SSL_MODE") viper.BindEnv("database.timezone", "DATABASE_TIMEZONE") viper.BindEnv("database.max_idle_conns", "DATABASE_MAX_IDLE_CONNS") viper.BindEnv("database.max_open_conns", "DATABASE_MAX_OPEN_CONNS") viper.BindEnv("database.conn_max_lifetime", "DATABASE_CONN_MAX_LIFETIME") viper.BindEnv("database.conn_max_idle_time", "DATABASE_CONN_MAX_IDLE_TIME") // Redis配置 viper.BindEnv("redis.host", "REDIS_HOST") viper.BindEnv("redis.port", "REDIS_PORT") viper.BindEnv("redis.password", "REDIS_PASSWORD") viper.BindEnv("redis.database", "REDIS_DATABASE") viper.BindEnv("redis.pool_size", "REDIS_POOL_SIZE") viper.BindEnv("redis.min_idle_conns", "REDIS_MIN_IDLE_CONNS") viper.BindEnv("redis.max_retries", "REDIS_MAX_RETRIES") viper.BindEnv("redis.dial_timeout", "REDIS_DIAL_TIMEOUT") viper.BindEnv("redis.read_timeout", "REDIS_READ_TIMEOUT") viper.BindEnv("redis.write_timeout", "REDIS_WRITE_TIMEOUT") viper.BindEnv("redis.pool_timeout", "REDIS_POOL_TIMEOUT") viper.BindEnv("redis.conn_max_idle_time", "REDIS_CONN_MAX_IDLE_TIME") // RustFS配置 viper.BindEnv("rustfs.endpoint", "RUSTFS_ENDPOINT") viper.BindEnv("rustfs.public_url", "RUSTFS_PUBLIC_URL") viper.BindEnv("rustfs.access_key", "RUSTFS_ACCESS_KEY") viper.BindEnv("rustfs.secret_key", "RUSTFS_SECRET_KEY") viper.BindEnv("rustfs.use_ssl", "RUSTFS_USE_SSL") // JWT配置 viper.BindEnv("jwt.secret", "JWT_SECRET") viper.BindEnv("jwt.expire_hours", "JWT_EXPIRE_HOURS") // 日志配置 viper.BindEnv("log.level", "LOG_LEVEL") viper.BindEnv("log.format", "LOG_FORMAT") viper.BindEnv("log.output", "LOG_OUTPUT") // 邮件配置 viper.BindEnv("email.enabled", "EMAIL_ENABLED") viper.BindEnv("email.smtp_host", "EMAIL_SMTP_HOST") viper.BindEnv("email.smtp_port", "EMAIL_SMTP_PORT") viper.BindEnv("email.username", "EMAIL_USERNAME") viper.BindEnv("email.password", "EMAIL_PASSWORD") viper.BindEnv("email.from_name", "EMAIL_FROM_NAME") // 站点配置 viper.BindEnv("site.name", "SITE_NAME") viper.BindEnv("site.description", "SITE_DESCRIPTION") viper.BindEnv("site.registration_enabled", "REGISTRATION_ENABLED") viper.BindEnv("site.default_avatar", "DEFAULT_AVATAR") viper.BindEnv("site.max_textures_per_user", "MAX_TEXTURES_PER_USER") viper.BindEnv("site.max_profiles_per_user", "MAX_PROFILES_PER_USER") viper.BindEnv("site.checkin_reward", "CHECKIN_REWARD") viper.BindEnv("site.texture_download_reward", "TEXTURE_DOWNLOAD_REWARD") } // overrideFromEnv 从环境变量中覆盖配置 func overrideFromEnv(config *Config) { // 处理RustFS存储桶配置 if texturesBucket := os.Getenv("RUSTFS_BUCKET_TEXTURES"); texturesBucket != "" { if config.RustFS.Buckets == nil { config.RustFS.Buckets = make(map[string]string) } config.RustFS.Buckets["textures"] = texturesBucket } if avatarsBucket := os.Getenv("RUSTFS_BUCKET_AVATARS"); avatarsBucket != "" { if config.RustFS.Buckets == nil { config.RustFS.Buckets = make(map[string]string) } config.RustFS.Buckets["avatars"] = avatarsBucket } // 处理数据库连接池配置 if maxIdleConns := os.Getenv("DATABASE_MAX_IDLE_CONNS"); maxIdleConns != "" { if val, err := strconv.Atoi(maxIdleConns); err == nil { config.Database.MaxIdleConns = val } } if maxOpenConns := os.Getenv("DATABASE_MAX_OPEN_CONNS"); maxOpenConns != "" { if val, err := strconv.Atoi(maxOpenConns); err == nil { config.Database.MaxOpenConns = val } } if connMaxLifetime := os.Getenv("DATABASE_CONN_MAX_LIFETIME"); connMaxLifetime != "" { if val, err := time.ParseDuration(connMaxLifetime); err == nil { config.Database.ConnMaxLifetime = val } } if connMaxIdleTime := os.Getenv("DATABASE_CONN_MAX_IDLE_TIME"); connMaxIdleTime != "" { if val, err := time.ParseDuration(connMaxIdleTime); err == nil { config.Database.ConnMaxIdleTime = val } } // 处理Redis连接池配置 if poolSize := os.Getenv("REDIS_POOL_SIZE"); poolSize != "" { if val, err := strconv.Atoi(poolSize); err == nil { config.Redis.PoolSize = val } } if minIdleConns := os.Getenv("REDIS_MIN_IDLE_CONNS"); minIdleConns != "" { if val, err := strconv.Atoi(minIdleConns); err == nil { config.Redis.MinIdleConns = val } } if maxRetries := os.Getenv("REDIS_MAX_RETRIES"); maxRetries != "" { if val, err := strconv.Atoi(maxRetries); err == nil { config.Redis.MaxRetries = val } } if dialTimeout := os.Getenv("REDIS_DIAL_TIMEOUT"); dialTimeout != "" { if val, err := time.ParseDuration(dialTimeout); err == nil { config.Redis.DialTimeout = val } } if readTimeout := os.Getenv("REDIS_READ_TIMEOUT"); readTimeout != "" { if val, err := time.ParseDuration(readTimeout); err == nil { config.Redis.ReadTimeout = val } } if writeTimeout := os.Getenv("REDIS_WRITE_TIMEOUT"); writeTimeout != "" { if val, err := time.ParseDuration(writeTimeout); err == nil { config.Redis.WriteTimeout = val } } if poolTimeout := os.Getenv("REDIS_POOL_TIMEOUT"); poolTimeout != "" { if val, err := time.ParseDuration(poolTimeout); err == nil { config.Redis.PoolTimeout = val } } if connMaxIdleTime := os.Getenv("REDIS_CONN_MAX_IDLE_TIME"); connMaxIdleTime != "" { if val, err := time.ParseDuration(connMaxIdleTime); err == nil { config.Redis.ConnMaxIdleTime = val } } // 处理邮件配置 if emailEnabled := os.Getenv("EMAIL_ENABLED"); emailEnabled != "" { config.Email.Enabled = emailEnabled == "true" || emailEnabled == "True" || emailEnabled == "TRUE" || emailEnabled == "1" } // 处理环境配置 if env := os.Getenv("ENVIRONMENT"); env != "" { config.Environment = env } // 处理安全配置 if allowedOrigins := os.Getenv("SECURITY_ALLOWED_ORIGINS"); allowedOrigins != "" { config.Security.AllowedOrigins = strings.Split(allowedOrigins, ",") } if allowedDomains := os.Getenv("SECURITY_ALLOWED_DOMAINS"); allowedDomains != "" { config.Security.AllowedDomains = strings.Split(allowedDomains, ",") } } // IsTestEnvironment 判断是否为测试环境 func (c *Config) IsTestEnvironment() bool { return c.Environment == "test" }