393 lines
8.7 KiB
Go
393 lines
8.7 KiB
Go
package services
|
||
|
||
import (
|
||
"context"
|
||
"encoding/json"
|
||
"fmt"
|
||
"os"
|
||
"path/filepath"
|
||
"strings"
|
||
"sync"
|
||
"time"
|
||
"voidraft/internal/models"
|
||
|
||
jsonparser "github.com/knadh/koanf/parsers/json"
|
||
"github.com/knadh/koanf/providers/file"
|
||
"github.com/knadh/koanf/providers/structs"
|
||
"github.com/knadh/koanf/v2"
|
||
"github.com/wailsapp/wails/v3/pkg/services/log"
|
||
)
|
||
|
||
// ConfigService 应用配置服务
|
||
type ConfigService struct {
|
||
koanf *koanf.Koanf // koanf 实例
|
||
logger *log.LogService // 日志服务
|
||
configDir string // 配置目录
|
||
settingsPath string // 设置文件路径
|
||
mu sync.RWMutex // 读写锁
|
||
fileProvider *file.File // 文件提供器,用于监听
|
||
|
||
observer *ConfigObserver
|
||
|
||
// 配置迁移器
|
||
configMigrator *ConfigMigrator
|
||
}
|
||
|
||
// NewConfigService 创建新的配置服务实例
|
||
func NewConfigService(logger *log.LogService) *ConfigService {
|
||
// 获取用户主目录
|
||
homeDir, err := os.UserHomeDir()
|
||
if err != nil {
|
||
panic(fmt.Errorf("unable to get the user's home directory: %w", err))
|
||
}
|
||
|
||
// 设置配置目录和设置文件路径
|
||
configDir := filepath.Join(homeDir, ".voidraft", "config")
|
||
settingsPath := filepath.Join(configDir, "settings.json")
|
||
|
||
cs := &ConfigService{
|
||
logger: logger,
|
||
configDir: configDir,
|
||
settingsPath: settingsPath,
|
||
koanf: koanf.New("."),
|
||
}
|
||
|
||
// 初始化配置观察者系统
|
||
cs.observer = NewConfigObserver(logger)
|
||
|
||
// 初始化配置迁移器
|
||
cs.configMigrator = NewConfigMigrator(logger, configDir, "settings", settingsPath)
|
||
|
||
cs.initConfig()
|
||
|
||
// 启动配置文件监听
|
||
cs.startWatching()
|
||
|
||
return cs
|
||
}
|
||
|
||
// setDefaults 设置默认配置
|
||
func (cs *ConfigService) setDefaults() error {
|
||
defaultConfig := models.NewDefaultAppConfig()
|
||
|
||
if err := cs.koanf.Load(structs.Provider(defaultConfig, "json"), nil); err != nil {
|
||
return err
|
||
}
|
||
|
||
return nil
|
||
}
|
||
|
||
// initConfig 初始化配置
|
||
func (cs *ConfigService) initConfig() error {
|
||
cs.mu.Lock()
|
||
defer cs.mu.Unlock()
|
||
|
||
// 检查配置文件是否存在
|
||
if _, err := os.Stat(cs.settingsPath); os.IsNotExist(err) {
|
||
return cs.createDefaultConfig()
|
||
}
|
||
|
||
// 配置文件存在,直接加载现有配置
|
||
cs.fileProvider = file.Provider(cs.settingsPath)
|
||
if err := cs.koanf.Load(cs.fileProvider, jsonparser.Parser()); err != nil {
|
||
return err
|
||
}
|
||
|
||
return nil
|
||
}
|
||
|
||
// MigrateConfig 执行配置迁移
|
||
func (cs *ConfigService) MigrateConfig() error {
|
||
if cs.configMigrator == nil {
|
||
return nil
|
||
}
|
||
|
||
defaultConfig := models.NewDefaultAppConfig()
|
||
result, err := cs.configMigrator.AutoMigrate(defaultConfig, cs.koanf)
|
||
|
||
if err != nil {
|
||
cs.logger.Error("Failed to check config migration", "error", err)
|
||
return err
|
||
}
|
||
|
||
if result != nil && result.Migrated {
|
||
cs.logger.Info("Config migration performed",
|
||
"fields", result.MissingFields,
|
||
"backup", result.BackupPath)
|
||
}
|
||
|
||
return nil
|
||
}
|
||
|
||
// createDefaultConfig 创建默认配置文件
|
||
func (cs *ConfigService) createDefaultConfig() error {
|
||
// 确保配置目录存在
|
||
if err := os.MkdirAll(cs.configDir, 0755); err != nil {
|
||
return err
|
||
}
|
||
|
||
if err := cs.setDefaults(); err != nil {
|
||
return err
|
||
}
|
||
|
||
if err := cs.writeConfigToFile(); err != nil {
|
||
return err
|
||
}
|
||
|
||
// 创建文件提供器
|
||
cs.fileProvider = file.Provider(cs.settingsPath)
|
||
|
||
if err := cs.koanf.Load(cs.fileProvider, jsonparser.Parser()); err != nil {
|
||
return err
|
||
}
|
||
return nil
|
||
}
|
||
|
||
// startWatching 启动配置文件监听
|
||
func (cs *ConfigService) startWatching() {
|
||
if cs.fileProvider == nil {
|
||
return
|
||
}
|
||
cs.fileProvider.Watch(func(event interface{}, err error) {
|
||
if err != nil {
|
||
return
|
||
}
|
||
|
||
cs.mu.Lock()
|
||
oldSnapshot := cs.createConfigSnapshot()
|
||
cs.koanf.Load(cs.fileProvider, jsonparser.Parser())
|
||
cs.mu.Unlock()
|
||
|
||
// 检测配置变更并通知观察者
|
||
cs.detectAndNotifyChanges(oldSnapshot)
|
||
})
|
||
|
||
}
|
||
|
||
// stopWatching 停止配置文件监听
|
||
func (cs *ConfigService) stopWatching() {
|
||
if cs.fileProvider != nil {
|
||
cs.fileProvider.Unwatch()
|
||
}
|
||
}
|
||
|
||
// GetConfig 获取完整应用配置
|
||
func (cs *ConfigService) GetConfig() (*models.AppConfig, error) {
|
||
cs.mu.RLock()
|
||
defer cs.mu.RUnlock()
|
||
|
||
var config models.AppConfig
|
||
if err := cs.koanf.UnmarshalWithConf("", &config, koanf.UnmarshalConf{Tag: "json"}); err != nil {
|
||
return nil, err
|
||
}
|
||
|
||
return &config, nil
|
||
}
|
||
|
||
// Set 设置配置项
|
||
func (cs *ConfigService) Set(key string, value interface{}) error {
|
||
cs.mu.Lock()
|
||
|
||
// 获取旧值
|
||
oldValue := cs.koanf.Get(key)
|
||
|
||
// 设置值到koanf
|
||
cs.koanf.Set(key, value)
|
||
|
||
// 更新时间戳
|
||
cs.koanf.Set("metadata.lastUpdated", time.Now().Format(time.RFC3339))
|
||
|
||
// 将配置写回文件
|
||
err := cs.writeConfigToFile()
|
||
cs.mu.Unlock()
|
||
|
||
if err != nil {
|
||
return err
|
||
}
|
||
|
||
if cs.observer != nil {
|
||
cs.observer.Notify(key, oldValue, value)
|
||
}
|
||
|
||
return nil
|
||
}
|
||
|
||
// Get 获取配置项
|
||
func (cs *ConfigService) Get(key string) interface{} {
|
||
cs.mu.RLock()
|
||
defer cs.mu.RUnlock()
|
||
return cs.koanf.Get(key)
|
||
}
|
||
|
||
// ResetConfig 强制重置所有配置为默认值
|
||
func (cs *ConfigService) ResetConfig() error {
|
||
cs.mu.Lock()
|
||
|
||
// 保存旧配置快照
|
||
oldSnapshot := cs.createConfigSnapshot()
|
||
|
||
// 停止文件监听
|
||
if cs.fileProvider != nil {
|
||
cs.fileProvider.Unwatch()
|
||
cs.fileProvider = nil
|
||
}
|
||
|
||
// 设置默认配置
|
||
if err := cs.setDefaults(); err != nil {
|
||
cs.mu.Unlock()
|
||
return err
|
||
}
|
||
|
||
// 写入配置文件
|
||
if err := cs.writeConfigToFile(); err != nil {
|
||
cs.mu.Unlock()
|
||
return err
|
||
}
|
||
|
||
// 重新创建koanf实例
|
||
cs.koanf = koanf.New(".")
|
||
|
||
// 重新加载默认配置到koanf
|
||
if err := cs.setDefaults(); err != nil {
|
||
cs.mu.Unlock()
|
||
return err
|
||
}
|
||
|
||
// 重新创建文件提供器
|
||
cs.fileProvider = file.Provider(cs.settingsPath)
|
||
|
||
// 重新加载配置文件
|
||
if err := cs.koanf.Load(cs.fileProvider, jsonparser.Parser()); err != nil {
|
||
cs.mu.Unlock()
|
||
return err
|
||
}
|
||
|
||
cs.mu.Unlock()
|
||
|
||
// 重新启动文件监听
|
||
cs.startWatching()
|
||
|
||
// 检测配置变更并通知观察者
|
||
cs.detectAndNotifyChanges(oldSnapshot)
|
||
|
||
return nil
|
||
}
|
||
|
||
// writeConfigToFile 将配置写回JSON文件
|
||
func (cs *ConfigService) writeConfigToFile() error {
|
||
configBytes, err := cs.koanf.Marshal(jsonparser.Parser())
|
||
if err != nil {
|
||
return err
|
||
}
|
||
|
||
if err := os.WriteFile(cs.settingsPath, configBytes, 0644); err != nil {
|
||
return err
|
||
}
|
||
|
||
return nil
|
||
}
|
||
|
||
// Watch 注册配置变更监听器
|
||
func (cs *ConfigService) Watch(path string, callback ObserverCallback) CancelFunc {
|
||
return cs.observer.Watch(path, callback)
|
||
}
|
||
|
||
// WatchWithContext 使用 Context 注册监听器
|
||
func (cs *ConfigService) WatchWithContext(ctx context.Context, path string, callback ObserverCallback) {
|
||
cs.observer.WatchWithContext(ctx, path, callback)
|
||
}
|
||
|
||
// createConfigSnapshot 创建当前配置的快照
|
||
func (cs *ConfigService) createConfigSnapshot() map[string]interface{} {
|
||
cs.mu.RLock()
|
||
defer cs.mu.RUnlock()
|
||
snapshot := make(map[string]interface{})
|
||
allKeys := cs.koanf.All()
|
||
|
||
// 递归展平配置
|
||
flattenMap("", allKeys, snapshot)
|
||
return snapshot
|
||
}
|
||
|
||
// 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()
|
||
|
||
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.observer != nil {
|
||
cs.observer.Shutdown()
|
||
}
|
||
return nil
|
||
}
|