Files
slgserver/config/config.go
2025-11-18 18:08:48 +08:00

234 lines
5.1 KiB
Go
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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 ""
}