Files
voidraft/internal/services/config_service.go
landaiqing cc98e556c6
Some checks failed
Deploy VitePress site to Pages / build (push) Has been cancelled
Deploy VitePress site to Pages / Deploy (push) Has been cancelled
♻️ Optimize code
2025-11-07 22:34:12 +08:00

393 lines
8.9 KiB
Go
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
package services
import (
"context"
"encoding/json"
"fmt"
"github.com/wailsapp/wails/v3/pkg/application"
"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")
observerService := NewConfigObserver(logger)
configMigrator := NewConfigMigrator(logger, configDir, "settings", settingsPath)
return &ConfigService{
logger: logger,
configDir: configDir,
settingsPath: settingsPath,
koanf: koanf.New("."),
observer: observerService,
configMigrator: configMigrator,
}
}
// ServiceStartup initializes the service when the application starts
func (cs *ConfigService) ServiceStartup(ctx context.Context, options application.ServiceOptions) error {
err := cs.initConfig()
if err != nil {
panic(err)
}
// 启动配置文件监听
cs.startWatching()
return nil
}
// 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()
_, err := cs.configMigrator.AutoMigrate(defaultConfig, cs.koanf)
if err != nil {
return err
}
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())
newSnapshot := cs.createConfigSnapshot()
cs.mu.Unlock()
// 检测配置变更并通知观察者
cs.notifyChanges(oldSnapshot, newSnapshot)
})
}
// 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)
// 更新时间戳
newTimestamp := time.Now().Format(time.RFC3339)
cs.koanf.Set("metadata.lastUpdated", newTimestamp)
// 将配置写回文件
err := cs.writeConfigToFile()
if err != nil {
// 写文件失败,回滚内存状态
if oldValue != nil {
cs.koanf.Set(key, oldValue)
} else {
cs.koanf.Delete(key)
}
cs.mu.Unlock()
return err
}
cs.mu.Unlock()
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
}
newSnapshot := cs.createConfigSnapshot()
cs.mu.Unlock()
// 重新启动文件监听
cs.startWatching()
// 检测配置变更并通知观察者
cs.notifyChanges(oldSnapshot, newSnapshot)
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{} {
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
}
}
}
// notifyChanges 检测配置变更并通知观察者
func (cs *ConfigService) notifyChanges(oldSnapshot, newSnapshot map[string]interface{}) {
// 检测变更
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
}