package config import ( "fmt" "os" "strconv" "strings" "github.com/joho/godotenv" ) type AppConfig struct { Env string Port string DB DBConfig Mail MailConfig AI AIConfig AuthCenter AuthCenterConfig // SiteAdminToken 与前端管理员口令一致,用于更新站点展示配置(如 60s 功能开关);为空则禁止写入 SiteAdminToken string } type DBConfig struct { Host string Port string Name string User string Password string } func (d DBConfig) DSN() string { return fmt.Sprintf("%s:%s@tcp(%s:%s)/%s?charset=utf8mb4&parseTime=True&loc=Local&timeout=10s", d.User, d.Password, d.Host, d.Port, d.Name) } type MailConfig struct { Host string Port int Username string Password string } type AuthCenterConfig struct { APIURL string AdminToken string } type AIProviderConfig struct { APIKey string `json:"api_key"` APIBase string `json:"api_base"` Models []string `json:"model"` } type AIConfig struct { Providers map[string]AIProviderConfig } var Cfg *AppConfig const ( envDevelopment = "development" envProduction = "production" defaultDevDBHost = "10.1.1.100" defaultDevDBPort = "3306" defaultDevDBName = "infogenie-test" defaultDevDBUser = "infogenie-test" defaultDevDBPassword = "infogenie-test" ) func Load() (*AppConfig, error) { env := normalizeEnv(os.Getenv("APP_ENV")) if env != envDevelopment && env != envProduction { return nil, fmt.Errorf("不支持的APP_ENV: %s", env) } if err := loadEnvFile(env); err != nil { return nil, err } mailPort, _ := strconv.Atoi(getEnv("MAIL_PORT", "465")) dbHost, err := getEnvByEnvironment(env, "DB_HOST", defaultDevDBHost) if err != nil { return nil, err } dbPort, err := getEnvByEnvironment(env, "DB_PORT", defaultDevDBPort) if err != nil { return nil, err } dbName, err := getEnvByEnvironment(env, "DB_NAME", defaultDevDBName) if err != nil { return nil, err } dbUser, err := getEnvByEnvironment(env, "DB_USER", defaultDevDBUser) if err != nil { return nil, err } dbPassword, err := getEnvByEnvironment(env, "DB_PASSWORD", defaultDevDBPassword) if err != nil { return nil, err } cfg := &AppConfig{ Env: env, Port: getEnv("APP_PORT", "5002"), DB: DBConfig{ Host: dbHost, Port: dbPort, Name: dbName, User: dbUser, Password: dbPassword, }, Mail: MailConfig{ Host: getEnv("MAIL_HOST", "smtp.qq.com"), Port: mailPort, Username: getEnv("MAIL_USERNAME", ""), Password: getEnv("MAIL_PASSWORD", ""), }, AuthCenter: AuthCenterConfig{ APIURL: getEnv("AUTH_CENTER_API_URL", "https://auth.api.shumengya.top"), AdminToken: getEnv("AUTH_CENTER_ADMIN_TOKEN", ""), }, SiteAdminToken: getEnv("INFOGENIE_SITE_ADMIN_TOKEN", ""), } if err := validateDBConfig(cfg); err != nil { return nil, err } // AI配置现在完全从数据库读取,不再加载ai_config.json文件 cfg.AI = AIConfig{Providers: make(map[string]AIProviderConfig)} Cfg = cfg return cfg, nil } func loadEnvFile(env string) error { envFile := fmt.Sprintf(".env.%s", env) if _, err := os.Stat(envFile); err == nil { return godotenv.Load(envFile) } return nil } func validateDBConfig(cfg *AppConfig) error { switch cfg.Env { case envDevelopment: if !isDevelopmentDBTarget(cfg.DB) { return fmt.Errorf("开发环境必须使用测试数据库: host=%s name=%s", cfg.DB.Host, cfg.DB.Name) } if looksProductionLike(cfg.DB) { return fmt.Errorf("开发环境检测到生产数据库配置: host=%s name=%s", cfg.DB.Host, cfg.DB.Name) } case envProduction: missing := make([]string, 0, 4) if strings.TrimSpace(cfg.DB.Host) == "" { missing = append(missing, "DB_HOST") } if strings.TrimSpace(cfg.DB.Port) == "" { missing = append(missing, "DB_PORT") } if strings.TrimSpace(cfg.DB.Name) == "" { missing = append(missing, "DB_NAME") } if strings.TrimSpace(cfg.DB.User) == "" { missing = append(missing, "DB_USER") } if strings.TrimSpace(cfg.DB.Password) == "" { missing = append(missing, "DB_PASSWORD") } if len(missing) > 0 { return fmt.Errorf("生产环境缺少必需数据库配置: %s", strings.Join(missing, ", ")) } if isDevelopmentDBTarget(cfg.DB) { return fmt.Errorf("生产环境数据库配置看起来像开发/测试环境: host=%s name=%s", cfg.DB.Host, cfg.DB.Name) } default: return fmt.Errorf("不支持的APP_ENV: %s", cfg.Env) } return nil } func isDevelopmentDBTarget(db DBConfig) bool { host := strings.ToLower(strings.TrimSpace(db.Host)) name := strings.ToLower(strings.TrimSpace(db.Name)) if host == "localhost" || strings.HasPrefix(host, "127.") || strings.HasPrefix(host, "10.1.1.") { return true } if strings.Contains(host, "dev") || strings.Contains(host, "test") || strings.Contains(host, "local") { return true } if strings.Contains(name, "test") || strings.Contains(name, "dev") || strings.Contains(name, "local") { return true } return false } func looksProductionLike(db DBConfig) bool { host := strings.ToLower(strings.TrimSpace(db.Host)) name := strings.ToLower(strings.TrimSpace(db.Name)) return strings.Contains(host, "bigmengya") || strings.Contains(host, "shumengya.top") || strings.Contains(name, "prod") || strings.Contains(name, "production") } func normalizeEnv(raw string) string { env := strings.ToLower(strings.TrimSpace(raw)) if env == "" { return envDevelopment } return env } func getEnv(key, fallback string) string { if v := os.Getenv(key); v != "" { return v } return fallback } func getEnvByEnvironment(env, key, devFallback string) (string, error) { if v := strings.TrimSpace(os.Getenv(key)); v != "" { return v, nil } if env == envProduction { return "", fmt.Errorf("生产环境缺少必需配置: %s", key) } return devFallback, nil }