package config import ( "log" "os" "strconv" "strings" "time" "github.com/joho/godotenv" ) type Config struct { Server ServerConfig Database DatabaseConfig JWT JWTConfig ShortVideo ShortVideoConfig AI AIConfig Admin AdminConfig OSS OSSConfig WeChatOA WeChatOfficialConfig Redis RedisConfig } type ServerConfig struct { Port string Mode string } type DatabaseConfig struct { Default DatabaseInstanceConfig Additional map[string]DatabaseInstanceConfig } type DatabaseInstanceConfig struct { Host string Port string User string Password string DBName string } type JWTConfig struct { Secret string Expire int } type ShortVideoConfig struct { APIKey string FreeDailyQuota int RequestTimeout time.Duration // 代理相关配置 ProxyEnabled bool // 是否启用媒体代理 ProxyAllowedDomains []string // 允许代理的域名白名单(为空表示允许所有) ProxyMaxSize int64 // 代理文件最大大小(字节),0 表示不限制 ProxyTimeout time.Duration // 代理请求超时时间 } // AIConfig 用于“AI 建议/问答”等能力的通用配置(OpenAI-compatible)。 type AIConfig struct { BaseURL string APIKey string Model string RequestTimeout time.Duration } // AdminConfig 用于简单的后台/运维接口鉴权(如生成兑换码)。 type AdminConfig struct { Token string DefaultUsername string DefaultPassword string } // OSSConfig 用于阿里云 OSS 直传相关配置。 // 前端通常会向后端请求上传凭证,然后直传文件到 OSS。 type OSSConfig struct { AccessKey string SecretKey string Bucket string UploadURL string CDNDomain string KeyPrefix string TokenExpireSeconds int CallbackURL string CallbackBody string CallbackBodyType string } // WeChatOfficialConfig 用于微信公众号网页授权(OAuth2)相关接口。 type WeChatOfficialConfig struct { AppID string AppSecret string RequestTimeout time.Duration } // RedisConfig 用于可选的 Redis 缓存(例如缓存 session_key -> user,减少 DB 查询)。 type RedisConfig struct { Addr string Password string DB int KeyPrefix string SessionTTLSeconds int } var AppConfig *Config func LoadConfig() { // 加载 .env 文件 if err := godotenv.Load(); err != nil { log.Println("未找到 .env 文件,使用环境变量") } defaultDB := DatabaseInstanceConfig{ Host: getEnv("DB_HOST", "localhost"), Port: getEnv("DB_PORT", "3306"), User: getEnv("DB_USER", "root"), Password: getEnv("DB_PASSWORD", ""), DBName: getEnv("DB_NAME", "wx_service"), } AppConfig = &Config{ Server: ServerConfig{ Port: getEnv("SERVER_PORT", "8080"), Mode: getEnv("GIN_MODE", "debug"), }, Database: DatabaseConfig{ Default: defaultDB, Additional: loadAdditionalDBConfigs(defaultDB), }, JWT: JWTConfig{ Secret: getEnv("JWT_SECRET", "your-secret-key"), Expire: 86400, // 24小时 }, ShortVideo: ShortVideoConfig{ APIKey: getEnv("SHORT_VIDEO_API_KEY", ""), FreeDailyQuota: getEnvAsInt("SHORT_VIDEO_FREE_QUOTA", 20), RequestTimeout: time.Duration(getEnvAsInt("SHORT_VIDEO_TIMEOUT_SECONDS", 5)) * time.Second, ProxyEnabled: getEnvAsBool("SHORT_VIDEO_PROXY_ENABLED", true), ProxyAllowedDomains: getEnvAsStringSlice("SHORT_VIDEO_PROXY_ALLOWED_DOMAINS", nil), ProxyMaxSize: int64(getEnvAsInt("SHORT_VIDEO_PROXY_MAX_SIZE_MB", 100)) * 1024 * 1024, ProxyTimeout: time.Duration(getEnvAsInt("SHORT_VIDEO_PROXY_TIMEOUT_SECONDS", 60)) * time.Second, }, AI: AIConfig{ BaseURL: getEnv("AI_BASE_URL", "https://api.openai.com/v1"), APIKey: getEnv("AI_API_KEY", ""), Model: getEnv("AI_MODEL", "gpt-4o-mini"), RequestTimeout: time.Duration(getEnvAsInt("AI_TIMEOUT_SECONDS", 15)) * time.Second, }, Admin: AdminConfig{ Token: getEnv("ADMIN_API_TOKEN", ""), DefaultUsername: getEnv("ADMIN_DEFAULT_USERNAME", "admin"), DefaultPassword: getEnv("ADMIN_DEFAULT_PASSWORD", "admin123"), }, OSS: OSSConfig{ AccessKey: getEnv("OSS_ACCESS_KEY", ""), SecretKey: getEnv("OSS_SECRET_KEY", ""), Bucket: getEnv("OSS_BUCKET", ""), UploadURL: getEnv("OSS_UPLOAD_URL", ""), CDNDomain: getEnv("OSS_CDN_DOMAIN", ""), KeyPrefix: getEnv("OSS_KEY_PREFIX", "uploads/"), TokenExpireSeconds: getEnvAsInt("OSS_TOKEN_EXPIRE_SECONDS", 300), CallbackURL: getEnv("OSS_CALLBACK_URL", ""), CallbackBody: getEnv("OSS_CALLBACK_BODY", "key=$(key)&hash=$(etag)&fsize=$(fsize)&mimeType=$(mimeType)"), CallbackBodyType: getEnv("OSS_CALLBACK_BODY_TYPE", "application/x-www-form-urlencoded"), }, WeChatOA: WeChatOfficialConfig{ AppID: getEnv("WECHAT_OA_APP_ID", ""), AppSecret: getEnv("WECHAT_OA_APP_SECRET", ""), RequestTimeout: time.Duration(getEnvAsInt("WECHAT_OA_TIMEOUT_SECONDS", 5)) * time.Second, }, Redis: RedisConfig{ Addr: getEnv("REDIS_ADDR", ""), Password: getEnv("REDIS_PASSWORD", ""), DB: getEnvAsInt("REDIS_DB", 0), KeyPrefix: getEnv("REDIS_KEY_PREFIX", "wx_service:"), SessionTTLSeconds: getEnvAsInt("REDIS_SESSION_TTL_SECONDS", 86400), }, } } func loadAdditionalDBConfigs(defaultCfg DatabaseInstanceConfig) map[string]DatabaseInstanceConfig { instances := strings.Split(getEnv("DB_INSTANCES", ""), ",") result := make(map[string]DatabaseInstanceConfig) for _, rawName := range instances { name := strings.TrimSpace(rawName) if name == "" { continue } upperName := strings.ToUpper(name) prefix := "DB_" + upperName + "_" result[strings.ToLower(name)] = DatabaseInstanceConfig{ Host: getEnv(prefix+"HOST", defaultCfg.Host), Port: getEnv(prefix+"PORT", defaultCfg.Port), User: getEnv(prefix+"USER", defaultCfg.User), Password: getEnv(prefix+"PASSWORD", defaultCfg.Password), DBName: getEnv(prefix+"NAME", defaultCfg.DBName), } } if len(result) == 0 { return nil } return result } func getEnv(key, defaultValue string) string { if value := os.Getenv(key); value != "" { return value } return defaultValue } func getEnvAsInt(key string, defaultValue int) int { if value := os.Getenv(key); value != "" { if v, err := strconv.Atoi(value); err == nil { return v } } return defaultValue } func getEnvAsBool(key string, defaultValue bool) bool { if value := os.Getenv(key); value != "" { v := strings.ToLower(value) return v == "true" || v == "1" || v == "yes" } return defaultValue } func getEnvAsStringSlice(key string, defaultValue []string) []string { if value := os.Getenv(key); value != "" { parts := strings.Split(value, ",") result := make([]string, 0, len(parts)) for _, part := range parts { trimmed := strings.TrimSpace(part) if trimmed != "" { result = append(result, trimmed) } } if len(result) > 0 { return result } } return defaultValue }