configx/configx.go

214 lines
4.5 KiB
Go

// 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)
})
}