Files
customer-server/service/config_service.go
2026-03-14 10:02:49 +08:00

212 lines
4.8 KiB
Go
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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)
}