fix: 修复有可能配置丢失的问题

This commit is contained in:
Kerwin
2025-08-18 13:38:52 +08:00
parent 98b94b3313
commit 05243bcfe7
13 changed files with 944 additions and 165 deletions

View File

@@ -97,37 +97,100 @@ func RequestToSystemConfig(req *dto.SystemConfigRequest) []entity.SystemConfig {
}
var configs []entity.SystemConfig
var updatedKeys []string
// 字符串字段 - 处理所有字段,包括空值
// 对于广告相关字段,允许空值以便清空配置
configs = append(configs, entity.SystemConfig{Key: entity.ConfigKeySiteTitle, Value: req.SiteTitle, Type: entity.ConfigTypeString})
configs = append(configs, entity.SystemConfig{Key: entity.ConfigKeySiteDescription, Value: req.SiteDescription, Type: entity.ConfigTypeString})
configs = append(configs, entity.SystemConfig{Key: entity.ConfigKeyKeywords, Value: req.Keywords, Type: entity.ConfigTypeString})
configs = append(configs, entity.SystemConfig{Key: entity.ConfigKeyAuthor, Value: req.Author, Type: entity.ConfigTypeString})
configs = append(configs, entity.SystemConfig{Key: entity.ConfigKeyCopyright, Value: req.Copyright, Type: entity.ConfigTypeString})
configs = append(configs, entity.SystemConfig{Key: entity.ConfigKeySiteLogo, Value: req.SiteLogo, Type: entity.ConfigTypeString})
configs = append(configs, entity.SystemConfig{Key: entity.ConfigKeyApiToken, Value: req.ApiToken, Type: entity.ConfigTypeString})
configs = append(configs, entity.SystemConfig{Key: entity.ConfigKeyForbiddenWords, Value: req.ForbiddenWords, Type: entity.ConfigTypeString})
configs = append(configs, entity.SystemConfig{Key: entity.ConfigKeyAdKeywords, Value: req.AdKeywords, Type: entity.ConfigTypeString})
configs = append(configs, entity.SystemConfig{Key: entity.ConfigKeyAutoInsertAd, Value: req.AutoInsertAd, Type: entity.ConfigTypeString})
// 字符串字段 - 处理被设置的字段
if req.SiteTitle != nil {
configs = append(configs, entity.SystemConfig{Key: entity.ConfigKeySiteTitle, Value: *req.SiteTitle, Type: entity.ConfigTypeString})
updatedKeys = append(updatedKeys, entity.ConfigKeySiteTitle)
}
if req.SiteDescription != nil {
configs = append(configs, entity.SystemConfig{Key: entity.ConfigKeySiteDescription, Value: *req.SiteDescription, Type: entity.ConfigTypeString})
updatedKeys = append(updatedKeys, entity.ConfigKeySiteDescription)
}
if req.Keywords != nil {
configs = append(configs, entity.SystemConfig{Key: entity.ConfigKeyKeywords, Value: *req.Keywords, Type: entity.ConfigTypeString})
updatedKeys = append(updatedKeys, entity.ConfigKeyKeywords)
}
if req.Author != nil {
configs = append(configs, entity.SystemConfig{Key: entity.ConfigKeyAuthor, Value: *req.Author, Type: entity.ConfigTypeString})
updatedKeys = append(updatedKeys, entity.ConfigKeyAuthor)
}
if req.Copyright != nil {
configs = append(configs, entity.SystemConfig{Key: entity.ConfigKeyCopyright, Value: *req.Copyright, Type: entity.ConfigTypeString})
updatedKeys = append(updatedKeys, entity.ConfigKeyCopyright)
}
if req.SiteLogo != nil {
configs = append(configs, entity.SystemConfig{Key: entity.ConfigKeySiteLogo, Value: *req.SiteLogo, Type: entity.ConfigTypeString})
updatedKeys = append(updatedKeys, entity.ConfigKeySiteLogo)
}
if req.ApiToken != nil {
configs = append(configs, entity.SystemConfig{Key: entity.ConfigKeyApiToken, Value: *req.ApiToken, Type: entity.ConfigTypeString})
updatedKeys = append(updatedKeys, entity.ConfigKeyApiToken)
}
if req.ForbiddenWords != nil {
configs = append(configs, entity.SystemConfig{Key: entity.ConfigKeyForbiddenWords, Value: *req.ForbiddenWords, Type: entity.ConfigTypeString})
updatedKeys = append(updatedKeys, entity.ConfigKeyForbiddenWords)
}
if req.AdKeywords != nil {
configs = append(configs, entity.SystemConfig{Key: entity.ConfigKeyAdKeywords, Value: *req.AdKeywords, Type: entity.ConfigTypeString})
updatedKeys = append(updatedKeys, entity.ConfigKeyAdKeywords)
}
if req.AutoInsertAd != nil {
configs = append(configs, entity.SystemConfig{Key: entity.ConfigKeyAutoInsertAd, Value: *req.AutoInsertAd, Type: entity.ConfigTypeString})
updatedKeys = append(updatedKeys, entity.ConfigKeyAutoInsertAd)
}
// 布尔值字段 - 只处理实际提交的字段
// 注意:由于 Go 的零值机制,我们需要通过其他方式判断字段是否被提交
// 这里暂时保持原样,但建议前端只提交有变化的字段
configs = append(configs, entity.SystemConfig{Key: entity.ConfigKeyAutoProcessReadyResources, Value: strconv.FormatBool(req.AutoProcessReadyResources), Type: entity.ConfigTypeBool})
configs = append(configs, entity.SystemConfig{Key: entity.ConfigKeyAutoTransferEnabled, Value: strconv.FormatBool(req.AutoTransferEnabled), Type: entity.ConfigTypeBool})
configs = append(configs, entity.SystemConfig{Key: entity.ConfigKeyAutoFetchHotDramaEnabled, Value: strconv.FormatBool(req.AutoFetchHotDramaEnabled), Type: entity.ConfigTypeBool})
configs = append(configs, entity.SystemConfig{Key: entity.ConfigKeyMaintenanceMode, Value: strconv.FormatBool(req.MaintenanceMode), Type: entity.ConfigTypeBool})
configs = append(configs, entity.SystemConfig{Key: entity.ConfigKeyEnableRegister, Value: strconv.FormatBool(req.EnableRegister), Type: entity.ConfigTypeBool})
// 布尔值字段 - 只处理被设置的字段
if req.AutoProcessReadyResources != nil {
configs = append(configs, entity.SystemConfig{Key: entity.ConfigKeyAutoProcessReadyResources, Value: strconv.FormatBool(*req.AutoProcessReadyResources), Type: entity.ConfigTypeBool})
updatedKeys = append(updatedKeys, entity.ConfigKeyAutoProcessReadyResources)
}
if req.AutoTransferEnabled != nil {
configs = append(configs, entity.SystemConfig{Key: entity.ConfigKeyAutoTransferEnabled, Value: strconv.FormatBool(*req.AutoTransferEnabled), Type: entity.ConfigTypeBool})
updatedKeys = append(updatedKeys, entity.ConfigKeyAutoTransferEnabled)
}
if req.AutoFetchHotDramaEnabled != nil {
configs = append(configs, entity.SystemConfig{Key: entity.ConfigKeyAutoFetchHotDramaEnabled, Value: strconv.FormatBool(*req.AutoFetchHotDramaEnabled), Type: entity.ConfigTypeBool})
updatedKeys = append(updatedKeys, entity.ConfigKeyAutoFetchHotDramaEnabled)
}
if req.MaintenanceMode != nil {
configs = append(configs, entity.SystemConfig{Key: entity.ConfigKeyMaintenanceMode, Value: strconv.FormatBool(*req.MaintenanceMode), Type: entity.ConfigTypeBool})
updatedKeys = append(updatedKeys, entity.ConfigKeyMaintenanceMode)
}
if req.EnableRegister != nil {
configs = append(configs, entity.SystemConfig{Key: entity.ConfigKeyEnableRegister, Value: strconv.FormatBool(*req.EnableRegister), Type: entity.ConfigTypeBool})
updatedKeys = append(updatedKeys, entity.ConfigKeyEnableRegister)
}
// 整数字段 - 添加所有提交的字段包括0值
configs = append(configs, entity.SystemConfig{Key: entity.ConfigKeyAutoProcessInterval, Value: strconv.Itoa(req.AutoProcessInterval), Type: entity.ConfigTypeInt})
configs = append(configs, entity.SystemConfig{Key: entity.ConfigKeyAutoTransferLimitDays, Value: strconv.Itoa(req.AutoTransferLimitDays), Type: entity.ConfigTypeInt})
configs = append(configs, entity.SystemConfig{Key: entity.ConfigKeyAutoTransferMinSpace, Value: strconv.Itoa(req.AutoTransferMinSpace), Type: entity.ConfigTypeInt})
configs = append(configs, entity.SystemConfig{Key: entity.ConfigKeyPageSize, Value: strconv.Itoa(req.PageSize), Type: entity.ConfigTypeInt})
// 整数字段 - 只处理被设置的字段
if req.AutoProcessInterval != nil {
configs = append(configs, entity.SystemConfig{Key: entity.ConfigKeyAutoProcessInterval, Value: strconv.Itoa(*req.AutoProcessInterval), Type: entity.ConfigTypeInt})
updatedKeys = append(updatedKeys, entity.ConfigKeyAutoProcessInterval)
}
if req.AutoTransferLimitDays != nil {
configs = append(configs, entity.SystemConfig{Key: entity.ConfigKeyAutoTransferLimitDays, Value: strconv.Itoa(*req.AutoTransferLimitDays), Type: entity.ConfigTypeInt})
updatedKeys = append(updatedKeys, entity.ConfigKeyAutoTransferLimitDays)
}
if req.AutoTransferMinSpace != nil {
configs = append(configs, entity.SystemConfig{Key: entity.ConfigKeyAutoTransferMinSpace, Value: strconv.Itoa(*req.AutoTransferMinSpace), Type: entity.ConfigTypeInt})
updatedKeys = append(updatedKeys, entity.ConfigKeyAutoTransferMinSpace)
}
if req.PageSize != nil {
configs = append(configs, entity.SystemConfig{Key: entity.ConfigKeyPageSize, Value: strconv.Itoa(*req.PageSize), Type: entity.ConfigTypeInt})
updatedKeys = append(updatedKeys, entity.ConfigKeyPageSize)
}
// 三方统计配置
configs = append(configs, entity.SystemConfig{Key: entity.ConfigKeyThirdPartyStatsCode, Value: req.ThirdPartyStatsCode, Type: entity.ConfigTypeString})
// 三方统计配置 - 只处理被设置的字段
if req.ThirdPartyStatsCode != nil {
configs = append(configs, entity.SystemConfig{Key: entity.ConfigKeyThirdPartyStatsCode, Value: *req.ThirdPartyStatsCode, Type: entity.ConfigTypeString})
updatedKeys = append(updatedKeys, entity.ConfigKeyThirdPartyStatsCode)
}
// 记录更新的配置项
if len(updatedKeys) > 0 {
utils.Info("配置更新 - 被修改的配置项: %v", updatedKeys)
}
return configs
}

