package config import ( "bytes" "fmt" "log" "os" "path/filepath" "github.com/joho/godotenv" "gorm.io/driver/mysql" "gorm.io/gorm" "gorm.io/gorm/logger" ) var DB *gorm.DB // JWTSecret is the secret key used for signing JWT tokens var JWTSecret = "your-secret-key" // Default value, should be set via JWT_SECRET environment variable in production // utf8BOM 用于剥离 Windows 编辑器常写入的 UTF-8 BOM,否则 godotenv 会解析失败 var utf8BOM = []byte{0xEF, 0xBB, 0xBF} // tryLoadEnv 尝试加载指定路径的 .env;成功则记录日志并返回 true。 // 会先去掉 UTF-8 BOM,再按 godotenv 默认语义:不覆盖已存在的 OS 环境变量。 func tryLoadEnv(path string) bool { if path == "" { return false } raw, err := os.ReadFile(path) if err != nil { return false } raw = bytes.TrimPrefix(raw, utf8BOM) envMap, err := godotenv.Parse(bytes.NewReader(raw)) if err != nil { log.Printf("Warning: found .env at %s but failed to parse: %v", path, err) return false } applied := 0 for k, v := range envMap { if os.Getenv(k) == "" { _ = os.Setenv(k, v) applied++ } } log.Printf("Loaded .env file from %s (%d keys applied)", path, applied) return true } // collectEnvCandidates 收集候选 .env 路径:可执行文件旁、cwd、cwd/server、向上 3 级 func collectEnvCandidates() []string { seen := map[string]struct{}{} var list []string add := func(p string) { abs, err := filepath.Abs(p) if err != nil { abs = p } if _, ok := seen[abs]; ok { return } seen[abs] = struct{}{} list = append(list, abs) } if execPath, err := os.Executable(); err == nil { add(filepath.Join(filepath.Dir(execPath), ".env")) } else { log.Printf("Warning: Failed to get executable path: %v", err) } cwd, err := os.Getwd() if err != nil { log.Printf("Warning: Failed to get working directory: %v", err) cwd = "." } add(filepath.Join(cwd, ".env")) add(filepath.Join(cwd, "server", ".env")) // 从仓库根启动时 // 向上最多 3 级父目录查找 .env 与 server/.env dir := cwd for i := 0; i < 3; i++ { parent := filepath.Dir(dir) if parent == dir { break } dir = parent add(filepath.Join(dir, ".env")) add(filepath.Join(dir, "server", ".env")) } return list } // LoadEnvFile 依次尝试多个路径加载 .env(后加载补缺,不覆盖已有 OS 环境变量) func LoadEnvFile() { loadedPaths := []string{} for _, p := range collectEnvCandidates() { if tryLoadEnv(p) { loadedPaths = append(loadedPaths, p) } } if len(loadedPaths) == 0 { log.Println("Info: no .env loaded; relying on process environment variables") } else { log.Printf("Env load summary: %d file(s) loaded", len(loadedPaths)) } appID := os.Getenv("WECHAT_APP_ID") secret := os.Getenv("WECHAT_APP_SECRET") log.Printf( "WeChat env: APP_ID_set=%v SECRET_set=%v", appID != "", secret != "", ) } func InitDB() { var err error // Try to get DSN from environment variable, otherwise use default dsn := os.Getenv("DB_DSN") if dsn == "" { dsn = "root:root@tcp(127.0.0.1:3306)/nl_blog?charset=utf8mb4&parseTime=True&loc=Local" //dsn = "root:mysql_PKC65h@tcp(127.0.0.1:3306)/nl_blog?charset=utf8mb4&parseTime=True&loc=Local" } // Try to get JWT secret from environment variable if secret := os.Getenv("JWT_SECRET"); secret != "" { JWTSecret = secret } DB, err = gorm.Open(mysql.Open(dsn), &gorm.Config{ Logger: logger.Default.LogMode(logger.Silent), // 禁用 GORM 日志,使用自定义日志 // 不使用 NowFunc,因为我们使用模型钩子手动处理 int64 时间戳 }) if err != nil { log.Fatal("Failed to connect to database:", err) } sqlDB, err := DB.DB() if err != nil { log.Fatal("Failed to get underlying sql.DB:", err) } if err = sqlDB.Ping(); err != nil { log.Fatal("Failed to ping database:", err) } fmt.Println("Database connected successfully") } func CloseDB() { if DB != nil { sqlDB, err := DB.DB() if err == nil { sqlDB.Close() } } }