first commit

This commit is contained in:
ytc1012
2025-11-18 18:08:48 +08:00
commit de90ad79ea
162 changed files with 28098 additions and 0 deletions

233
config/config.go Normal file
View File

@@ -0,0 +1,233 @@
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 ""
}

22
config/reload_unix.go Normal file
View File

@@ -0,0 +1,22 @@
// +build !windows,!plan9
package config
import (
"os"
"os/signal"
"syscall"
)
func signalReload() {
ch := make(chan os.Signal, 1)
signal.Notify(ch, syscall.SIGUSR1)
for {
sig := <-ch
switch sig {
case syscall.SIGUSR1:
ReloadConfigFile()
}
}
}

4
config/reload_windows.go Normal file
View File

@@ -0,0 +1,4 @@
package config
func signalReload() {
}