package config import ( "fmt" "os" "path/filepath" "github.com/spf13/viper" "go.yaml.in/yaml/v3" ) type Config struct { Database DatabaseConfig `yaml:"database"` Log LogConfig `yaml:"log"` } type DatabaseConfig struct { DSN string `yaml:"dsn"` // 数据库连接字符串 Path string `yaml:"path"` // SQLite: 目录路径 Name string `yaml:"name"` // SQLite: 文件名 } type LogConfig struct { Level string `yaml:"level"` Format string `yaml:"format"` Output string `yaml:"output"` Verbose bool `yaml:"verbose"` LogFile string `yaml:"log_file"` ShowColor bool `yaml:"show_color"` } // --------------------------------- 全局单例配置(核心) --------------------------------- var ( // GlobalConfig 全局配置单例实例 GlobalConfig *Config // 命令行参数配置(全局、由root命令绑定) CLIParams = struct { Verbose bool // -v/--verbose NoColor bool // --no-color Config string // -c/--config }{} BaseDir string = "/etc/sunhpc" LogDir string = "/var/log/sunhpc" TmplDir string = BaseDir + "/tmpl.d" appName string = "sunhpc" defaultDBPath string = "/var/lib/sunhpc" defaultDBName string = "sunhpc.db" ) // ----------------------------------- 配置加载(只加载一次) ----------------------------------- func LoadConfig() (*Config, error) { // 如果已经加载过,直接返回 if GlobalConfig != nil { return GlobalConfig, nil } viper.SetConfigName("sunhpc") viper.SetConfigType("yaml") viper.AddConfigPath(BaseDir) viper.AddConfigPath(".") viper.AddConfigPath(filepath.Join(os.Getenv("HOME"), ".")) // Step 1: 设置默认值(最低优先级) viper.SetDefault("log.level", "info") viper.SetDefault("log.format", "text") viper.SetDefault("log.output", "stdout") viper.SetDefault("log.verbose", false) viper.SetDefault("log.log_file", filepath.Join(LogDir, "sunhpc.log")) viper.SetDefault("database.name", "sunhpc.db") viper.SetDefault("database.path", "/var/lib/sunhpc") if err := viper.ReadInConfig(); err != nil { // 配置文件不存在时,使用默认值 if _, ok := err.(viper.ConfigFileNotFoundError); !ok { return nil, err } } // 合并命令行参数(最高优先级) if CLIParams.Verbose { viper.Set("log.verbose", true) viper.Set("log.level", "debug") } // 合并noColor参数 if CLIParams.NoColor { viper.Set("log.show_color", false) } fullPath := filepath.Join( viper.GetString("database.path"), viper.GetString("database.name")) dsn := fmt.Sprintf( "%s?_foreign_keys=on&_journal_mode=WAL&_timeout=5000", fullPath) viper.Set("database.dsn", dsn) // 解码到结构体 var cfg Config if err := viper.Unmarshal(&cfg); err != nil { return nil, err } GlobalConfig = &cfg return GlobalConfig, nil } // InitDirs 创建所有必需目录 func InitDirs() error { dirs := []string{ BaseDir, TmplDir, LogDir, } for _, d := range dirs { if err := os.MkdirAll(d, 0755); err != nil { return fmt.Errorf("创建目录 %s 失败: %w", d, err) } } return nil } func (c *Config) WriteDefaultConfig(path string) error { // 确保目录存在 dir := filepath.Dir(path) if err := os.MkdirAll(dir, 0755); err != nil { return fmt.Errorf("创建目录失败: %w", err) } // 生成默认配置 cfg := DefaultConfig(path) // 序列化为 YAML data, err := yaml.Marshal(cfg) if err != nil { return fmt.Errorf("序列化配置失败: %w", err) } // 写入文件(0644 权限) return os.WriteFile(path, data, 0644) } func DefaultConfig(path string) *Config { return &Config{ Database: DatabaseConfig{ DSN: fmt.Sprintf("%s?_foreign_keys=on&_journal_mode=WAL&_timeout=5000", filepath.Join(filepath.Dir(path), defaultDBName)), Path: filepath.Dir(path), Name: defaultDBName, }, Log: LogConfig{ Level: "info", Format: "text", Output: "stdout", LogFile: filepath.Join(filepath.Dir(path), "sunhpc.log"), Verbose: false, }, } } // ResetConfig 重置全局配置为默认值 func ResetConfig() { GlobalConfig = nil viper.Reset() CLIParams = struct { Verbose bool // -v/--verbose NoColor bool // --no-color Config string // -c/--config }{} }