需求:现在的配置文件的文件格式为
/data/
版本号1/
xx.json
yy.json
版本号2/
xx.json
yy.json
package util
import (
"app01/config"
"encoding/json"
"fmt"
"os"
"path/filepath"
"strings"
"sync"
"time"
"github.com/fsnotify/fsnotify"
)
// Package configmanager 提供通用的JSON配置文件管理功能
// 支持多种JSON格式,多版本管理,内存缓存,线程安全
// ConfigManager 通用配置管理器(支持多版本)
type ConfigManager struct {
versions map[string]map[string]interface{} // version -> fileName -> config data
mu sync.RWMutex // 读写锁,保护versions
watcher *fsnotify.Watcher // 文件监控器
watchDone chan struct{} // 用于停止监控的通道
debounceTimers sync.Map // 用于防抖的定时器 map[filePath]*time.Timer
}
// 全局单例
var (
globalConfigManager *ConfigManager
configOnce sync.Once
)
// GetConfigManager 获取全局配置管理器单例
func GetConfigManager() *ConfigManager {
configOnce.Do(func() {
// 创建文件监控器
watcher, err := fsnotify.NewWatcher()
if err != nil {
fmt.Printf("创建文件监控器失败: %v\n", err)
}
globalConfigManager = &ConfigManager{
versions: make(map[string]map[string]interface{}),
watcher: watcher,
watchDone: make(chan struct{}),
}
// 启动监控协程
if watcher != nil {
go globalConfigManager.startWatchLoop()
}
})
return globalConfigManager
}
// WatchConfigFiles 监控指定版本的配置文件变更
func (cm *ConfigManager) WatchConfigFiles(version string, jsonFiles []string) error {
if cm.watcher == nil {
return fmt.Errorf("文件监控器未初始化")
}
for _, jsonFile := range jsonFiles {
filePath := filepath.Join(config.AppConfig.App.ConfigDir, version, jsonFile)
// 添加文件到监控器
if err := cm.watcher.Add(filePath); err != nil {
Log.Error(fmt.Sprintf("添加监控文件 %s 失败: %v", filePath, err))
return fmt.Errorf("添加监控文件 %s 失败: %v", filePath, err)
}
}
return nil
}
// StopWatch 停止文件监控
func (cm *ConfigManager) StopWatch() {
if cm.watcher != nil {
close(cm.watchDone)
err := cm.watcher.Close()
if err != nil {
return
}
cm.watcher = nil
}
}
// startWatchLoop 启动监控事件循环
func (cm *ConfigManager) startWatchLoop() {
for {
select {
case event, ok := <-cm.watcher.Events:
if !ok {
return
}
// 只处理写入事件
if event.Op&fsnotify.Write == fsnotify.Write {
filePath := event.Name
// 防抖处理: 500ms内重复事件只执行最后一次
if timer, ok := cm.debounceTimers.Load(filePath); ok {
timer.(*time.Timer).Stop()
}
// 创建新的定时器
timer := time.AfterFunc(500*time.Millisecond, func() {
cm.debounceTimers.Delete(filePath)
// 解析版本和文件名
relPath, err := filepath.Rel(config.AppConfig.App.ConfigDir, event.Name)
if err != nil {
return
}
// 分割版本和文件名 (data/version/file.json)
parts := strings.Split(relPath, string(filepath.Separator))
if len(parts) < 2 {
Log.Error(fmt.Sprintf("无效的配置文件路径: %s\n", event.Name))
return
}
version := parts[0]
jsonFile := strings.Join(parts[1:], string(filepath.Separator))
// 执行重新加载
if err := cm.ReloadConfigWithVersion(version, jsonFile); err != nil {
Log.Error(fmt.Sprintf("重新加载配置文件失败: %v\n", err))
}
})
cm.debounceTimers.Store(filePath, timer)
}
case err, ok := <-cm.watcher.Errors:
if !ok {
return
}
Log.Error(fmt.Sprintf("监控器错误: %v\n", err))
case <-cm.watchDone:
return
}
}
}
// LoadConfigWithVersion 从文件加载JSON配置到指定版本
func (cm *ConfigManager) LoadConfigWithVersion(version, jsonFile string) error {
cm.mu.Lock()
defer cm.mu.Unlock()
// 初始化版本映射
if cm.versions[version] == nil {
cm.versions[version] = make(map[string]interface{})
}
// 如果已经加载过,直接返回
if _, exists := cm.versions[version][jsonFile]; exists {
return nil
}
filePath := filepath.Join(config.AppConfig.App.ConfigDir, version, jsonFile)
data, err := os.ReadFile(filePath)
if err != nil {
return fmt.Errorf("读取JSON文件 %s 失败: %v", filePath, err)
}
var configStruct interface{}
err = json.Unmarshal(data, &configStruct)
if err != nil {
return fmt.Errorf("解析JSON失败: %v", err)
}
cm.versions[version][jsonFile] = configStruct
return nil
}
// LoadConfigs 批量加载多个配置文件(默认版本)
// LoadConfigsWithVersion 批量加载多个配置文件到指定版本
func (cm *ConfigManager) LoadConfigsWithVersion(version string, jsonFiles []string) error {
for _, jsonFile := range jsonFiles {
err := cm.LoadConfigWithVersion(version, jsonFile)
if err != nil {
return fmt.Errorf("加载文件 %s 到版本 %s 失败: %v", jsonFile, version, err)
}
}
return nil
}
// GetConfigWithVersion 获取指定版本和文件的配置数据,并转换为目标类型
func (cm *ConfigManager) GetConfigWithVersion(version, jsonFile string, target interface{}) error {
cm.mu.RLock()
versionConfigs, versionExists := cm.versions[version]
if !versionExists {
cm.mu.RUnlock()
return fmt.Errorf("版本 %s 不存在", version)
}
configStruct, exists := versionConfigs[jsonFile]
cm.mu.RUnlock()
if !exists {
return fmt.Errorf("配置文件 %s 在版本 %s 中未加载,请先调用LoadConfigWithVersion", jsonFile, version)
}
// 将interface{}转换为JSON字节,再解析为目标类型
// 这样可以处理任意类型的转换
jsonData, err := json.Marshal(configStruct)
if err != nil {
return fmt.Errorf("序列化配置数据失败: %v", err)
}
err = json.Unmarshal(jsonData, target)
if err != nil {
return fmt.Errorf("反序列化到目标类型失败: %v", err)
}
return nil
}
// GetRawConfigWithVersion 获取指定版本的原始配置数据
func (cm *ConfigManager) GetRawConfigWithVersion(version, jsonFile string) (interface{}, error) {
cm.mu.RLock()
defer cm.mu.RUnlock()
versionConfigs, versionExists := cm.versions[version]
if !versionExists {
return nil, fmt.Errorf("版本 %s 不存在", version)
}
configStruct, exists := versionConfigs[jsonFile]
if !exists {
return nil, fmt.Errorf("配置文件 %s 在版本 %s 中未加载", jsonFile, version)
}
return configStruct, nil
}
// IsLoadedWithVersion 检查指定版本和文件是否已加载
func (cm *ConfigManager) IsLoadedWithVersion(version, jsonFile string) bool {
cm.mu.RLock()
defer cm.mu.RUnlock()
versionConfigs, versionExists := cm.versions[version]
if !versionExists {
return false
}
_, exists := versionConfigs[jsonFile]
return exists
}
// GetLoadedFilesWithVersion 获取指定版本的已加载文件列表
func (cm *ConfigManager) GetLoadedFilesWithVersion(version string) []string {
cm.mu.RLock()
defer cm.mu.RUnlock()
versionConfigs, exists := cm.versions[version]
if !exists {
return []string{}
}
files := make([]string, 0, len(versionConfigs))
for fileName := range versionConfigs {
files = append(files, fileName)
}
return files
}
// GetAllVersions 获取所有已加载的版本列表
func (cm *ConfigManager) GetAllVersions() []string {
cm.mu.RLock()
defer cm.mu.RUnlock()
versions := make([]string, 0, len(cm.versions))
for version := range cm.versions {
versions = append(versions, version)
}
return versions
}
// ReloadConfigWithVersion 强制重新加载指定版本的配置文件
func (cm *ConfigManager) ReloadConfigWithVersion(version, jsonFile string) error {
cm.mu.Lock()
defer cm.mu.Unlock()
// 初始化版本映射(如果不存在)
if cm.versions[version] == nil {
cm.versions[version] = make(map[string]interface{})
}
// 删除现有配置
delete(cm.versions[version], jsonFile)
// 重新加载
filePath := filepath.Join(config.AppConfig.App.ConfigDir, version, jsonFile)
data, err := os.ReadFile(filePath)
if err != nil {
return fmt.Errorf("读取JSON文件 %s 失败: %v", filePath, err)
}
var configStruct interface{}
err = json.Unmarshal(data, &configStruct)
if err != nil {
return fmt.Errorf("解析JSON失败: %v", err)
}
cm.versions[version][jsonFile] = configStruct
return nil
}
// ReloadAllConfigsWithVersion 重新加载指定版本的所有已加载配置文件
func (cm *ConfigManager) ReloadAllConfigsWithVersion(version string) error {
files := cm.GetLoadedFilesWithVersion(version)
for _, file := range files {
if err := cm.ReloadConfigWithVersion(version, file); err != nil {
return fmt.Errorf("重新加载文件 %s 在版本 %s 失败: %v", file, version, err)
}
}
return nil
}
// ReloadAllVersions 重新加载所有版本的所有配置文件
func (cm *ConfigManager) ReloadAllVersions() error {
versions := cm.GetAllVersions()
for _, version := range versions {
if err := cm.ReloadAllConfigsWithVersion(version); err != nil {
return err
}
}
return nil
}
// UnloadConfigWithVersion 从指定版本中卸载配置文件
func (cm *ConfigManager) UnloadConfigWithVersion(version, jsonFile string) {
cm.mu.Lock()
defer cm.mu.Unlock()
if cm.versions[version] != nil {
delete(cm.versions[version], jsonFile)
}
}
// UnloadVersion 卸载整个版本
func (cm *ConfigManager) UnloadVersion(version string) {
cm.mu.Lock()
defer cm.mu.Unlock()
delete(cm.versions, version)
}
// Clear 清空所有已加载的配置
func (cm *ConfigManager) Clear() {
cm.mu.Lock()
defer cm.mu.Unlock()
cm.versions = make(map[string]map[string]interface{})
}
调用
// 初始化配置管理器并加载监控data目录下的JSON文件
cm := util.GetConfigManager()
// 停止文件监控
defer cm.StopWatch()
// 获取data目录下的所有版本子目录
versionDirs, err := filepath.Glob(filepath.Join(config.AppConfig.App.ConfigDir, "*"))
if err != nil {
return
}
// 遍历所有版本目录
for _, versionDir := range versionDirs {
// 获取版本号(目录名)
version := filepath.Base(versionDir)
// 获取当前版本目录下的所有JSON文件
jsonFiles, err := filepath.Glob(filepath.Join(versionDir, "*.json"))
if err != nil {
continue
}
// 提取文件名(不包含路径)
fileNames := make([]string, 0, len(jsonFiles))
for _, file := range jsonFiles {
fileNames = append(fileNames, filepath.Base(file))
}
// 加载当前版本的配置文件
if err := cm.LoadConfigsWithVersion(version, fileNames); err != nil {
continue
}
// 监控当前版本的配置文件
if err := cm.WatchConfigFiles(version, fileNames); err != nil {
continue
}
}