View File

@@ -3,38 +3,38 @@ package dto
// SystemConfigRequest 系统配置请求
type SystemConfigRequest struct {
// SEO 配置
SiteTitle string `json:"site_title"`
SiteDescription string `json:"site_description"`
Keywords string `json:"keywords"`
Author string `json:"author"`
Copyright string `json:"copyright"`
SiteLogo string `json:"site_logo"`
SiteTitle *string `json:"site_title,omitempty"`
SiteDescription *string `json:"site_description,omitempty"`
Keywords *string `json:"keywords,omitempty"`
Author *string `json:"author,omitempty"`
Copyright *string `json:"copyright,omitempty"`
SiteLogo *string `json:"site_logo,omitempty"`
// 自动处理配置组
AutoProcessReadyResources bool `json:"auto_process_ready_resources"` // 自动处理待处理资源
AutoProcessInterval int `json:"auto_process_interval"` // 自动处理间隔(分钟)
AutoTransferEnabled bool `json:"auto_transfer_enabled"` // 开启自动转存
AutoTransferLimitDays int `json:"auto_transfer_limit_days"` // 自动转存限制天数0表示不限制
AutoTransferMinSpace int `json:"auto_transfer_min_space"` // 最小存储空间GB
AutoFetchHotDramaEnabled bool `json:"auto_fetch_hot_drama_enabled"` // 自动拉取热播剧名字
AutoProcessReadyResources *bool `json:"auto_process_ready_resources,omitempty"` // 自动处理待处理资源
AutoProcessInterval *int `json:"auto_process_interval,omitempty"` // 自动处理间隔(分钟)
AutoTransferEnabled *bool `json:"auto_transfer_enabled,omitempty"` // 开启自动转存
AutoTransferLimitDays *int `json:"auto_transfer_limit_days,omitempty"` // 自动转存限制天数0表示不限制
AutoTransferMinSpace *int `json:"auto_transfer_min_space,omitempty"` // 最小存储空间GB
AutoFetchHotDramaEnabled *bool `json:"auto_fetch_hot_drama_enabled,omitempty"` // 自动拉取热播剧名字
// API配置
ApiToken string `json:"api_token"` // 公开API访问令牌
ApiToken *string `json:"api_token,omitempty"` // 公开API访问令牌
// 违禁词配置
ForbiddenWords string `json:"forbidden_words"` // 违禁词列表,用逗号分隔
ForbiddenWords *string `json:"forbidden_words,omitempty"` // 违禁词列表,用逗号分隔
// 广告配置
AdKeywords string `json:"ad_keywords"` // 广告关键词列表,用逗号分隔
AutoInsertAd string `json:"auto_insert_ad"` // 自动插入广告内容
AdKeywords *string `json:"ad_keywords,omitempty"` // 广告关键词列表,用逗号分隔
AutoInsertAd *string `json:"auto_insert_ad,omitempty"` // 自动插入广告内容
// 其他配置
PageSize int `json:"page_size"`
MaintenanceMode bool `json:"maintenance_mode"`
EnableRegister bool `json:"enable_register"` // 开启注册功能
PageSize *int `json:"page_size,omitempty"`
MaintenanceMode *bool `json:"maintenance_mode,omitempty"`
EnableRegister *bool `json:"enable_register,omitempty"` // 开启注册功能
// 三方统计配置
ThirdPartyStatsCode string `json:"third_party_stats_code"` // 三方统计代码
ThirdPartyStatsCode *string `json:"third_party_stats_code,omitempty"` // 三方统计代码
}
// SystemConfigResponse 系统配置响应
@@ -66,7 +66,7 @@ type SystemConfigResponse struct {
ForbiddenWords string `json:"forbidden_words"` // 违禁词列表,用逗号分隔
// 广告配置
AdKeywords string `json:"ad_keywords"` // 广告关键词列表,用逗号分隔
AdKeywords string `json:"ad_keywords"` // 广告关键词列表,用逗号分隔
AutoInsertAd string `json:"auto_insert_ad"` // 自动插入广告内容
// 其他配置

View File

@@ -5,6 +5,7 @@ import (
"sync"
"github.com/ctwj/urldb/db/entity"
"github.com/ctwj/urldb/utils"
"gorm.io/gorm"
)
@@ -21,6 +22,8 @@ type SystemConfigRepository interface {
GetConfigInt(key string) (int, error)
GetCachedConfigs() map[string]string
ClearConfigCache()
SafeRefreshConfigCache() error
ValidateConfigIntegrity() error
}
// SystemConfigRepositoryImpl 系统配置Repository实现
@@ -60,27 +63,39 @@ func (r *SystemConfigRepositoryImpl) FindByKey(key string) (*entity.SystemConfig
// UpsertConfigs 批量创建或更新配置
func (r *SystemConfigRepositoryImpl) UpsertConfigs(configs []entity.SystemConfig) error {
for _, config := range configs {
var existingConfig entity.SystemConfig
err := r.db.Where("key = ?", config.Key).First(&existingConfig).Error
// 使用事务确保数据一致性
return r.db.Transaction(func(tx *gorm.DB) error {
// 在更新前备份当前配置
var existingConfigs []entity.SystemConfig
if err := tx.Find(&existingConfigs).Error; err != nil {
utils.Error("备份配置失败: %v", err)
// 不返回错误,继续执行更新
}
if err != nil {
// 如果不存在,则创建
if err := r.db.Create(&config).Error; err != nil {
return err
}
} else {
// 如果存在,则更新
config.ID = existingConfig.ID
if err := r.db.Save(&config).Error; err != nil {
return err
for _, config := range configs {
var existingConfig entity.SystemConfig
err := tx.Where("key = ?", config.Key).First(&existingConfig).Error
if err != nil {
// 如果不存在,则创建
if err := tx.Create(&config).Error; err != nil {
utils.Error("创建配置失败 [%s]: %v", config.Key, err)
return fmt.Errorf("创建配置失败 [%s]: %v", config.Key, err)
}
} else {
// 如果存在,则更新
config.ID = existingConfig.ID
if err := tx.Save(&config).Error; err != nil {
utils.Error("更新配置失败 [%s]: %v", config.Key, err)
return fmt.Errorf("更新配置失败 [%s]: %v", config.Key, err)
}
}
}
}
// 更新配置后刷新缓存
r.refreshConfigCache()
return nil
// 更新成功后刷新缓存
r.refreshConfigCache()
return nil
})
}
// GetOrCreateDefault 获取配置或创建默认配置
@@ -92,6 +107,7 @@ func (r *SystemConfigRepositoryImpl) GetOrCreateDefault() ([]entity.SystemConfig
// 如果没有配置,创建默认配置
if len(configs) == 0 {
utils.Info("未找到任何配置,创建默认配置")
defaultConfigs := []entity.SystemConfig{
{Key: entity.ConfigKeySiteTitle, Value: entity.ConfigDefaultSiteTitle, Type: entity.ConfigTypeString},
{Key: entity.ConfigKeySiteDescription, Value: entity.ConfigDefaultSiteDescription, Type: entity.ConfigTypeString},
@@ -105,10 +121,10 @@ func (r *SystemConfigRepositoryImpl) GetOrCreateDefault() ([]entity.SystemConfig
{Key: entity.ConfigKeyAutoTransferMinSpace, Value: entity.ConfigDefaultAutoTransferMinSpace, Type: entity.ConfigTypeInt},
{Key: entity.ConfigKeyAutoFetchHotDramaEnabled, Value: entity.ConfigDefaultAutoFetchHotDramaEnabled, Type: entity.ConfigTypeBool},
{Key: entity.ConfigKeyApiToken, Value: entity.ConfigDefaultApiToken, Type: entity.ConfigTypeString},
{Key: entity.ConfigKeyPageSize, Value: entity.ConfigDefaultPageSize, Type: entity.ConfigTypeInt},
{Key: entity.ConfigKeyMaintenanceMode, Value: entity.ConfigDefaultMaintenanceMode, Type: entity.ConfigTypeBool},
{Key: entity.ConfigKeyEnableRegister, Value: entity.ConfigDefaultEnableRegister, Type: entity.ConfigTypeBool},
{Key: entity.ConfigKeyThirdPartyStatsCode, Value: entity.ConfigDefaultThirdPartyStatsCode, Type: entity.ConfigTypeString},
{Key: entity.ConfigKeyPageSize, Value: entity.ConfigDefaultPageSize, Type: entity.ConfigTypeInt},
{Key: entity.ConfigKeyMaintenanceMode, Value: entity.ConfigDefaultMaintenanceMode, Type: entity.ConfigTypeBool},
{Key: entity.ConfigKeyEnableRegister, Value: entity.ConfigDefaultEnableRegister, Type: entity.ConfigTypeBool},
{Key: entity.ConfigKeyThirdPartyStatsCode, Value: entity.ConfigDefaultThirdPartyStatsCode, Type: entity.ConfigTypeString},
}
err = r.UpsertConfigs(defaultConfigs)
@@ -208,6 +224,66 @@ func (r *SystemConfigRepositoryImpl) refreshConfigCache() {
r.initConfigCache()
}
// SafeRefreshConfigCache 安全的刷新配置缓存(带错误处理)
func (r *SystemConfigRepositoryImpl) SafeRefreshConfigCache() error {
defer func() {
if r := recover(); r != nil {
utils.Error("配置缓存刷新时发生panic: %v", r)
}
}()
r.refreshConfigCache()
return nil
}
// ValidateConfigIntegrity 验证配置完整性
func (r *SystemConfigRepositoryImpl) ValidateConfigIntegrity() error {
configs, err := r.FindAll()
if err != nil {
return fmt.Errorf("获取配置失败: %v", err)
}
// 检查关键配置是否存在
requiredKeys := []string{
entity.ConfigKeySiteTitle,
entity.ConfigKeySiteDescription,
entity.ConfigKeyKeywords,
entity.ConfigKeyAuthor,
entity.ConfigKeyCopyright,
entity.ConfigKeyAutoProcessReadyResources,
entity.ConfigKeyAutoProcessInterval,
entity.ConfigKeyAutoTransferEnabled,
entity.ConfigKeyAutoTransferLimitDays,
entity.ConfigKeyAutoTransferMinSpace,
entity.ConfigKeyAutoFetchHotDramaEnabled,
entity.ConfigKeyApiToken,
entity.ConfigKeyPageSize,
entity.ConfigKeyMaintenanceMode,
entity.ConfigKeyEnableRegister,
entity.ConfigKeyThirdPartyStatsCode,
}
existingKeys := make(map[string]bool)
for _, config := range configs {
existingKeys[config.Key] = true
}
var missingKeys []string
for _, key := range requiredKeys {
if !existingKeys[key] {
missingKeys = append(missingKeys, key)
}
}
if len(missingKeys) > 0 {
utils.Error("发现缺失的配置项: %v", missingKeys)
return fmt.Errorf("配置不完整,缺失: %v", missingKeys)
}
utils.Info("配置完整性检查通过")
return nil
}
// GetConfigValue 获取配置值(字符串)
func (r *SystemConfigRepositoryImpl) GetConfigValue(key string) (string, error) {
// 初始化缓存