// Package config 负责从 Nacos 配置中心拉取动态参数并维护本地缓存。 // 服务端逻辑统一通过 config.GetXxx(key) 读取,避免硬编码。 package config import ( "encoding/json" "fmt" "os" "strconv" "sync" "gopkg.in/yaml.v3" "github.com/nacos-group/nacos-sdk-go/v2/clients" "github.com/nacos-group/nacos-sdk-go/v2/clients/config_client" "github.com/nacos-group/nacos-sdk-go/v2/common/constant" "github.com/nacos-group/nacos-sdk-go/v2/vo" ) // Config 维护 Nacos 配置的本地缓存,支持热更监听。 type Config struct { client config_client.IConfigClient cache map[string]interface{} mu sync.RWMutex } // Global 全局配置实例,由 main.go 初始化。 var Global *Config // NewNacosConfig 创建并连接 Nacos 配置中心。 // 参数可从环境变量读取:NACOS_SERVER、NACOS_NAMESPACE。 func NewNacosConfig(serverAddr, namespace string) (*Config, error) { if serverAddr == "" { serverAddr = os.Getenv("NACOS_SERVER") } if serverAddr == "" { serverAddr = "127.0.0.1" } if namespace == "" { namespace = os.Getenv("NACOS_NAMESPACE") } if namespace == "" { namespace = "honghuang" } sc := []constant.ServerConfig{ { IpAddr: serverAddr, Port: 8848, }, } cc := constant.ClientConfig{ NamespaceId: namespace, TimeoutMs: 5000, NotLoadCacheAtStart: true, LogDir: "/tmp/nacos/log", CacheDir: "/tmp/nacos/cache", } client, err := clients.NewConfigClient( vo.NacosClientParam{ ServerConfigs: sc, ClientConfig: &cc, }, ) if err != nil { return nil, fmt.Errorf("nacos config client init failed: %w", err) } return &Config{ client: client, cache: make(map[string]interface{}), }, nil } // Load 加载指定 dataId + group 的 YAML/JSON 配置到本地缓存。 func (c *Config) Load(dataId, group string) error { content, err := c.client.GetConfig(vo.ConfigParam{DataId: dataId, Group: group}) if err != nil { return fmt.Errorf("nacos get config %s/%s failed: %w", dataId, group, err) } var m map[string]interface{} if err := yaml.Unmarshal([]byte(content), &m); err != nil { return fmt.Errorf("parse config %s/%s failed: %w", dataId, group, err) } c.mu.Lock() for k, v := range m { c.cache[k] = v } c.mu.Unlock() // 监听后续变更 _ = c.client.ListenConfig(vo.ConfigParam{ DataId: dataId, Group: group, OnChange: func(namespace, group, dataId, data string) { var changed map[string]interface{} if err := yaml.Unmarshal([]byte(data), &changed); err != nil { return } c.mu.Lock() for k, v := range changed { c.cache[k] = v } c.mu.Unlock() }, }) return nil } // NewStaticConfig 创建一个不连接 Nacos 的静态配置实例,便于测试与本地降级。 func NewStaticConfig(defaults map[string]interface{}) *Config { cache := make(map[string]interface{}) for k, v := range defaults { cache[k] = v } return &Config{cache: cache} } // Get 返回原始配置值。 func (c *Config) Get(key string) (interface{}, bool) { if c == nil { return nil, false } c.mu.RLock() defer c.mu.RUnlock() if c.cache == nil { return nil, false } v, ok := c.cache[key] return v, ok } // GetString 读取字符串配置,缺失返回默认值。 func (c *Config) GetString(key, def string) string { v, ok := c.Get(key) if !ok { return def } s, _ := v.(string) if s == "" { return def } return s } // GetInt 读取整型配置。 func (c *Config) GetInt(key string, def int) int { v, ok := c.Get(key) if !ok { return def } switch n := v.(type) { case int: return n case int64: return int(n) case float64: return int(n) case string: if i, err := strconv.Atoi(n); err == nil { return i } } return def } // GetFloat64 读取浮点配置。 func (c *Config) GetFloat64(key string, def float64) float64 { v, ok := c.Get(key) if !ok { return def } switch n := v.(type) { case float64: return n case int: return float64(n) case string: if f, err := strconv.ParseFloat(n, 64); err == nil { return f } } return def } // GetBool 读取布尔配置。 func (c *Config) GetBool(key string, def bool) bool { v, ok := c.Get(key) if !ok { return def } switch b := v.(type) { case bool: return b case string: if r, err := strconv.ParseBool(b); err == nil { return r } } return def } // GetJSON 读取嵌套 JSON 对象并反序列化到 out。 func (c *Config) GetJSON(key string, out interface{}) bool { v, ok := c.Get(key) if !ok { return false } data, err := json.Marshal(v) if err != nil { return false } return json.Unmarshal(data, out) == nil }