214 lines
4.5 KiB
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)
|
||
|
})
|
||
|
}
|