package config import ( "fmt" "log/slog" "os" "path/filepath" "strings" "github.com/spf13/viper" "gopkg.in/ini.v1" ) var ( V *viper.Viper ROOT string ) const mainIniPath = "bin/conf/env.ini" func init() { root, err := findConfigRoot() if err != nil { slog.Warn("can't find config root", "error", err) ROOT, _ = os.Getwd() } else { ROOT = root } configPath := filepath.Join(ROOT, mainIniPath) V = viper.New() // 使用 ini 包解析配置文件 cfg, err := ini.Load(configPath) if err != nil { slog.Error("load config file error", "error", err) // 创建一个空的配置 cfg = ini.Empty() } // 将 ini 配置加载到 viper 中 loadIniToViper(cfg, V) if err = loadIncludeFiles(); err != nil { panic("load include files error:" + err.Error()) } go signalReload() } func ReloadConfigFile() { configPath := filepath.Join(ROOT, mainIniPath) // 使用 ini 包重新加载配置文件 cfg, err := ini.Load(configPath) if err != nil { slog.Error("reload config file error", "error", err) return } // 清空现有配置并重新加载 V = viper.New() loadIniToViper(cfg, V) if err := loadIncludeFiles(); err != nil { slog.Error("reload include files error", "error", err) return } slog.Info("reload config file successfully") } func SaveConfigFile() error { configPath := filepath.Join(ROOT, mainIniPath) if err := V.WriteConfigAs(configPath); err != nil { slog.Error("save config file error", "error", err) return err } slog.Info("save config file successfully") return nil } func loadIncludeFiles() error { includeFile := GetString("include_files.path", "") if includeFile != "" { includeFiles := strings.Split(includeFile, ",") for _, file := range includeFiles { file = strings.TrimSpace(file) if file != "" { // 使用绝对路径或相对路径 if !filepath.IsAbs(file) { file = filepath.Join(ROOT, file) } // 使用 ini 包加载包含的配置文件 cfg, err := ini.Load(file) if err != nil { return fmt.Errorf("failed to read config file %s: %w", file, err) } // 合并配置到主 viper 实例 loadIniToViper(cfg, V) } } } return nil } // GetString 获取字符串配置值,支持 section.key 格式 func GetString(key string, defaultValue string) string { if V.IsSet(key) { return V.GetString(key) } return defaultValue } // GetInt 获取整数配置值,支持 section.key 格式 func GetInt(key string, defaultValue int) int { if V.IsSet(key) { return V.GetInt(key) } return defaultValue } // GetBool 获取布尔配置值,支持 section.key 格式 func GetBool(key string, defaultValue bool) bool { if V.IsSet(key) { return V.GetBool(key) } return defaultValue } // GetSection 获取整个 section 的配置,返回 map[string]string func GetSection(section string) (map[string]string, error) { // 从 viper 中获取 section,格式为 section.key sectionMap := make(map[string]string) // 遍历所有配置键,查找属于该 section 的配置 allKeys := V.AllKeys() found := false for _, key := range allKeys { if strings.HasPrefix(key, section+".") { found = true subKey := strings.TrimPrefix(key, section+".") sectionMap[subKey] = V.GetString(key) } } if !found { return nil, fmt.Errorf("section %s not found", section) } return sectionMap, nil } // GetPath 获取路径配置值,如果是相对路径则基于 ROOT 解析为绝对路径 func GetPath(key string, defaultValue string) string { pathValue := GetString(key, defaultValue) if pathValue == "" { return defaultValue } // 如果是绝对路径,直接返回 if filepath.IsAbs(pathValue) { return pathValue } // 相对路径基于 ROOT 解析 return filepath.Join(ROOT, pathValue) } // loadIniToViper 将 ini 配置加载到 viper 中 func loadIniToViper(cfg *ini.File, v *viper.Viper) { for _, section := range cfg.Sections() { sectionName := section.Name() // 跳过默认 section (DEFAULT) if sectionName == ini.DEFAULT_SECTION { sectionName = "" } for _, key := range section.Keys() { keyName := key.Name() value := key.Value() // 构建 viper 键名:section.key 或 key (如果 section 为空) var viperKey string if sectionName != "" { viperKey = sectionName + "." + keyName } else { viperKey = keyName } v.Set(viperKey, value) } } } // fileExist 检查文件或目录是否存在 // 如果由 filename 指定的文件或目录存在则返回 true,否则返回 false func fileExist(filename string) bool { _, err := os.Stat(filename) return err == nil || os.IsExist(err) } func findConfigRoot() (string, error) { if execPath, err := os.Executable(); err == nil { if root := searchConfig(filepath.Dir(execPath)); root != "" { return root, nil } } if wd, err := os.Getwd(); err == nil { if root := searchConfig(wd); root != "" { return root, nil } } return "", fmt.Errorf("unable to locate %s", mainIniPath) } func searchConfig(start string) string { dir := start for { configPath := filepath.Join(dir, mainIniPath) if fileExist(configPath) { return dir } parent := filepath.Dir(dir) if parent == dir { break } dir = parent } return "" }