// Package configx 提供了一个灵活且健壮的配置管理解决方案 // 它支持多种配置文件格式、环境变量、热重载和类型安全的配置值访问 package configx import ( "fmt" "os" "path/filepath" "reflect" "strings" "sync" "github.com/fsnotify/fsnotify" "github.com/joho/godotenv" "github.com/mitchellh/mapstructure" "github.com/spf13/viper" ) // Config 表示配置管理器 type Config struct { v *viper.Viper data interface{} configFile string environment string autoReload bool mu sync.RWMutex } // Option 定义了配置选项的函数类型 type Option func(*Config) error // New 创建并初始化一个新的 Config 实例 func New(data interface{}, opts ...Option) (*Config, error) { c := &Config{ v: viper.New(), data: data, } for _, opt := range opts { if err := opt(c); err != nil { return nil, err } } if err := c.initialize(); err != nil { return nil, err } return c, nil } // WithConfigFile 设置配置文件路径 func WithConfigFile(file string) Option { return func(c *Config) error { c.configFile = file return nil } } // WithEnvFile 设置 .env 文件路径 func WithEnvFile(file string) Option { return func(c *Config) error { return godotenv.Load(file) } } // WithAutoReload 设置是否自动重新加载配置文件 func WithAutoReload(enabled bool) Option { return func(c *Config) error { c.autoReload = enabled return nil } } // WithEnvPrefix 设置环境变量前缀 func WithEnvPrefix(prefix string) Option { return func(c *Config) error { c.v.SetEnvPrefix(prefix) return nil } } // initialize 初始化 Config 实例 func (c *Config) initialize() error { // 从 .env 文件中获取环境变量 if c.environment == "" { c.environment = os.Getenv("APP_ENV") if c.environment == "" { c.environment = "dev" } } c.v.Set("environment", c.environment) // 如果没有设置配置文件,根据环境选择默认配置文件 if c.configFile == "" { c.configFile = fmt.Sprintf("config.%s.yaml", c.environment) } c.v.SetConfigType(filepath.Ext(c.configFile)[1:]) c.v.SetConfigFile(c.configFile) // 首先读取配置文件 if err := c.v.ReadInConfig(); err != nil { return fmt.Errorf("failed to read config file: %w", err) } // 然后设置从环境变量读取配置 c.v.AutomaticEnv() // 如果没有设置环境变量前缀,使用默认前缀 if c.v.GetEnvPrefix() == "" { c.v.SetEnvPrefix("APP") } c.v.SetEnvKeyReplacer(strings.NewReplacer(".", "_")) if err := c.unmarshal(); err != nil { return fmt.Errorf("failed to unmarshal config: %w", err) } // 监听配置文件变化 if c.autoReload { c.v.WatchConfig() c.v.OnConfigChange(func(in fsnotify.Event) { c.Reload() }) } return nil } // unmarshal 将配置解析到数据结构中 func (c *Config) unmarshal() error { return c.v.Unmarshal(c.data, func(dc *mapstructure.DecoderConfig) { dc.TagName = "config" }) } // Get 返回整个配置结构 func (c *Config) Get() interface{} { c.mu.RLock() defer c.mu.RUnlock() return c.data } // GetEnvironment 返回当前环境 func (c *Config) GetEnvironment() string { return c.environment } // GetString 返回与键关联的值作为字符串 func (c *Config) GetString(key string) string { c.mu.RLock() defer c.mu.RUnlock() return c.v.GetString(key) } // GetInt 返回与键关联的值作为整数 func (c *Config) GetInt(key string) int { c.mu.RLock() defer c.mu.RUnlock() return c.v.GetInt(key) } // GetBool 返回与键关联的值作为布尔值 func (c *Config) GetBool(key string) bool { c.mu.RLock() defer c.mu.RUnlock() return c.v.GetBool(key) } // GetFloat64 返回与键关联的值作为 float64 func (c *Config) GetFloat64(key string) float64 { c.mu.RLock() defer c.mu.RUnlock() return c.v.GetFloat64(key) } // GetStringSlice 返回与键关联的值作为字符串切片 func (c *Config) GetStringSlice(key string) []string { c.mu.RLock() defer c.mu.RUnlock() return c.v.GetStringSlice(key) } // Reload 重新加载配置 func (c *Config) Reload() error { c.mu.Lock() defer c.mu.Unlock() err := c.v.ReadInConfig() if err != nil { return err } return c.unmarshal() } // Watchfield 监听配置字段变化 func (c *Config) WatchField(key string, callback func(newValue interface{})) { if !c.autoReload { return } old := c.v.Get(key) c.v.OnConfigChange(func(in fsnotify.Event) { newValue := c.v.Get(key) if reflect.DeepEqual(old, newValue) || newValue == nil { return } callback(newValue) }) }