Files
sunhpc-go/pkg/config/config.go

227 lines
5.9 KiB
Go

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)
}
}
// 读取配置文件,根据配置文件内容初始化相关目录.
cfg, err := LoadConfig()
if err != nil {
return fmt.Errorf("读取配置文件失败: %w", err)
}
// 初始化日志
logger.Init(cfg.Log)
// 确保数据库目录存在
if err := os.MkdirAll(cfg.Database.Path, 0755); err != nil {
logger.Debugf("创建数据库目录 %s 失败: %v", cfg.Database.Path, err)
return err
}
logger.Debugf("创建数据库目录 %s 成功", cfg.Database.Path)
return nil
}
func createDefaultConfig(configPath string) error {
fmt.Printf("设置默认配置文件: %s\n", configPath)
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,
},
}
// 序列号并写入
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
}{}
}