Files
customer-server/service/config_service.go

212 lines
4.8 KiB
Go
Raw Normal View History

2026-03-14 10:02:49 +08:00
package service
import (
"context"
"encoding/json"
"fmt"
"os"
"strconv"
"strings"
"sync"
"time"
"github.com/gogf/gf/v2/frame/g"
"github.com/gogf/gf/v2/os/glog"
"github.com/gogf/gf/v2/util/gconv"
"github.com/hashicorp/consul/api"
)
var (
configCache = make(map[string]interface{}) // 动态配置缓存
configMu sync.RWMutex // 读写锁
consulClient *api.Client // Consul客户端
startOnce sync.Once // 确保只启动一次监听
)
// InitConsulWatcher 初始化Consul配置监听
func InitConsulWatcher(ctx context.Context) error {
consulAddr := g.Cfg().MustGet(ctx, "consul.address").String()
if consulAddr == "" {
glog.Warning(ctx, "Consul未配置使用本地config.yml")
return nil
}
config := api.DefaultConfig()
config.Address = consulAddr
client, err := api.NewClient(config)
if err != nil {
glog.Errorf(ctx, "Consul客户端初始化失败: %v", err)
return err
}
consulClient = client
startOnce.Do(func() {
go watchAllConfigsV2(ctx)
glog.Info(ctx, "Consul配置监听已启动")
})
return nil
}
// watchAllConfigsV2 监听Consul前缀下所有配置
func watchAllConfigsV2(ctx context.Context) {
const prefix = "customerService/"
kv := consulClient.KV()
var lastIndex uint64
for {
pairs, meta, err := kv.List(prefix, &api.QueryOptions{
WaitIndex: lastIndex,
WaitTime: 5 * time.Minute,
})
if err != nil {
glog.Errorf(ctx, "Consul查询失败: %v", err)
time.Sleep(5 * time.Second)
continue
}
if meta.LastIndex != lastIndex {
lastIndex = meta.LastIndex
updateConfigCacheDiff(ctx, pairs)
}
}
}
// updateConfigCacheDiff 增量更新配置缓存
func updateConfigCacheDiff(ctx context.Context, pairs api.KVPairs) {
configMu.Lock()
defer configMu.Unlock()
for _, pair := range pairs {
key := strings.TrimPrefix(pair.Key, "customerService/")
key = strings.ReplaceAll(key, "/", ".")
newVal := parseValue(pair.Value)
oldVal, exists := configCache[key]
if !exists || oldVal != newVal {
logConfigChange(ctx, key, gconv.String(oldVal), gconv.String(newVal))
configCache[key] = newVal
}
}
}
// parseValue 自动推断配置值类型
func parseValue(value []byte) interface{} {
str := string(value)
if i, err := strconv.Atoi(str); err == nil {
return i
}
if b, err := strconv.ParseBool(str); err == nil {
return b
}
var arr []interface{}
if err := json.Unmarshal(value, &arr); err == nil {
return arr
}
return str
}
// GetConfigInt 读取int配置
func GetConfigInt(ctx context.Context, key string) int {
configMu.RLock()
val, ok := configCache[key]
configMu.RUnlock()
if ok {
return gconv.Int(val)
}
return g.Cfg().MustGet(ctx, key).Int()
}
// GetConfigString 读取string配置
func GetConfigString(ctx context.Context, key string) string {
configMu.RLock()
val, ok := configCache[key]
configMu.RUnlock()
if ok {
return gconv.String(val)
}
return g.Cfg().MustGet(ctx, key).String()
}
// GetConfigBool 读取bool配置
func GetConfigBool(ctx context.Context, key string) bool {
configMu.RLock()
val, ok := configCache[key]
configMu.RUnlock()
if ok {
return gconv.Bool(val)
}
return g.Cfg().MustGet(ctx, key).Bool()
}
// GetConfigStringSlice 读取字符串数组配置
func GetConfigStringSlice(ctx context.Context, key string) []string {
configMu.RLock()
val, ok := configCache[key]
configMu.RUnlock()
if ok {
if arr, ok := val.([]interface{}); ok {
result := make([]string, len(arr))
for i, v := range arr {
result[i] = gconv.String(v)
}
return result
}
}
return g.Cfg().MustGet(ctx, key).Strings()
}
// GetInstanceConfigStringSlice 读取实例级字符串数组配置(支持实例级负载隔离)
// 优先级:实例专用配置 > 全局Consul配置 > config.yml
func GetInstanceConfigStringSlice(ctx context.Context, key string) []string {
// 获取实例ID环境变量优先hostname备用
instanceID := os.Getenv("INSTANCE_ID")
if instanceID == "" {
hostname, err := os.Hostname()
if err == nil {
instanceID = hostname
}
}
// 如果有实例ID先查找实例专用配置
if instanceID != "" {
instanceKey := fmt.Sprintf("instance.%s.%s", instanceID, key)
configMu.RLock()
val, ok := configCache[instanceKey]
configMu.RUnlock()
if ok {
if arr, ok := val.([]interface{}); ok {
result := make([]string, len(arr))
for i, v := range arr {
result[i] = gconv.String(v)
}
glog.Debugf(ctx, "🎯 使用实例专用配置: %s = %v", instanceKey, result)
return result
}
}
}
// 未找到实例配置fallback到全局配置
return GetConfigStringSlice(ctx, key)
}
// logConfigChange 记录配置变更
func logConfigChange(ctx context.Context, key, oldVal, newVal string) {
glog.Infof(ctx, "📝 配置变更: %s = %s → %s", key, oldVal, newVal)
}