package config import ( "fmt" "log/slog" "os" "strconv" "github.com/fsnotify/fsnotify" "github.com/spf13/viper" ) type Config struct { Database DatabaseConfig Server ServerConfig App AppConfig } type DatabaseConfig struct { Driver string DSN string } type ServerConfig struct { Port int Mode string } type AppConfig struct { Name string Description string Author string Theme string } var globalConfig *Config // Load 加载配置(支持环境变量和配置文件) func Load() *Config { // 1. 初始化 viper viper.SetConfigName("config") viper.SetConfigType("yaml") viper.AddConfigPath("./config") viper.AddConfigPath(".") // 2. 设置默认值 setDefaults() // 3. 自动读取环境变量 viper.AutomaticEnv() viper.SetEnvPrefix("GOBLOG") // 4. 读取配置文件(如果存在) if err := viper.ReadInConfig(); err != nil { if _, ok := err.(viper.ConfigFileNotFoundError); !ok { slog.Warn("读取配置文件失败,使用默认配置", "error", err) } } else { slog.Info("配置文件加载成功", "file", viper.ConfigFileUsed()) } // 5. 解析配置 cfg := parseConfig() globalConfig = cfg // 6. 监听配置变更 watchConfig() return cfg } // setDefaults 设置默认配置值 func setDefaults() { viper.SetDefault("server.port", 8080) viper.SetDefault("server.mode", "debug") viper.SetDefault("database.driver", "sqlite") viper.SetDefault("database.dsn", "goblog.db") viper.SetDefault("app.name", "GoBlog") viper.SetDefault("app.description", "一个简洁的个人博客") viper.SetDefault("app.author", "Admin") viper.SetDefault("app.theme", "default") } // parseConfig 解析配置到结构体 func parseConfig() *Config { port := viper.GetInt("server.port") if port == 0 { port, _ = strconv.Atoi(getEnv("SERVER_PORT", "8080")) } return &Config{ Database: DatabaseConfig{ Driver: viper.GetString("database.driver"), DSN: viper.GetString("database.dsn"), }, Server: ServerConfig{ Port: port, Mode: viper.GetString("server.mode"), }, App: AppConfig{ Name: viper.GetString("app.name"), Description: viper.GetString("app.description"), Author: viper.GetString("app.author"), Theme: viper.GetString("app.theme"), }, } } // watchConfig 监听配置文件变更 func watchConfig() { viper.OnConfigChange(func(e fsnotify.Event) { slog.Info("配置文件已变更", "file", e.Name, "operation", e.Op.String()) // 重新解析配置 globalConfig = parseConfig() slog.Info("配置已重新加载", "theme", globalConfig.App.Theme, "port", globalConfig.Server.Port, ) }) viper.WatchConfig() } // GetGlobalConfig 获取全局配置(可用于热更新场景) func GetGlobalConfig() *Config { return globalConfig } // getEnv 获取环境变量 func getEnv(key, defaultValue string) string { if value := os.Getenv(key); value != "" { return value } return defaultValue } // CreateDefaultConfig 创建默认配置文件 func CreateDefaultConfig() error { configDir := "./config" if _, err := os.Stat(configDir); os.IsNotExist(err) { if err := os.MkdirAll(configDir, 0755); err != nil { return fmt.Errorf("创建配置目录失败: %w", err) } } configPath := configDir + "/config.yaml" if _, err := os.Stat(configPath); err == nil { return nil // 配置文件已存在 } defaultConfig := `# GoBlog 配置文件 server: port: 8080 mode: debug # debug 或 release database: driver: sqlite # sqlite 或 mysql dsn: goblog.db # SQLite文件路径或MySQL DSN app: name: GoBlog description: 一个简洁的个人博客 author: Admin theme: default # 主题名称 ` if err := os.WriteFile(configPath, []byte(defaultConfig), 0644); err != nil { return fmt.Errorf("创建配置文件失败: %w", err) } slog.Info("已创建默认配置文件", "file", configPath) return nil }