2025-12-19 22:36:48 +08:00
|
|
|
|
package config
|
|
|
|
|
|
|
|
|
|
|
|
import (
|
|
|
|
|
|
"fmt"
|
|
|
|
|
|
"log"
|
|
|
|
|
|
"os"
|
|
|
|
|
|
|
|
|
|
|
|
"github.com/spf13/viper"
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
type Config struct {
|
|
|
|
|
|
Server ServerConfig `mapstructure:"server"`
|
|
|
|
|
|
Database DatabaseConfig `mapstructure:"database"`
|
2026-01-06 19:36:42 +08:00
|
|
|
|
Redis RedisConfig `mapstructure:"redis"`
|
2025-12-19 22:36:48 +08:00
|
|
|
|
JWT JWTConfig `mapstructure:"jwt"`
|
|
|
|
|
|
Wechat WechatConfig `mapstructure:"wechat"`
|
|
|
|
|
|
XHS XHSConfig `mapstructure:"xhs"`
|
|
|
|
|
|
Scheduler SchedulerConfig `mapstructure:"scheduler"`
|
2026-01-06 19:36:42 +08:00
|
|
|
|
Upload UploadConfig `mapstructure:"upload"`
|
|
|
|
|
|
AliSms AliSmsConfig `mapstructure:"ali_sms"`
|
2025-12-19 22:36:48 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
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"`
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-01-06 19:36:42 +08:00
|
|
|
|
type RedisConfig struct {
|
|
|
|
|
|
Host string `mapstructure:"host"`
|
|
|
|
|
|
Port int `mapstructure:"port"`
|
|
|
|
|
|
Password string `mapstructure:"password"`
|
|
|
|
|
|
DB int `mapstructure:"db"`
|
|
|
|
|
|
PoolSize int `mapstructure:"pool_size"`
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-12-19 22:36:48 +08:00
|
|
|
|
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"` // 动态获取代理的接口地址(可选)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-01-06 19:36:42 +08:00
|
|
|
|
// 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
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-12-19 22:36:48 +08:00
|
|
|
|
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)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-01-06 19:36:42 +08:00
|
|
|
|
// 打印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")
|
|
|
|
|
|
|
2025-12-19 22:36:48 +08:00
|
|
|
|
log.Printf("配置加载成功: %s 环境", env)
|
|
|
|
|
|
log.Printf("数据库配置: %s@%s:%d/%s", AppConfig.Database.Username, AppConfig.Database.Host, AppConfig.Database.Port, AppConfig.Database.DBName)
|
2026-01-06 19:36:42 +08:00
|
|
|
|
log.Printf("Python服务地址: %s", AppConfig.XHS.PythonServiceURL)
|
2025-12-19 22:36:48 +08:00
|
|
|
|
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")
|
|
|
|
|
|
|
2026-01-06 19:36:42 +08:00
|
|
|
|
// 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")
|
|
|
|
|
|
|
2025-12-19 22:36:48 +08:00
|
|
|
|
// 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")
|
2026-01-06 19:36:42 +08:00
|
|
|
|
|
|
|
|
|
|
// 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")
|
2025-12-19 22:36:48 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 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,
|
|
|
|
|
|
)
|
|
|
|
|
|
}
|