package config import ( "fmt" "os" "path/filepath" "sunhpc/pkg/logger" "sunhpc/pkg/utils" "sync" "github.com/spf13/viper" "go.yaml.in/yaml/v3" ) // ============================================================== // 全局变量 // ============================================================== var ( GlobalConfig *Config configOnce sync.Once // 确保配置只加载一次 configMutex sync.RWMutex // 读写锁保护 GlobalConfig ) // ============================================================== // 目录常量 (可在 main.go 中通过 flag 覆盖默认值) // ============================================================== var ( BaseDir string = utils.DefaultBaseDir TmplDir string = utils.DefaultTmplDir LogDir string = utils.DefaultLogDir ) // ============================================================== // 配置结构体 // ============================================================== type Config struct { Log logger.LogConfig `mapstructure:"log" yaml:"log"` Database DatabaseConfig `mapstructure:"database" yaml:"database"` } type DatabaseConfig struct { DSN string `mapstructure:"dsn" yaml:"dsn"` // 数据库连接字符串 Path string `mapstructure:"path" yaml:"path"` // SQLite: 目录路径 Name string `mapstructure:"name" yaml:"name"` // SQLite: 文件名 Args string `mapstructure:"args" yaml:"args"` // 数据库连接参数 } type CLIParamsType struct { Verbose bool // -v/--verbose NoColor bool // --no-color Config string // -c/--config } var CLIParams CLIParamsType // 命令行参数 // InitConfigs 初始化所有配置目录等数据 func InitConfigs() 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) } } // 检查配置文件是否存在,不存在则创建默认配置 configPath := filepath.Join(BaseDir, "sunhpc.yaml") if _, err := os.Stat(configPath); os.IsNotExist(err) { if err := createDefaultConfig(configPath); err != nil { return fmt.Errorf("创建默认配置文件失败: %w", err) } } return nil } func createDefaultConfig(configPath string) error { defaultConfig := &Config{ Database: DatabaseConfig{ Path: utils.DefaultDBPath, Name: utils.DefaultDBName, Args: utils.DefaultDBArgs, }, Log: logger.LogConfig{ Level: "info", Format: "text", Output: "stdout", Verbose: false, LogFile: utils.DefaultLogFile, ShowColor: true, }, } // 确保数据库目录存在 if err := os.MkdirAll(defaultConfig.Database.Path, 0755); err != nil { return fmt.Errorf("创建数据库目录失败: %w", err) } // 序列号并写入 data, err := yaml.Marshal(defaultConfig) if err != nil { return fmt.Errorf("序列化配置失败: %w", err) } return os.WriteFile(configPath, data, 0644) } // ----------------------------------- 配置加载(只加载一次) ----------------------------------- func LoadConfig() (*Config, error) { configMutex.RLock() if GlobalConfig != nil { // 如果已经加载过,直接返回 configMutex.RUnlock() return GlobalConfig, nil } configMutex.RUnlock() configMutex.Lock() defer configMutex.Unlock() // 双重检查 if GlobalConfig != nil { // 如果已经加载过,直接返回 return GlobalConfig, nil } // 配置文件路径 if CLIParams.Config != "" { viper.SetConfigFile(CLIParams.Config) } else { 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", utils.DefaultLogFile) viper.SetDefault("database.name", utils.DefaultDBName) viper.SetDefault("database.path", utils.DefaultDBPath) 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) } // 计算派生配置 (如数据库 DSN) dbPath := viper.GetString("database.path") dbName := viper.GetString("database.name") dbArgs := viper.GetString("database.args") fullPath := filepath.Join(dbPath, dbName) dsn := fmt.Sprintf("%s?%s", fullPath, dbArgs) viper.Set("database.dsn", dsn) // 解码到结构体 var cfg Config if err := viper.Unmarshal(&cfg); err != nil { return nil, err } GlobalConfig = &cfg return GlobalConfig, nil } // ============================================================== // SaveConfig - 保存全局配置到文件、运行时配置 // ============================================================== func SaveConfig() error { configMutex.RLock() defer configMutex.RUnlock() if GlobalConfig == nil { return fmt.Errorf("全局配置为空") } configPath := filepath.Join(BaseDir, "config.yaml") data, err := yaml.Marshal(GlobalConfig) if err != nil { return err } return os.WriteFile(configPath, data, 0644) } // ResetConfig 重置全局配置为默认值 func ResetConfig() { GlobalConfig = nil viper.Reset() CLIParams = struct { Verbose bool // -v/--verbose NoColor bool // --no-color Config string // -c/--config }{} }