212 lines
4.8 KiB
Go
212 lines
4.8 KiB
Go
|
|
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)
|
|||
|
|
}
|