package config import ( "fmt" "log" "os" "github.com/spf13/viper" ) type Config struct { Server ServerConfig `mapstructure:"server"` Database DatabaseConfig `mapstructure:"database"` Redis RedisConfig `mapstructure:"redis"` JWT JWTConfig `mapstructure:"jwt"` Wechat WechatConfig `mapstructure:"wechat"` XHS XHSConfig `mapstructure:"xhs"` Scheduler SchedulerConfig `mapstructure:"scheduler"` Upload UploadConfig `mapstructure:"upload"` AliSms AliSmsConfig `mapstructure:"ali_sms"` } type ServerConfig struct { Port int `mapstructure:"port"` Mode string `mapstructure:"mode"` } type DatabaseConfig struct { Host string `mapstructure:"host"` Port int `mapstructure:"port"` Username string `mapstructure:"username"` Password string `mapstructure:"password"` DBName string `mapstructure:"dbname"` Charset string `mapstructure:"charset"` ParseTime bool `mapstructure:"parse_time"` Loc string `mapstructure:"loc"` MaxIdleConns int `mapstructure:"max_idle_conns"` MaxOpenConns int `mapstructure:"max_open_conns"` ConnMaxLifetime int `mapstructure:"conn_max_lifetime"` } type RedisConfig struct { Host string `mapstructure:"host"` Port int `mapstructure:"port"` Password string `mapstructure:"password"` DB int `mapstructure:"db"` PoolSize int `mapstructure:"pool_size"` } 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 XHSConfig struct { PythonServiceURL string `mapstructure:"python_service_url"` } type SchedulerConfig struct { Enabled bool `mapstructure:"enabled"` // 是否启用定时任务 PublishCron string `mapstructure:"publish_cron"` // 发布任务的Cron表达式 MaxConcurrent int `mapstructure:"max_concurrent"` // 最大并发发布数 PublishTimeout int `mapstructure:"publish_timeout"` // 发布超时时间(秒) MaxArticlesPerUserPerRun int `mapstructure:"max_articles_per_user_per_run"` // 每轮每个用户最大发文数 MaxFailuresPerUserPerRun int `mapstructure:"max_failures_per_user_per_run"` // 每轮每个用户最大失败次数 MaxDailyArticlesPerUser int `mapstructure:"max_daily_articles_per_user"` // 每个用户每日最大发文数 MaxHourlyArticlesPerUser int `mapstructure:"max_hourly_articles_per_user"` // 每个用户每小时最大发文数 Proxy string `mapstructure:"proxy"` // 全局代理地址(可选) UserAgent string `mapstructure:"user_agent"` // 全局User-Agent(可选) ProxyFetchURL string `mapstructure:"proxy_fetch_url"` // 动态获取代理的接口地址(可选) } // UploadConfig 文件上传配置 type UploadConfig struct { MaxImageSize int64 `mapstructure:"max_image_size"` // 图片最大大小(字节) MaxFileSize int64 `mapstructure:"max_file_size"` // 文件最大大小(字节) ImageTypes []string `mapstructure:"image_types"` // 允许的图片类型 StaticPath string `mapstructure:"static_path"` // 静态文件路径(本地存储) BaseURL string `mapstructure:"base_url"` // 静态文件访问基础URL(本地存储) StorageType string `mapstructure:"storage_type"` // 存储类型:local(本地) 或 oss(阿里云OSS) OSS OSSConfig `mapstructure:"oss"` // OSS配置 } type OSSConfig struct { Endpoint string `mapstructure:"endpoint"` // OSS访问域名 AccessKeyID string `mapstructure:"access_key_id"` // AccessKey ID AccessKeySecret string `mapstructure:"access_key_secret"` // AccessKey Secret BucketName string `mapstructure:"bucket_name"` // Bucket名称 BasePath string `mapstructure:"base_path"` // 文件存储基础路径 Domain string `mapstructure:"domain"` // 自定义域名(可选) } // AliSmsConfig 阿里云短信配置 type AliSmsConfig struct { AccessKeyID string `mapstructure:"access_key_id"` // AccessKey ID AccessKeySecret string `mapstructure:"access_key_secret"` // AccessKey Secret SignName string `mapstructure:"sign_name"` // 短信签名 TemplateCode string `mapstructure:"template_code"` // 短信模板CODE } var AppConfig *Config // LoadConfig 加载配置文件 // 优先级: 环境变量 > 配置文件 func LoadConfig(env string) error { // 1. 优先从环境变量 APP_ENV 获取环境配置 if envFromEnv := os.Getenv("APP_ENV"); envFromEnv != "" { env = envFromEnv log.Printf("从环境变量 APP_ENV 读取环境: %s", env) } if env == "" { env = "dev" } // 2. 加载配置文件 viper.SetConfigName(fmt.Sprintf("config.%s", env)) viper.SetConfigType("yaml") viper.AddConfigPath("./config") viper.AddConfigPath("../config") if err := viper.ReadInConfig(); err != nil { return fmt.Errorf("读取配置文件失败: %w", err) } // 3. 绑定环境变量(自动读取环境变量覆盖配置) viper.AutomaticEnv() // 绑定特定的环境变量到配置项 bindEnvVariables() AppConfig = &Config{} if err := viper.Unmarshal(AppConfig); err != nil { return fmt.Errorf("解析配置文件失败: %w", err) } // 打印OSS配置来源调试信息 log.Printf("\n=== OSS配置来源检查 ===") log.Printf("upload.oss.access_key_secret 配置值: [%s]", AppConfig.Upload.OSS.AccessKeySecret) log.Printf("环境变量 OSS_ACCESS_KEY_SECRET: [%s]", os.Getenv("OSS_ACCESS_KEY_SECRET")) log.Printf("环境变量 OSS_TEST_ACCESS_KEY_SECRET: [%s]", os.Getenv("OSS_TEST_ACCESS_KEY_SECRET")) log.Printf("====================\n") log.Printf("配置加载成功: %s 环境", env) log.Printf("数据库配置: %s@%s:%d/%s", AppConfig.Database.Username, AppConfig.Database.Host, AppConfig.Database.Port, AppConfig.Database.DBName) log.Printf("Python服务地址: %s", AppConfig.XHS.PythonServiceURL) return nil } // bindEnvVariables 绑定环境变量到配置项 func bindEnvVariables() { // Server 配置 viper.BindEnv("server.port", "SERVER_PORT") viper.BindEnv("server.mode", "SERVER_MODE") // Database 配置 viper.BindEnv("database.host", "DB_HOST") viper.BindEnv("database.port", "DB_PORT") viper.BindEnv("database.username", "DB_USERNAME") viper.BindEnv("database.password", "DB_PASSWORD") viper.BindEnv("database.dbname", "DB_NAME") viper.BindEnv("database.charset", "DB_CHARSET") // Redis 配置 viper.BindEnv("redis.host", "REDIS_HOST") viper.BindEnv("redis.port", "REDIS_PORT") viper.BindEnv("redis.password", "REDIS_PASSWORD") viper.BindEnv("redis.db", "REDIS_DB") viper.BindEnv("redis.pool_size", "REDIS_POOL_SIZE") // JWT 配置 viper.BindEnv("jwt.secret", "JWT_SECRET") viper.BindEnv("jwt.expire_hours", "JWT_EXPIRE_HOURS") // Wechat 配置 viper.BindEnv("wechat.app_id", "WECHAT_APP_ID") viper.BindEnv("wechat.app_secret", "WECHAT_APP_SECRET") // XHS 配置 viper.BindEnv("xhs.python_service_url", "XHS_PYTHON_SERVICE_URL") // Scheduler 配置 viper.BindEnv("scheduler.enabled", "SCHEDULER_ENABLED") viper.BindEnv("scheduler.publish_cron", "SCHEDULER_PUBLISH_CRON") viper.BindEnv("scheduler.max_concurrent", "SCHEDULER_MAX_CONCURRENT") viper.BindEnv("scheduler.publish_timeout", "SCHEDULER_PUBLISH_TIMEOUT") viper.BindEnv("scheduler.max_articles_per_user_per_run", "SCHEDULER_MAX_ARTICLES_PER_USER_PER_RUN") viper.BindEnv("scheduler.max_failures_per_user_per_run", "SCHEDULER_MAX_FAILURES_PER_USER_PER_RUN") viper.BindEnv("scheduler.max_daily_articles_per_user", "SCHEDULER_MAX_DAILY_ARTICLES_PER_USER") viper.BindEnv("scheduler.max_hourly_articles_per_user", "SCHEDULER_MAX_HOURLY_ARTICLES_PER_USER") viper.BindEnv("scheduler.proxy", "SCHEDULER_PROXY") viper.BindEnv("scheduler.user_agent", "SCHEDULER_USER_AGENT") viper.BindEnv("scheduler.proxy_fetch_url", "SCHEDULER_PROXY_FETCH_URL") // OSS 配置 - 强制从配置文件读取,不使用环境变量 // viper.BindEnv("upload.oss.endpoint", "OSS_ENDPOINT") // viper.BindEnv("upload.oss.access_key_id", "OSS_ACCESS_KEY_ID") // viper.BindEnv("upload.oss.access_key_secret", "OSS_ACCESS_KEY_SECRET") // viper.BindEnv("upload.oss.bucket_name", "OSS_BUCKET_NAME") // viper.BindEnv("upload.oss.base_path", "OSS_BASE_PATH") // viper.BindEnv("upload.oss.domain", "OSS_DOMAIN") // Upload 配置 viper.BindEnv("upload.max_image_size", "UPLOAD_MAX_IMAGE_SIZE") viper.BindEnv("upload.max_file_size", "UPLOAD_MAX_FILE_SIZE") viper.BindEnv("upload.static_path", "UPLOAD_STATIC_PATH") viper.BindEnv("upload.base_url", "UPLOAD_BASE_URL") viper.BindEnv("upload.storage_type", "UPLOAD_STORAGE_TYPE") // AliSms 配置 viper.BindEnv("ali_sms.access_key_id", "ALI_SMS_ACCESS_KEY_ID") viper.BindEnv("ali_sms.access_key_secret", "ALI_SMS_ACCESS_KEY_SECRET") viper.BindEnv("ali_sms.sign_name", "ALI_SMS_SIGN_NAME") viper.BindEnv("ali_sms.template_code", "ALI_SMS_TEMPLATE_CODE") } // GetDSN 获取数据库连接字符串 func (c *DatabaseConfig) GetDSN() string { return fmt.Sprintf("%s:%s@tcp(%s:%d)/%s?charset=%s&parseTime=%t&loc=%s", c.Username, c.Password, c.Host, c.Port, c.DBName, c.Charset, c.ParseTime, c.Loc, ) } // GetPythonServiceURL 获取Python服务地址 func GetPythonServiceURL() string { if AppConfig != nil && AppConfig.XHS.PythonServiceURL != "" { return AppConfig.XHS.PythonServiceURL } // 默认返回本地地址 return "http://localhost:8000" }