♻️ Refactor configuration service

This commit is contained in:
2025-05-28 00:37:18 +08:00
parent 72a222f932
commit 5f102edcf7
18 changed files with 721 additions and 657 deletions

View File

@@ -3,48 +3,22 @@ package services
import (
"errors"
"fmt"
"github.com/wailsapp/wails/v3/pkg/services/log"
"os"
"path/filepath"
"sync"
"time"
"voidraft/internal/models"
"dario.cat/mergo"
"github.com/fsnotify/fsnotify"
"github.com/spf13/viper"
"github.com/wailsapp/wails/v3/pkg/services/log"
)
// ConfigPath 配置路径提供接口
type ConfigPath interface {
// GetConfigPath 获取配置文件路径
GetConfigPath() string
}
// DefaultConfigPathProvider 默认配置路径提供者
type DefaultConfigPathProvider struct{}
// GetConfigPath 获取默认配置路径
func (p *DefaultConfigPathProvider) GetConfigPath() string {
// 获取用户主目录
homePath, err := os.UserHomeDir()
if err != nil {
homePath = "."
}
// 返回固定的配置路径
return filepath.Join(homePath, ".voidraft", "config", "config.json")
}
// ConfigOption 配置服务选项
type ConfigOption struct {
Logger *log.LoggerService // 日志服务
PathProvider ConfigPath // 路径提供者
}
// ConfigService 提供配置管理功能
// ConfigService 提供基于 Viper 的配置管理功能
type ConfigService struct {
store *Store[models.AppConfig] // 配置存储
logger *log.LoggerService // 日志服务
cache *models.AppConfig // 配置缓存
cacheMu sync.RWMutex // 缓存锁
viper *viper.Viper // Viper 实例
logger *log.LoggerService // 日志服务
mu sync.RWMutex // 读写锁
}
// ConfigError 配置错误
@@ -65,280 +39,391 @@ func (e *ConfigError) Unwrap() error {
// Is 实现错误匹配
func (e *ConfigError) Is(target error) bool {
_, ok := target.(*ConfigError)
var configError *ConfigError
ok := errors.As(target, &configError)
return ok
}
// NewConfigService 创建新的配置服务实例
func NewConfigService(opt ...ConfigOption) *ConfigService {
var option ConfigOption
// ConfigLimits 配置限制定义
type ConfigLimits struct {
FontSizeMin int
FontSizeMax int
TabSizeMin int
TabSizeMax int
}
// 使用第一个选项
if len(opt) > 0 {
option = opt[0]
// getConfigLimits 获取配置限制
func getConfigLimits() ConfigLimits {
return ConfigLimits{
FontSizeMin: 12,
FontSizeMax: 28,
TabSizeMin: 2,
TabSizeMax: 8,
}
}
// validateAndFixValue 验证并修正配置值
func (cs *ConfigService) validateAndFixValue(key string, value interface{}) (interface{}, bool) {
limits := getConfigLimits()
fixed := false
switch key {
case "editor.font_size":
if intVal, ok := value.(int); ok {
if intVal < limits.FontSizeMin {
cs.logger.Warning("Config: Font size too small, fixing", "original", intVal, "fixed", limits.FontSizeMin)
return limits.FontSizeMin, true
}
if intVal > limits.FontSizeMax {
cs.logger.Warning("Config: Font size too large, fixing", "original", intVal, "fixed", limits.FontSizeMax)
return limits.FontSizeMax, true
}
}
case "editor.tab_size":
if intVal, ok := value.(int); ok {
if intVal < limits.TabSizeMin {
cs.logger.Warning("Config: Tab size too small, fixing", "original", intVal, "fixed", limits.TabSizeMin)
return limits.TabSizeMin, true
}
if intVal > limits.TabSizeMax {
cs.logger.Warning("Config: Tab size too large, fixing", "original", intVal, "fixed", limits.TabSizeMax)
return limits.TabSizeMax, true
}
}
case "editor.tab_type":
if strVal, ok := value.(string); ok {
validTypes := []string{string(models.TabTypeSpaces), string(models.TabTypeTab)}
isValid := false
for _, validType := range validTypes {
if strVal == validType {
isValid = true
break
}
}
if !isValid {
cs.logger.Warning("Config: Invalid tab type, fixing", "original", strVal, "fixed", string(models.TabTypeSpaces))
return string(models.TabTypeSpaces), true
}
}
case "editor.language":
if strVal, ok := value.(string); ok {
validLanguages := []string{string(models.LangZhCN), string(models.LangEnUS)}
isValid := false
for _, validLang := range validLanguages {
if strVal == validLang {
isValid = true
break
}
}
if !isValid {
cs.logger.Warning("Config: Invalid language, fixing", "original", strVal, "fixed", string(models.LangZhCN))
return string(models.LangZhCN), true
}
}
case "document.auto_save_delay":
if intVal, ok := value.(int); ok {
if intVal < 1000 {
cs.logger.Warning("Config: Auto save delay too small, fixing", "original", intVal, "fixed", 1000)
return 1000, true
}
if intVal > 30000 {
cs.logger.Warning("Config: Auto save delay too large, fixing", "original", intVal, "fixed", 30000)
return 30000, true
}
}
case "document.change_threshold":
if intVal, ok := value.(int); ok {
if intVal < 10 {
cs.logger.Warning("Config: Change threshold too small, fixing", "original", intVal, "fixed", 10)
return 10, true
}
if intVal > 10000 {
cs.logger.Warning("Config: Change threshold too large, fixing", "original", intVal, "fixed", 10000)
return 10000, true
}
}
case "document.min_save_interval":
if intVal, ok := value.(int); ok {
if intVal < 100 {
cs.logger.Warning("Config: Min save interval too small, fixing", "original", intVal, "fixed", 100)
return 100, true
}
if intVal > 10000 {
cs.logger.Warning("Config: Min save interval too large, fixing", "original", intVal, "fixed", 10000)
return 10000, true
}
}
}
return value, fixed
}
// validateAllConfig 验证并修正所有配置
func (cs *ConfigService) validateAllConfig() error {
hasChanges := false
// 获取当前配置
var config models.AppConfig
if err := cs.viper.Unmarshal(&config); err != nil {
return &ConfigError{Operation: "unmarshal_for_validation", Err: err}
}
// 验证编辑器配置
if fixedValue, fixed := cs.validateAndFixValue("editor.font_size", config.Editor.FontSize); fixed {
cs.viper.Set("editor.font_size", fixedValue)
hasChanges = true
}
if fixedValue, fixed := cs.validateAndFixValue("editor.tab_size", config.Editor.TabSize); fixed {
cs.viper.Set("editor.tab_size", fixedValue)
hasChanges = true
}
if fixedValue, fixed := cs.validateAndFixValue("editor.tab_type", string(config.Editor.TabType)); fixed {
cs.viper.Set("editor.tab_type", fixedValue)
hasChanges = true
}
if fixedValue, fixed := cs.validateAndFixValue("editor.language", string(config.Editor.Language)); fixed {
cs.viper.Set("editor.language", fixedValue)
hasChanges = true
}
// 验证文档配置
if fixedValue, fixed := cs.validateAndFixValue("document.auto_save_delay", config.Document.AutoSaveDelay); fixed {
cs.viper.Set("document.auto_save_delay", fixedValue)
hasChanges = true
}
if fixedValue, fixed := cs.validateAndFixValue("document.change_threshold", config.Document.ChangeThreshold); fixed {
cs.viper.Set("document.change_threshold", fixedValue)
hasChanges = true
}
if fixedValue, fixed := cs.validateAndFixValue("document.min_save_interval", config.Document.MinSaveInterval); fixed {
cs.viper.Set("document.min_save_interval", fixedValue)
hasChanges = true
}
// 如果有修正,保存配置
if hasChanges {
if err := cs.viper.WriteConfig(); err != nil {
return &ConfigError{Operation: "save_validated_config", Err: err}
}
cs.logger.Info("Config: Configuration validated and fixed")
}
return nil
}
// NewConfigService 创建新的配置服务实例
func NewConfigService(logger *log.LoggerService) *ConfigService {
// 设置日志服务
logger := option.Logger
if logger == nil {
logger = log.New()
}
// 设置路径提供者
pathProvider := option.PathProvider
if pathProvider == nil {
pathProvider = &DefaultConfigPathProvider{}
// 获取当前工作目录
currentDir, err := os.Getwd()
if err != nil {
currentDir = "."
}
// 获取配置路径
configPath := pathProvider.GetConfigPath()
logger.Info("Config: Using config path", "path", configPath)
// 固定配置路径和文件名
configPath := filepath.Join(currentDir, "config")
configName := "config"
// 创建存储
store := NewStore[models.AppConfig](StoreOption{
FilePath: configPath,
Logger: logger,
})
// 创建 Viper 实例
v := viper.New()
// 配置 Viper
v.SetConfigName(configName)
v.SetConfigType("yaml")
v.AddConfigPath(configPath)
// 设置环境变量前缀
v.SetEnvPrefix("VOIDRAFT")
v.AutomaticEnv()
// 设置默认值
setDefaults(v)
// 构造配置服务实例
service := &ConfigService{
store: store,
viper: v,
logger: logger,
}
// 初始化加载配置
// 初始化配置
if err := service.initConfig(); err != nil {
service.logger.Error("Config: Failed to initialize config", "error", err)
}
// 验证并修正配置
if err := service.validateAllConfig(); err != nil {
service.logger.Error("Config: Failed to validate config", "error", err)
}
// 启动配置文件监听
service.startWatching()
return service
}
// setDefaults 设置默认配置值
func setDefaults(v *viper.Viper) {
defaultConfig := models.NewDefaultAppConfig()
// 编辑器配置默认值
v.SetDefault("editor.font_size", defaultConfig.Editor.FontSize)
v.SetDefault("editor.enable_tab_indent", defaultConfig.Editor.EnableTabIndent)
v.SetDefault("editor.tab_size", defaultConfig.Editor.TabSize)
v.SetDefault("editor.tab_type", defaultConfig.Editor.TabType)
v.SetDefault("editor.language", defaultConfig.Editor.Language)
v.SetDefault("editor.always_on_top", defaultConfig.Editor.AlwaysOnTop)
// 文档配置默认值
v.SetDefault("document.auto_save_delay", defaultConfig.Document.AutoSaveDelay)
v.SetDefault("document.change_threshold", defaultConfig.Document.ChangeThreshold)
v.SetDefault("document.min_save_interval", defaultConfig.Document.MinSaveInterval)
// 路径配置默认值
v.SetDefault("paths.data_path", defaultConfig.Paths.DataPath)
// 元数据默认值
v.SetDefault("metadata.version", defaultConfig.Metadata.Version)
v.SetDefault("metadata.last_updated", defaultConfig.Metadata.LastUpdated)
}
// initConfig 初始化配置
func (cs *ConfigService) initConfig() error {
config, err := cs.loadConfig()
cs.mu.Lock()
defer cs.mu.Unlock()
if err != nil {
// 如果加载失败,使用默认配置
defaultConfig := models.NewDefaultAppConfig()
cs.logger.Info("Config: Using default config")
// 保存默认配置并更新缓存
if err := cs.saveConfigWithCache(*defaultConfig); err != nil {
return &ConfigError{Operation: "init_save_default", Err: err}
// 尝试读取配置文件
if err := cs.viper.ReadInConfig(); err != nil {
var configFileNotFoundError viper.ConfigFileNotFoundError
if errors.As(err, &configFileNotFoundError) {
// 配置文件不存在,创建默认配置文件
cs.logger.Info("Config: Config file not found, creating default config")
return cs.createDefaultConfig()
}
return nil
}
// 合并默认配置
if err := cs.mergeWithDefaults(&config); err != nil {
return &ConfigError{Operation: "init_merge_defaults", Err: err}
// 配置文件存在但读取失败
return &ConfigError{Operation: "read_config", Err: err}
}
cs.logger.Info("Config: Successfully loaded config file", "file", cs.viper.ConfigFileUsed())
return nil
}
// mergeWithDefaults 将默认配置合并到现有配置中
func (cs *ConfigService) mergeWithDefaults(config *models.AppConfig) error {
defaultConfig := models.NewDefaultAppConfig()
// createDefaultConfig 创建默认配置文件
func (cs *ConfigService) createDefaultConfig() error {
// 获取配置目录路径
currentDir, err := os.Getwd()
if err != nil {
currentDir = "."
}
configDir := filepath.Join(currentDir, "config")
configPath := filepath.Join(configDir, "config.yaml")
// 使用mergo库合并配置
if err := mergo.Merge(config, defaultConfig, mergo.WithOverrideEmptySlice); err != nil {
return err
// 确保配置目录存在
if err := os.MkdirAll(configDir, 0755); err != nil {
return &ConfigError{Operation: "create_config_dir", Err: err}
}
// 更新最后修改时间
config.Metadata.LastUpdated = time.Now()
// 设置当前时间为最后更新时间
cs.viper.Set("metadata.last_updated", time.Now())
// 保存合并后的配置
return cs.saveConfigWithCache(*config)
}
// loadConfig 加载配置
func (cs *ConfigService) loadConfig() (models.AppConfig, error) {
// 尝试从缓存获取
cs.cacheMu.RLock()
cachedConfig := cs.cache
cs.cacheMu.RUnlock()
if cachedConfig != nil {
return *cachedConfig, nil
}
// 从存储加载
config := cs.store.Get()
// 检查配置是否有效
if !isValidConfig(config) {
return models.AppConfig{}, errors.New("invalid or empty configuration")
}
return config, nil
}
// isValidConfig 检查配置是否有效
func isValidConfig(config models.AppConfig) bool {
// 检查关键字段
if config.Metadata.Version == "" {
return false
}
return true
}
// saveConfigWithCache 保存配置并更新缓存
func (cs *ConfigService) saveConfigWithCache(config models.AppConfig) error {
// 更新缓存
cs.cacheMu.Lock()
cs.cache = &config
cs.cacheMu.Unlock()
// 保存到存储
if err := cs.store.Set(config); err != nil {
return err
// 使用 SafeWriteConfigAs 写入配置文件(如果文件不存在则创建)
if err := cs.viper.SafeWriteConfigAs(configPath); err != nil {
return &ConfigError{Operation: "write_default_config", Err: err}
}
cs.logger.Info("Config: Created default config file", "path", configPath)
return nil
}
// startWatching 启动配置文件监听
func (cs *ConfigService) startWatching() {
// 设置配置变化回调
cs.viper.OnConfigChange(func(e fsnotify.Event) {
cs.logger.Info("Config: Config file changed", "file", e.Name, "operation", e.Op.String())
// 注释掉自动更新时间戳,避免无限循环
// err := cs.Set("metadata.last_updated", time.Now())
// if err != nil {
// cs.logger.Error("Config: Failed to update last_updated field", "error", err)
// }
})
// 启动配置文件监听
cs.viper.WatchConfig()
cs.logger.Info("Config: Started watching config file for changes")
}
// GetConfig 获取完整应用配置
func (cs *ConfigService) GetConfig() (*models.AppConfig, error) {
// 优先使用缓存
cs.cacheMu.RLock()
if cs.cache != nil {
config := *cs.cache
cs.cacheMu.RUnlock()
return &config, nil
cs.mu.RLock()
defer cs.mu.RUnlock()
var config models.AppConfig
if err := cs.viper.Unmarshal(&config); err != nil {
return nil, &ConfigError{Operation: "unmarshal_config", Err: err}
}
cs.cacheMu.RUnlock()
// 加载配置
config, err := cs.loadConfig()
if err != nil {
// 加载失败,使用默认配置
defaultConfig := models.NewDefaultAppConfig()
// 保存默认配置
if saveErr := cs.saveConfigWithCache(*defaultConfig); saveErr != nil {
cs.logger.Error("Config: Failed to save default config", "error", saveErr)
return nil, &ConfigError{Operation: "get_save_default", Err: saveErr}
}
return defaultConfig, nil
}
return &config, nil
}
// SaveConfig 保存完整应用配置
func (cs *ConfigService) SaveConfig(config *models.AppConfig) error {
if config == nil {
return errors.New("cannot save nil config")
// Set 设置配置
func (cs *ConfigService) Set(key string, value interface{}) error {
cs.mu.Lock()
defer cs.mu.Unlock()
// 验证并修正配置值
validatedValue, wasFixed := cs.validateAndFixValue(key, value)
// 设置验证后的值
cs.viper.Set(key, validatedValue)
// 使用 WriteConfig 写入配置文件(会触发文件监听)
if err := cs.viper.WriteConfig(); err != nil {
return &ConfigError{Operation: "set_config", Err: err}
}
// 更新配置元数据
config.Metadata.LastUpdated = time.Now()
if wasFixed {
cs.logger.Debug("Config: Successfully set config with validation", "key", key, "original", value, "fixed", validatedValue)
} else {
cs.logger.Debug("Config: Successfully set config", "key", key, "value", value)
}
// 保存配置
return cs.saveConfigWithCache(*config)
return nil
}
// UpdateEditorConfig 更新编辑器配置
func (cs *ConfigService) UpdateEditorConfig(editorConfig models.EditorConfig) error {
config, err := cs.GetConfig()
if err != nil {
return err
}
// 保存当前的语言设置
currentLanguage := config.Editor.Language
// 更新编辑器配置
config.Editor = editorConfig
// 如果更新后的语言设置为空,则使用之前的语言设置
if editorConfig.Language == "" {
config.Editor.Language = currentLanguage
}
return cs.SaveConfig(config)
}
// GetEditorConfig 获取编辑器配置
func (cs *ConfigService) GetEditorConfig() (models.EditorConfig, error) {
config, err := cs.GetConfig()
if err != nil {
return models.EditorConfig{}, err
}
return config.Editor, nil
}
// SetLanguage 设置语言
func (cs *ConfigService) SetLanguage(language models.LanguageType) error {
// 验证语言类型有效
if language != models.LangZhCN && language != models.LangEnUS {
return errors.New("invalid language type")
}
config, err := cs.GetConfig()
if err != nil {
return err
}
config.Editor.Language = language
return cs.SaveConfig(config)
}
// GetLanguage 获取当前语言设置
func (cs *ConfigService) GetLanguage() (models.LanguageType, error) {
editorConfig, err := cs.GetEditorConfig()
if err != nil {
return "", err
}
return editorConfig.Language, nil
}
// UpdatePaths 更新路径配置
func (cs *ConfigService) UpdatePaths(paths models.PathsConfig) error {
config, err := cs.GetConfig()
if err != nil {
return err
}
config.Paths = paths
return cs.SaveConfig(config)
}
// GetPaths 获取路径配置
func (cs *ConfigService) GetPaths() (models.PathsConfig, error) {
config, err := cs.GetConfig()
if err != nil {
return models.PathsConfig{}, err
}
return config.Paths, nil
}
// UpdateMetadata 更新配置元数据
func (cs *ConfigService) UpdateMetadata(metadata models.ConfigMetadata) error {
config, err := cs.GetConfig()
if err != nil {
return err
}
config.Metadata = metadata
return cs.SaveConfig(config)
}
// GetMetadata 获取配置元数据
func (cs *ConfigService) GetMetadata() (models.ConfigMetadata, error) {
config, err := cs.GetConfig()
if err != nil {
return models.ConfigMetadata{}, err
}
return config.Metadata, nil
// Get 获取配置
func (cs *ConfigService) Get(key string) interface{} {
cs.mu.RLock()
defer cs.mu.RUnlock()
return cs.viper.Get(key)
}
// ResetConfig 重置为默认配置
func (cs *ConfigService) ResetConfig() error {
defaultConfig := models.NewDefaultAppConfig()
return cs.SaveConfig(defaultConfig)
cs.mu.Lock()
defer cs.mu.Unlock()
// 重新设置默认值
setDefaults(cs.viper)
// 使用 WriteConfig 写入配置文件(会触发文件监听)
if err := cs.viper.WriteConfig(); err != nil {
return &ConfigError{Operation: "reset_config", Err: err}
}
cs.logger.Info("Config: Successfully reset to default configuration")
return nil
}