Go管理配置文件+实时监控文件变更

需求:现在的配置文件的文件格式为

/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{}                     // 用于停止监控的通道
	watchedVersions sync.Map                          // 已监控的版本目录 map[version]struct{}
}

// 全局单例
var (
	globalConfigManager *ConfigManager
	configOnce          sync.Once
)

// GetConfigManager 获取全局配置管理器单例
func GetConfigManager() *ConfigManager {
	configOnce.Do(func() {
		// 创建文件监控器
		watcher, err := fsnotify.NewWatcher()
		if err != nil {
			Log.Error("创建文件监控器失败:" + err.Error())
		}

		globalConfigManager = &ConfigManager{
			versions:        make(map[string]map[string]interface{}),
			watcher:         watcher,
			watchDone:       make(chan struct{}),
			watchedVersions: sync.Map{},
		}

		// 启动监控协程
		if watcher != nil {
			go globalConfigManager.startWatchLoop()
		}
	})
	return globalConfigManager
}

// WatchConfigFiles 监控指定版本的配置文件变更
// 现在改为监控整个版本目录,以支持新文件的自动发现和加载
func (cm *ConfigManager) WatchConfigFiles(version string) error {
	if cm.watcher == nil {
		return fmt.Errorf("文件监控器未初始化")
	}

	// 监控整个版本目录,而不是单个文件
	dirPath := filepath.Join(config.AppConfig.App.ConfigDir, version)

	// 确保目录存在
	if _, err := os.Stat(dirPath); os.IsNotExist(err) {
		Log.Error(fmt.Sprintf("版本目录 %s 不存在", dirPath))
		return fmt.Errorf("版本目录 %s 不存在", dirPath)
	}

	// 检查是否已经监控过该版本目录
	if _, exists := cm.watchedVersions.Load(version); !exists {
		// 添加目录到监控器
		if err := cm.watcher.Add(dirPath); err != nil {
			Log.Error(fmt.Sprintf("添加监控目录 %s 失败: %v", dirPath, err))
			return fmt.Errorf("添加监控目录 %s 失败: %v", dirPath, err)
		}
		// 记录已监控的版本
		cm.watchedVersions.Store(version, struct{}{})
	}

	return nil
}

// WatchConfigRootDir 监控配置根目录,以支持新版本目录的自动发现
func (cm *ConfigManager) WatchConfigRootDir() error {
	if cm.watcher == nil {
		return fmt.Errorf("文件监控器未初始化")
	}

	// 添加配置根目录到监控器
	if err := cm.watcher.Add(config.AppConfig.App.ConfigDir); err != nil {
		Log.Error(fmt.Sprintf("添加监控根目录 %s 失败: %v", config.AppConfig.App.ConfigDir, err))
		return fmt.Errorf("添加监控根目录 %s 失败: %v", config.AppConfig.App.ConfigDir, err)
	}

	return nil
}

