package services import ( "context" "crypto/sha256" "encoding/json" "fmt" "sync" "time" "voidraft/internal/models" "github.com/spf13/viper" "github.com/wailsapp/wails/v3/pkg/services/log" ) // ConfigChangeType 配置变更类型 type ConfigChangeType string const ( // ConfigChangeTypeHotkey 热键配置变更 ConfigChangeTypeHotkey ConfigChangeType = "hotkey" // ConfigChangeTypeDataPath 数据路径配置变更 ConfigChangeTypeDataPath ConfigChangeType = "datapath" ) // ConfigChangeCallback 配置变更回调函数类型 type ConfigChangeCallback func(changeType ConfigChangeType, oldConfig, newConfig *models.AppConfig) error // ConfigListener 配置监听器 type ConfigListener struct { Name string // 监听器名称 ChangeType ConfigChangeType // 监听的配置变更类型 Callback ConfigChangeCallback // 回调函数(现在包含新旧配置) DebounceDelay time.Duration // 防抖延迟时间 GetConfigFunc func(*viper.Viper) *models.AppConfig // 获取相关配置的函数 // 内部状态 mu sync.RWMutex // 监听器状态锁 timer *time.Timer // 防抖定时器 lastConfigHash string // 上次配置的哈希值,用于变更检测 lastConfig *models.AppConfig // 上次的配置副本 ctx context.Context cancel context.CancelFunc } // ConfigNotificationService 配置通知服务 type ConfigNotificationService struct { listeners sync.Map // 使用sync.Map替代普通map+锁 logger *log.LoggerService // 日志服务 viper *viper.Viper // Viper实例 ctx context.Context cancel context.CancelFunc wg sync.WaitGroup } // NewConfigNotificationService 创建配置通知服务 func NewConfigNotificationService(viper *viper.Viper, logger *log.LoggerService) *ConfigNotificationService { ctx, cancel := context.WithCancel(context.Background()) return &ConfigNotificationService{ logger: logger, viper: viper, ctx: ctx, cancel: cancel, } } // RegisterListener 注册配置监听器 func (cns *ConfigNotificationService) RegisterListener(listener *ConfigListener) error { // 清理已存在的监听器 if existingValue, loaded := cns.listeners.LoadAndDelete(listener.ChangeType); loaded { if existing, ok := existingValue.(interface{ cancel() }); ok { existing.cancel() } } // 初始化新监听器 listener.ctx, listener.cancel = context.WithCancel(cns.ctx) if err := cns.initializeListenerState(listener); err != nil { listener.cancel() return fmt.Errorf("failed to initialize listener state: %w", err) } cns.listeners.Store(listener.ChangeType, listener) return nil } // initializeListenerState 初始化监听器状态 func (cns *ConfigNotificationService) initializeListenerState(listener *ConfigListener) error { if listener.GetConfigFunc == nil { return fmt.Errorf("GetConfigFunc is required") } if config := listener.GetConfigFunc(cns.viper); config != nil { listener.mu.Lock() listener.lastConfig = deepCopyConfig(config) listener.lastConfigHash = computeConfigHash(config) listener.mu.Unlock() } return nil } // UnregisterListener 注销配置监听器 func (cns *ConfigNotificationService) UnregisterListener(changeType ConfigChangeType) { if value, loaded := cns.listeners.LoadAndDelete(changeType); loaded { if listener, ok := value.(*ConfigListener); ok { listener.cancel() } } } // CheckConfigChanges 检查配置变更并通知相关监听器 func (cns *ConfigNotificationService) CheckConfigChanges() { cns.listeners.Range(func(key, value interface{}) bool { if listener, ok := value.(*ConfigListener); ok { cns.checkAndNotify(listener) } return true }) } // checkAndNotify 检查配置变更并通知 func (cns *ConfigNotificationService) checkAndNotify(listener *ConfigListener) { if listener.GetConfigFunc == nil { return } currentConfig := listener.GetConfigFunc(cns.viper) listener.mu.RLock() lastHash := listener.lastConfigHash lastConfig := listener.lastConfig listener.mu.RUnlock() var hasChanges bool var currentHash string if currentConfig != nil { currentHash = computeConfigHash(currentConfig) hasChanges = currentHash != lastHash } else { hasChanges = lastConfig != nil } if hasChanges { listener.mu.Lock() listener.lastConfig = deepCopyConfig(currentConfig) listener.lastConfigHash = currentHash listener.mu.Unlock() cns.debounceNotify(listener, lastConfig, currentConfig) } } // computeConfigHash 计算配置的哈希值 func computeConfigHash(config *models.AppConfig) string { if config == nil { return "" } jsonBytes, err := json.Marshal(config) if err != nil { return fmt.Sprintf("%p", config) } hash := sha256.Sum256(jsonBytes) return fmt.Sprintf("%x", hash) } // deepCopyConfig 深拷贝配置对象 func deepCopyConfig(src *models.AppConfig) *models.AppConfig { if src == nil { return nil } jsonBytes, err := json.Marshal(src) if err != nil { return src } var dst models.AppConfig if err := json.Unmarshal(jsonBytes, &dst); err != nil { return src } return &dst } // debounceNotify 防抖通知 func (cns *ConfigNotificationService) debounceNotify(listener *ConfigListener, oldConfig, newConfig *models.AppConfig) { listener.mu.Lock() defer listener.mu.Unlock() // 取消之前的定时器 if listener.timer != nil { listener.timer.Stop() } // 创建配置副本,避免在闭包中持有原始引用 oldConfigCopy := deepCopyConfig(oldConfig) newConfigCopy := deepCopyConfig(newConfig) changeType := listener.ChangeType listener.timer = time.AfterFunc(listener.DebounceDelay, func() { cns.executeCallback(listener.ctx, changeType, listener.Callback, oldConfigCopy, newConfigCopy) }) } // executeCallback 执行回调函数 func (cns *ConfigNotificationService) executeCallback( ctx context.Context, changeType ConfigChangeType, callback ConfigChangeCallback, oldConfig, newConfig *models.AppConfig, ) { cns.wg.Add(1) go func() { defer cns.wg.Done() defer func() { if r := recover(); r != nil { cns.logger.Error("config callback panic", "error", r) } }() callbackCtx, cancel := context.WithTimeout(ctx, 5*time.Second) defer cancel() done := make(chan error, 1) go func() { done <- callback(changeType, oldConfig, newConfig) }() select { case <-callbackCtx.Done(): cns.logger.Error("config callback timeout") case err := <-done: if err != nil { cns.logger.Error("config callback error", "error", err) } } }() } // Cleanup 清理所有监听器 func (cns *ConfigNotificationService) Cleanup() { cns.cancel() // 取消所有context cns.listeners.Range(func(key, value interface{}) bool { cns.listeners.Delete(key) return true }) cns.wg.Wait() // 等待所有协程完成 } // CreateHotkeyListener 创建热键配置监听器 func CreateHotkeyListener(callback func(enable bool, hotkey *models.HotkeyCombo) error) *ConfigListener { return &ConfigListener{ Name: "HotkeyListener", ChangeType: ConfigChangeTypeHotkey, Callback: func(changeType ConfigChangeType, oldConfig, newConfig *models.AppConfig) error { if newConfig != nil { return callback(newConfig.General.EnableGlobalHotkey, &newConfig.General.GlobalHotkey) } // 使用默认配置 defaultConfig := models.NewDefaultAppConfig() return callback(defaultConfig.General.EnableGlobalHotkey, &defaultConfig.General.GlobalHotkey) }, DebounceDelay: 200 * time.Millisecond, GetConfigFunc: func(v *viper.Viper) *models.AppConfig { var config models.AppConfig if err := v.Unmarshal(&config); err != nil { return nil } return &config }, } } // CreateDataPathListener 创建数据路径配置监听器 func CreateDataPathListener(callback func(oldPath, newPath string) error) *ConfigListener { return &ConfigListener{ Name: "DataPathListener", ChangeType: ConfigChangeTypeDataPath, Callback: func(changeType ConfigChangeType, oldConfig, newConfig *models.AppConfig) error { var oldPath, newPath string if oldConfig != nil { oldPath = oldConfig.General.DataPath } if newConfig != nil { newPath = newConfig.General.DataPath } else { defaultConfig := models.NewDefaultAppConfig() newPath = defaultConfig.General.DataPath } if oldPath != newPath { return callback(oldPath, newPath) } return nil }, DebounceDelay: 100 * time.Millisecond, GetConfigFunc: func(v *viper.Viper) *models.AppConfig { var config models.AppConfig if err := v.Unmarshal(&config); err != nil { return nil } return &config }, } } // ServiceShutdown 关闭服务 func (cns *ConfigNotificationService) ServiceShutdown() error { cns.Cleanup() return nil }