Files
backend/pkg/config/config.go

415 lines
15 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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"
}