package config import ( "crypto/rand" "encoding/hex" "log" "os" "strconv" "sql-platform/internal/model" "github.com/joho/godotenv" ) // DefaultJWTSecret 仅用于本地 DEBUG;生产模式(DEBUG=false)会被 EnsureJWTSecret 替换为随机密钥 const DefaultJWTSecret = "sql-platform-secret-key-change-in-production" type Config struct { Port string DBPath string JWTSecret string JWTExpire int // hours GhOstPath string Debug bool } var C Config func Init() { _ = godotenv.Load() C = Config{ Port: getEnv("PORT", "9000"), DBPath: getEnv("DB_PATH", "../backend/sqlite3.db"), JWTSecret: getEnv("JWT_SECRET", DefaultJWTSecret), JWTExpire: getEnvInt("JWT_EXPIRE_HOURS", 24), GhOstPath: getEnv("GH_OST_PATH", "gh-ost"), Debug: getEnvBool("DEBUG", true), } } // EnsureJWTSecret 治理默认 JWT 密钥: // - 显式配置了 JWT_SECRET(非默认值)→ 直接使用 // - DEBUG 模式使用默认密钥 → 打印告警(开发便利优先) // - 生产模式使用默认密钥 → 自动生成随机密钥并持久化到 system_settings(重启复用; // 首次切换会使存量会话失效一次) // // 必须在 model.DB 初始化并完成 AutoMigrate 之后调用。 func EnsureJWTSecret() { if C.JWTSecret != DefaultJWTSecret { return } if C.Debug { log.Println("[WARN] 正在使用默认 JWT 密钥(DEBUG 模式);生产环境请设置 JWT_SECRET 环境变量") return } var s model.SystemSetting if err := model.DB.Where("`key` = ?", "jwt_secret").Take(&s).Error; err == nil && s.Value != "" { C.JWTSecret = s.Value return } buf := make([]byte, 32) if _, err := rand.Read(buf); err != nil { log.Fatalf("生成随机 JWT 密钥失败: %v", err) } secret := hex.EncodeToString(buf) if err := model.DB.Where(model.SystemSetting{Key: "jwt_secret"}). Assign(model.SystemSetting{Value: secret, ValueType: "string", Group: "security", Label: "JWT Secret"}). FirstOrCreate(&s).Error; err != nil { log.Fatalf("持久化 JWT 密钥失败: %v", err) } C.JWTSecret = secret log.Println("[INFO] 已自动生成随机 JWT 密钥并持久化(未显式配置 JWT_SECRET 的生产部署,存量会话将失效一次)") } func getEnv(key, fallback string) string { if v := os.Getenv(key); v != "" { return v } return fallback } func getEnvInt(key string, fallback int) int { if v := os.Getenv(key); v != "" { if i, err := strconv.Atoi(v); err == nil { return i } } return fallback } func getEnvBool(key string, fallback bool) bool { if v := os.Getenv(key); v != "" { if b, err := strconv.ParseBool(v); err == nil { return b } } return fallback }