// LoadAndWatchVersionDir 加载并监控指定的版本目录
func (cm *ConfigManager) LoadAndWatchVersionDir(versionDir string) error {
	// 获取版本号(目录名)
	version := filepath.Base(versionDir)

	// 获取当前版本目录下的所有JSON文件
	jsonFiles, err := filepath.Glob(filepath.Join(versionDir, "*.json"))
	if err != nil {
		return fmt.Errorf("获取JSON文件失败: %v", err)
	}

	// 提取文件名(不包含路径)
	fileNames := make([]string, 0, len(jsonFiles))
	for _, file := range jsonFiles {
		fileNames = append(fileNames, filepath.Base(file))
	}

	// 加载当前版本的配置文件
	if err := cm.LoadConfigsWithVersion(version, fileNames); err != nil {
		return fmt.Errorf("加载配置文件失败: %v", err)
	}

	// 监控当前版本的配置文件
	if err := cm.WatchConfigFiles(version); err != nil {
		return fmt.Errorf("监控配置文件失败: %v", 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
			}

			filePath := event.Name

			// 过滤临时文件和备份文件
			if strings.HasSuffix(filePath, "~") || strings.HasPrefix(filepath.Base(filePath), ".") {
				continue
			}

			// 检查是否是新创建的版本目录
			if event.Op&fsnotify.Create != 0 {
				fileInfo, err := os.Stat(filePath)
				if err == nil && fileInfo.IsDir() {
					// 检查是否是配置根目录下的直接子目录(即版本目录)
					parentDir := filepath.Dir(filePath)
					if parentDir == config.AppConfig.App.ConfigDir {
						// 这是一个新的版本目录,加载并监控它
						if err := cm.LoadAndWatchVersionDir(filePath); err != nil {
							Log.Error(fmt.Sprintf("加载新版本目录失败: %v\n", err))
						}
						continue
					}
				}
			}

			// 解析版本和文件名
			relPath, err := filepath.Rel(config.AppConfig.App.ConfigDir, filePath)
			if err != nil {
				Log.Error(fmt.Sprintf("无法获取相对路径: %v\n", err))
				continue
			}

			// 分割版本和文件名 (version/file.json)
			parts := strings.Split(relPath, string(filepath.Separator))
			if len(parts) < 2 {
				// 这可能是版本目录本身的事件,跳过
				continue
			}

			version := parts[0]
			jsonFile := strings.Join(parts[1:], string(filepath.Separator))
			// 处理移除事件 - 直接从内存中卸载配置
			// 注意:删除事件不需要检查文件是否存在
			if event.Op&fsnotify.Remove != 0 {
				cm.UnloadConfigWithVersion(version, jsonFile)
				continue
			}

			// 处理写入或创建事件 - 直接重新加载配置
			if event.Op&(fsnotify.Write|fsnotify.Create) != 0 {
				// 检查事件是否针对文件(忽略目录事件)
				fileInfo, err := os.Stat(filePath)
				if err != nil || fileInfo.IsDir() || os.IsNotExist(err) {
					// 如果是目录事件或无法获取文件信息,跳过处理
					continue
				}

				if err := cm.ReloadConfigWithVersion(version, jsonFile); err != nil {
					Log.Error(fmt.Sprintf("重新加载配置文件失败: %v\n", err))
				}
			}
		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)
	// 检查文件是否存在(避免处理已删除的文件)
	if _, err := os.Stat(filePath); os.IsNotExist(err) {
		// 文件不存在,可能是在删除过程中触发的事件,跳过处理
		return nil
	}
	time.Sleep(50 * time.Millisecond)
	data, err := os.ReadFile(filePath)
	if err != nil {
		return fmt.Errorf("读取JSON文件 %s 失败: %v", filePath, err)
	}

	// 检查数据是否为空
	if len(data) == 0 {
		return fmt.Errorf("JSON文件 %s 为空", filePath)
	}

	var configStruct interface{}
	err = json.Unmarshal(data, &configStruct)
	if err != nil {
		return fmt.Errorf("版本: %s,文件: %s,解析JSON失败: %v", version, jsonFile, 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()
	// 监控配置根目录,以支持新版本目录的自动发现
	if err := cm.WatchConfigRootDir(); err != nil {
		fmt.Printf("监控配置根目录失败: %v\n", err)
		os.Exit(1)
	}

	// 获取并加载所有现有的版本目录
	versionDirs, err := filepath.Glob(filepath.Join(config.AppConfig.App.ConfigDir, "*"))
	if err != nil {
		fmt.Printf("获取版本目录列表失败: %v\n", err)
		os.Exit(1)
	}

	// 加载并监控每个版本目录
	for _, versionDir := range versionDirs {
		// 确保是目录
		fileInfo, err := os.Stat(versionDir)
		if err != nil || !fileInfo.IsDir() {
			continue
		}

		// 加载并监控该版本目录
		if err := cm.LoadAndWatchVersionDir(versionDir); err != nil {
			fmt.Printf("加载版本目录 %s 失败: %v\n", versionDir, err)
			// 继续处理其他版本,不立即退出
		}
	}
posted @ 2025-11-03 15:05  朝阳1  阅读(6)  评论(0)    收藏  举报