♻️ Refactor configuration change notification service

This commit is contained in:
2025-11-07 00:35:11 +08:00
parent 551e7e2cfd
commit 5902f482d9
11 changed files with 586 additions and 705 deletions

View File

@@ -1,10 +1,12 @@
package services
import (
"errors"
"context"
"encoding/json"
"fmt"
"os"
"path/filepath"
"strings"
"sync"
"time"
"voidraft/internal/models"
@@ -25,36 +27,12 @@ type ConfigService struct {
mu sync.RWMutex // 读写锁
fileProvider *file.File // 文件提供器,用于监听
// 配置通知服务
notificationService *ConfigNotificationService
observer *ConfigObserver
// 配置迁移器
configMigrator *ConfigMigrator
}
// ConfigError 配置错误
type ConfigError struct {
Operation string // 操作名称
Err error // 原始错误
}
// Error 实现error接口
func (e *ConfigError) Error() string {
return fmt.Sprintf("config error during %s: %v", e.Operation, e.Err)
}
// Unwrap 获取原始错误
func (e *ConfigError) Unwrap() error {
return e.Err
}
// Is 实现错误匹配
func (e *ConfigError) Is(target error) bool {
var configError *ConfigError
ok := errors.As(target, &configError)
return ok
}
// NewConfigService 创建新的配置服务实例
func NewConfigService(logger *log.LogService) *ConfigService {
// 获取用户主目录
@@ -74,8 +52,8 @@ func NewConfigService(logger *log.LogService) *ConfigService {
koanf: koanf.New("."),
}
// 初始化配置通知服务
cs.notificationService = NewConfigNotificationService(cs.koanf, logger)
// 初始化配置观察者系统
cs.observer = NewConfigObserver(logger)
// 初始化配置迁移器
cs.configMigrator = NewConfigMigrator(logger, configDir, "settings", settingsPath)
@@ -93,7 +71,7 @@ func (cs *ConfigService) setDefaults() error {
defaultConfig := models.NewDefaultAppConfig()
if err := cs.koanf.Load(structs.Provider(defaultConfig, "json"), nil); err != nil {
return &ConfigError{Operation: "load_defaults", Err: err}
return err
}
return nil
@@ -112,7 +90,7 @@ func (cs *ConfigService) initConfig() error {
// 配置文件存在,直接加载现有配置
cs.fileProvider = file.Provider(cs.settingsPath)
if err := cs.koanf.Load(cs.fileProvider, jsonparser.Parser()); err != nil {
return &ConfigError{Operation: "load_config_file", Err: err}
return err
}
return nil
@@ -145,7 +123,7 @@ func (cs *ConfigService) MigrateConfig() error {
func (cs *ConfigService) createDefaultConfig() error {
// 确保配置目录存在
if err := os.MkdirAll(cs.configDir, 0755); err != nil {
return &ConfigError{Operation: "create_config_dir", Err: err}
return err
}
if err := cs.setDefaults(); err != nil {
@@ -160,7 +138,7 @@ func (cs *ConfigService) createDefaultConfig() error {
cs.fileProvider = file.Provider(cs.settingsPath)
if err := cs.koanf.Load(cs.fileProvider, jsonparser.Parser()); err != nil {
return &ConfigError{Operation: "load_config_file", Err: err}
return err
}
return nil
}
@@ -170,21 +148,20 @@ func (cs *ConfigService) startWatching() {
if cs.fileProvider == nil {
return
}
err := cs.fileProvider.Watch(func(event interface{}, err error) {
cs.fileProvider.Watch(func(event interface{}, err error) {
if err != nil {
return
}
cs.koanf.Load(cs.fileProvider, jsonparser.Parser())
// 使用配置通知服务检查所有已注册的配置变更
if cs.notificationService != nil {
cs.notificationService.CheckConfigChanges()
}
cs.mu.Lock()
oldSnapshot := cs.createConfigSnapshot()
cs.koanf.Load(cs.fileProvider, jsonparser.Parser())
cs.mu.Unlock()
// 检测配置变更并通知观察者
cs.detectAndNotifyChanges(oldSnapshot)
})
if err != nil {
cs.logger.Error("Failed to setup config file watcher", "error", err)
}
}
// stopWatching 停止配置文件监听
@@ -201,7 +178,7 @@ func (cs *ConfigService) GetConfig() (*models.AppConfig, error) {
var config models.AppConfig
if err := cs.koanf.UnmarshalWithConf("", &config, koanf.UnmarshalConf{Tag: "json"}); err != nil {
return nil, &ConfigError{Operation: "unmarshal_config", Err: err}
return nil, err
}
return &config, nil
@@ -210,7 +187,9 @@ func (cs *ConfigService) GetConfig() (*models.AppConfig, error) {
// Set 设置配置项
func (cs *ConfigService) Set(key string, value interface{}) error {
cs.mu.Lock()
defer cs.mu.Unlock()
// 获取旧值
oldValue := cs.koanf.Get(key)
// 设置值到koanf
cs.koanf.Set(key, value)
@@ -219,7 +198,18 @@ func (cs *ConfigService) Set(key string, value interface{}) error {
cs.koanf.Set("metadata.lastUpdated", time.Now().Format(time.RFC3339))
// 将配置写回文件
return cs.writeConfigToFile()
err := cs.writeConfigToFile()
cs.mu.Unlock()
if err != nil {
return err
}
if cs.observer != nil {
cs.observer.Notify(key, oldValue, value)
}
return nil
}
// Get 获取配置项
@@ -232,7 +222,9 @@ func (cs *ConfigService) Get(key string) interface{} {
// ResetConfig 强制重置所有配置为默认值
func (cs *ConfigService) ResetConfig() error {
cs.mu.Lock()
defer cs.mu.Unlock()
// 保存旧配置快照
oldSnapshot := cs.createConfigSnapshot()
// 停止文件监听
if cs.fileProvider != nil {
@@ -242,12 +234,14 @@ func (cs *ConfigService) ResetConfig() error {
// 设置默认配置
if err := cs.setDefaults(); err != nil {
return &ConfigError{Operation: "reset_set_defaults", Err: err}
cs.mu.Unlock()
return err
}
// 写入配置文件
if err := cs.writeConfigToFile(); err != nil {
return &ConfigError{Operation: "reset_write_config", Err: err}
cs.mu.Unlock()
return err
}
// 重新创建koanf实例
@@ -255,7 +249,8 @@ func (cs *ConfigService) ResetConfig() error {
// 重新加载默认配置到koanf
if err := cs.setDefaults(); err != nil {
return &ConfigError{Operation: "reset_reload_defaults", Err: err}
cs.mu.Unlock()
return err
}
// 重新创建文件提供器
@@ -263,16 +258,17 @@ func (cs *ConfigService) ResetConfig() error {
// 重新加载配置文件
if err := cs.koanf.Load(cs.fileProvider, jsonparser.Parser()); err != nil {
return &ConfigError{Operation: "reset_reload_config", Err: err}
cs.mu.Unlock()
return err
}
cs.mu.Unlock()
// 重新启动文件监听
cs.startWatching()
// 手动触发配置变更检查,确保通知系统能感知到变更
if cs.notificationService != nil {
cs.notificationService.CheckConfigChanges()
}
// 检测配置变更并通知观察者
cs.detectAndNotifyChanges(oldSnapshot)
return nil
}
@@ -281,71 +277,116 @@ func (cs *ConfigService) ResetConfig() error {
func (cs *ConfigService) writeConfigToFile() error {
configBytes, err := cs.koanf.Marshal(jsonparser.Parser())
if err != nil {
return &ConfigError{Operation: "marshal_config", Err: err}
return err
}
if err := os.WriteFile(cs.settingsPath, configBytes, 0644); err != nil {
return &ConfigError{Operation: "write_config_file", Err: err}
return err
}
return nil
}
// SetHotkeyChangeCallback 设置热键配置变更回调
func (cs *ConfigService) SetHotkeyChangeCallback(callback func(enable bool, hotkey *models.HotkeyCombo) error) error {
cs.mu.Lock()
defer cs.mu.Unlock()
// 创建热键监听器并注册
hotkeyListener := CreateHotkeyListener("DefaultHotkeyListener", callback)
return cs.notificationService.RegisterListener(hotkeyListener)
// Watch 注册配置变更监听器
func (cs *ConfigService) Watch(path string, callback ObserverCallback) CancelFunc {
return cs.observer.Watch(path, callback)
}
// SetDataPathChangeCallback 设置数据路径配置变更回调
func (cs *ConfigService) SetDataPathChangeCallback(callback func() error) error {
cs.mu.Lock()
defer cs.mu.Unlock()
// 创建数据路径监听器并注册
dataPathListener := CreateDataPathListener("DefaultDataPathListener", callback)
return cs.notificationService.RegisterListener(dataPathListener)
// WatchWithContext 使用 Context 注册监听器
func (cs *ConfigService) WatchWithContext(ctx context.Context, path string, callback ObserverCallback) {
cs.observer.WatchWithContext(ctx, path, callback)
}
// SetBackupConfigChangeCallback 设置备份配置变更回调
func (cs *ConfigService) SetBackupConfigChangeCallback(callback func(config *models.GitBackupConfig) error) error {
cs.mu.Lock()
defer cs.mu.Unlock()
// createConfigSnapshot 创建当前配置的快照
func (cs *ConfigService) createConfigSnapshot() map[string]interface{} {
cs.mu.RLock()
defer cs.mu.RUnlock()
snapshot := make(map[string]interface{})
allKeys := cs.koanf.All()
// 创建备份配置监听器并注册
backupListener := CreateBackupConfigListener("DefaultBackupConfigListener", callback)
return cs.notificationService.RegisterListener(backupListener)
// 递归展平配置
flattenMap("", allKeys, snapshot)
return snapshot
}
// SetWindowSnapConfigChangeCallback 设置窗口吸附配置变更回调
func (cs *ConfigService) SetWindowSnapConfigChangeCallback(callback func(enabled bool) error) error {
cs.mu.Lock()
defer cs.mu.Unlock()
// flattenMap 递归展平嵌套的 map使用 strings.Builder 优化字符串拼接)
func flattenMap(prefix string, data map[string]interface{}, result map[string]interface{}) {
var builder strings.Builder
for key, value := range data {
builder.Reset()
if prefix != "" {
builder.WriteString(prefix)
builder.WriteString(".")
}
builder.WriteString(key)
fullKey := builder.String()
// 创建窗口吸附配置监听器并注册
windowSnapListener := CreateWindowSnapConfigListener("DefaultWindowSnapConfigListener", callback)
return cs.notificationService.RegisterListener(windowSnapListener)
if valueMap, ok := value.(map[string]interface{}); ok {
// 递归处理嵌套 map
flattenMap(fullKey, valueMap, result)
} else {
// 保存叶子节点
result[fullKey] = value
}
}
}
// detectAndNotifyChanges 检测配置变更并通知观察者
func (cs *ConfigService) detectAndNotifyChanges(oldSnapshot map[string]interface{}) {
// 创建新快照
newSnapshot := cs.createConfigSnapshot()
// 检测变更
changes := make(map[string]struct {
OldValue interface{}
NewValue interface{}
})
// 检查新增和修改的键
for key, newValue := range newSnapshot {
oldValue, exists := oldSnapshot[key]
if !exists || !isEqual(oldValue, newValue) {
changes[key] = struct {
OldValue interface{}
NewValue interface{}
}{
OldValue: oldValue,
NewValue: newValue,
}
}
}
// 检查删除的键
for key, oldValue := range oldSnapshot {
if _, exists := newSnapshot[key]; !exists {
changes[key] = struct {
OldValue interface{}
NewValue interface{}
}{
OldValue: oldValue,
NewValue: nil,
}
}
}
// 通知所有变更
if cs.observer != nil && len(changes) > 0 {
cs.observer.NotifyAll(changes)
}
}
// isEqual 值相等比较
func isEqual(a, b interface{}) bool {
aJSON, _ := json.Marshal(a)
bJSON, _ := json.Marshal(b)
return string(aJSON) == string(bJSON)
}
// ServiceShutdown 关闭服务
func (cs *ConfigService) ServiceShutdown() error {
cs.stopWatching()
if cs.notificationService != nil {
cs.notificationService.Cleanup()
if cs.observer != nil {
cs.observer.Shutdown()
}
return nil
}
// GetConfigDir 获取配置目录
func (cs *ConfigService) GetConfigDir() string {
return cs.configDir
}
// GetSettingsPath 获取设置文件路径
func (cs *ConfigService) GetSettingsPath() string {
return cs.settingsPath
}