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 configs []entity.SystemConfig
var updatedKeys []string
// 字符串字段 - 处理所有字段,包括空值 // 字符串字段 - 处理被设置的字段
// 对于广告相关字段,允许空值以便清空配置 if req.SiteTitle != nil {
configs = append(configs, entity.SystemConfig{Key: entity.ConfigKeySiteTitle, Value: req.SiteTitle, Type: entity.ConfigTypeString}) 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}) updatedKeys = append(updatedKeys, entity.ConfigKeySiteTitle)
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}) if req.SiteDescription != nil {
configs = append(configs, entity.SystemConfig{Key: entity.ConfigKeyCopyright, Value: req.Copyright, Type: entity.ConfigTypeString}) configs = append(configs, entity.SystemConfig{Key: entity.ConfigKeySiteDescription, Value: *req.SiteDescription, Type: entity.ConfigTypeString})
configs = append(configs, entity.SystemConfig{Key: entity.ConfigKeySiteLogo, Value: req.SiteLogo, Type: entity.ConfigTypeString}) updatedKeys = append(updatedKeys, entity.ConfigKeySiteDescription)
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}) if req.Keywords != nil {
configs = append(configs, entity.SystemConfig{Key: entity.ConfigKeyAdKeywords, Value: req.AdKeywords, Type: entity.ConfigTypeString}) configs = append(configs, entity.SystemConfig{Key: entity.ConfigKeyKeywords, Value: *req.Keywords, Type: entity.ConfigTypeString})
configs = append(configs, entity.SystemConfig{Key: entity.ConfigKeyAutoInsertAd, Value: req.AutoInsertAd, 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 的零值机制,我们需要通过其他方式判断字段是否被提交 if req.AutoProcessReadyResources != nil {
// 这里暂时保持原样,但建议前端只提交有变化的字段 configs = append(configs, entity.SystemConfig{Key: entity.ConfigKeyAutoProcessReadyResources, Value: strconv.FormatBool(*req.AutoProcessReadyResources), Type: entity.ConfigTypeBool})
configs = append(configs, entity.SystemConfig{Key: entity.ConfigKeyAutoProcessReadyResources, Value: strconv.FormatBool(req.AutoProcessReadyResources), Type: entity.ConfigTypeBool}) updatedKeys = append(updatedKeys, entity.ConfigKeyAutoProcessReadyResources)
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}) if req.AutoTransferEnabled != nil {
configs = append(configs, entity.SystemConfig{Key: entity.ConfigKeyMaintenanceMode, Value: strconv.FormatBool(req.MaintenanceMode), 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.ConfigKeyEnableRegister, Value: strconv.FormatBool(req.EnableRegister), 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}) if req.AutoProcessInterval != nil {
configs = append(configs, entity.SystemConfig{Key: entity.ConfigKeyAutoTransferLimitDays, Value: strconv.Itoa(req.AutoTransferLimitDays), Type: entity.ConfigTypeInt}) configs = append(configs, entity.SystemConfig{Key: entity.ConfigKeyAutoProcessInterval, Value: strconv.Itoa(*req.AutoProcessInterval), Type: entity.ConfigTypeInt})
configs = append(configs, entity.SystemConfig{Key: entity.ConfigKeyAutoTransferMinSpace, Value: strconv.Itoa(req.AutoTransferMinSpace), Type: entity.ConfigTypeInt}) updatedKeys = append(updatedKeys, entity.ConfigKeyAutoProcessInterval)
configs = append(configs, entity.SystemConfig{Key: entity.ConfigKeyPageSize, Value: strconv.Itoa(req.PageSize), Type: entity.ConfigTypeInt}) }
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 return configs
} }

View File

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

View File

@@ -5,6 +5,7 @@ import (
"sync" "sync"
"github.com/ctwj/urldb/db/entity" "github.com/ctwj/urldb/db/entity"
"github.com/ctwj/urldb/utils"
"gorm.io/gorm" "gorm.io/gorm"
) )
@@ -21,6 +22,8 @@ type SystemConfigRepository interface {
GetConfigInt(key string) (int, error) GetConfigInt(key string) (int, error)
GetCachedConfigs() map[string]string GetCachedConfigs() map[string]string
ClearConfigCache() ClearConfigCache()
SafeRefreshConfigCache() error
ValidateConfigIntegrity() error
} }
// SystemConfigRepositoryImpl 系统配置Repository实现 // SystemConfigRepositoryImpl 系统配置Repository实现
@@ -60,27 +63,39 @@ func (r *SystemConfigRepositoryImpl) FindByKey(key string) (*entity.SystemConfig
// UpsertConfigs 批量创建或更新配置 // UpsertConfigs 批量创建或更新配置
func (r *SystemConfigRepositoryImpl) UpsertConfigs(configs []entity.SystemConfig) error { func (r *SystemConfigRepositoryImpl) UpsertConfigs(configs []entity.SystemConfig) error {
for _, config := range configs { // 使用事务确保数据一致性
var existingConfig entity.SystemConfig return r.db.Transaction(func(tx *gorm.DB) error {
err := r.db.Where("key = ?", config.Key).First(&existingConfig).Error // 在更新前备份当前配置
var existingConfigs []entity.SystemConfig
if err := tx.Find(&existingConfigs).Error; err != nil {
utils.Error("备份配置失败: %v", err)
// 不返回错误,继续执行更新
}
if err != nil { for _, config := range configs {
// 如果不存在,则创建 var existingConfig entity.SystemConfig
if err := r.db.Create(&config).Error; err != nil { err := tx.Where("key = ?", config.Key).First(&existingConfig).Error
return err
} if err != nil {
} else { // 如果不存在,则创建
// 如果存在,则更新 if err := tx.Create(&config).Error; err != nil {
config.ID = existingConfig.ID utils.Error("创建配置失败 [%s]: %v", config.Key, err)
if err := r.db.Save(&config).Error; err != nil { return fmt.Errorf("创建配置失败 [%s]: %v", config.Key, err)
return 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() r.refreshConfigCache()
return nil return nil
})
} }
// GetOrCreateDefault 获取配置或创建默认配置 // GetOrCreateDefault 获取配置或创建默认配置
@@ -92,6 +107,7 @@ func (r *SystemConfigRepositoryImpl) GetOrCreateDefault() ([]entity.SystemConfig
// 如果没有配置,创建默认配置 // 如果没有配置,创建默认配置
if len(configs) == 0 { if len(configs) == 0 {
utils.Info("未找到任何配置,创建默认配置")
defaultConfigs := []entity.SystemConfig{ defaultConfigs := []entity.SystemConfig{
{Key: entity.ConfigKeySiteTitle, Value: entity.ConfigDefaultSiteTitle, Type: entity.ConfigTypeString}, {Key: entity.ConfigKeySiteTitle, Value: entity.ConfigDefaultSiteTitle, Type: entity.ConfigTypeString},
{Key: entity.ConfigKeySiteDescription, Value: entity.ConfigDefaultSiteDescription, 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.ConfigKeyAutoTransferMinSpace, Value: entity.ConfigDefaultAutoTransferMinSpace, Type: entity.ConfigTypeInt},
{Key: entity.ConfigKeyAutoFetchHotDramaEnabled, Value: entity.ConfigDefaultAutoFetchHotDramaEnabled, Type: entity.ConfigTypeBool}, {Key: entity.ConfigKeyAutoFetchHotDramaEnabled, Value: entity.ConfigDefaultAutoFetchHotDramaEnabled, Type: entity.ConfigTypeBool},
{Key: entity.ConfigKeyApiToken, Value: entity.ConfigDefaultApiToken, Type: entity.ConfigTypeString}, {Key: entity.ConfigKeyApiToken, Value: entity.ConfigDefaultApiToken, Type: entity.ConfigTypeString},
{Key: entity.ConfigKeyPageSize, Value: entity.ConfigDefaultPageSize, Type: entity.ConfigTypeInt}, {Key: entity.ConfigKeyPageSize, Value: entity.ConfigDefaultPageSize, Type: entity.ConfigTypeInt},
{Key: entity.ConfigKeyMaintenanceMode, Value: entity.ConfigDefaultMaintenanceMode, Type: entity.ConfigTypeBool}, {Key: entity.ConfigKeyMaintenanceMode, Value: entity.ConfigDefaultMaintenanceMode, Type: entity.ConfigTypeBool},
{Key: entity.ConfigKeyEnableRegister, Value: entity.ConfigDefaultEnableRegister, Type: entity.ConfigTypeBool}, {Key: entity.ConfigKeyEnableRegister, Value: entity.ConfigDefaultEnableRegister, Type: entity.ConfigTypeBool},
{Key: entity.ConfigKeyThirdPartyStatsCode, Value: entity.ConfigDefaultThirdPartyStatsCode, Type: entity.ConfigTypeString}, {Key: entity.ConfigKeyThirdPartyStatsCode, Value: entity.ConfigDefaultThirdPartyStatsCode, Type: entity.ConfigTypeString},
} }
err = r.UpsertConfigs(defaultConfigs) err = r.UpsertConfigs(defaultConfigs)
@@ -208,6 +224,66 @@ func (r *SystemConfigRepositoryImpl) refreshConfigCache() {
r.initConfigCache() 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 获取配置值(字符串) // GetConfigValue 获取配置值(字符串)
func (r *SystemConfigRepositoryImpl) GetConfigValue(key string) (string, error) { func (r *SystemConfigRepositoryImpl) GetConfigValue(key string) (string, error) {
// 初始化缓存 // 初始化缓存

View File

@@ -28,6 +28,20 @@ func NewSystemConfigHandler(systemConfigRepo repo.SystemConfigRepository) *Syste
// GetConfig 获取系统配置 // GetConfig 获取系统配置
func (h *SystemConfigHandler) GetConfig(c *gin.Context) { func (h *SystemConfigHandler) GetConfig(c *gin.Context) {
// 先验证配置完整性
if err := h.systemConfigRepo.ValidateConfigIntegrity(); err != nil {
utils.Error("配置完整性检查失败: %v", err)
// 如果配置不完整,尝试重新创建默认配置
configs, err := h.systemConfigRepo.GetOrCreateDefault()
if err != nil {
ErrorResponse(c, "获取系统配置失败", http.StatusInternalServerError)
return
}
configResponse := converter.SystemConfigToResponse(configs)
SuccessResponse(c, configResponse)
return
}
configs, err := h.systemConfigRepo.GetOrCreateDefault() configs, err := h.systemConfigRepo.GetOrCreateDefault()
if err != nil { if err != nil {
ErrorResponse(c, "获取系统配置失败", http.StatusInternalServerError) ErrorResponse(c, "获取系统配置失败", http.StatusInternalServerError)
@@ -47,22 +61,22 @@ func (h *SystemConfigHandler) UpdateConfig(c *gin.Context) {
} }
// 验证参数 - 只验证提交的字段 // 验证参数 - 只验证提交的字段
if req.SiteTitle != "" && (len(req.SiteTitle) < 1 || len(req.SiteTitle) > 100) { if req.SiteTitle != nil && (len(*req.SiteTitle) < 1 || len(*req.SiteTitle) > 100) {
ErrorResponse(c, "网站标题长度必须在1-100字符之间", http.StatusBadRequest) ErrorResponse(c, "网站标题长度必须在1-100字符之间", http.StatusBadRequest)
return return
} }
if req.AutoProcessInterval > 0 && (req.AutoProcessInterval < 1 || req.AutoProcessInterval > 1440) { if req.AutoProcessInterval != nil && (*req.AutoProcessInterval < 1 || *req.AutoProcessInterval > 1440) {
ErrorResponse(c, "自动处理间隔必须在1-1440分钟之间", http.StatusBadRequest) ErrorResponse(c, "自动处理间隔必须在1-1440分钟之间", http.StatusBadRequest)
return return
} }
if req.PageSize > 0 && (req.PageSize < 10 || req.PageSize > 500) { if req.PageSize != nil && (*req.PageSize < 10 || *req.PageSize > 500) {
ErrorResponse(c, "每页显示数量必须在10-500之间", http.StatusBadRequest) ErrorResponse(c, "每页显示数量必须在10-500之间", http.StatusBadRequest)
return return
} }
if req.AutoTransferMinSpace > 0 && (req.AutoTransferMinSpace < 100 || req.AutoTransferMinSpace > 1024) { if req.AutoTransferMinSpace != nil && (*req.AutoTransferMinSpace < 100 || *req.AutoTransferMinSpace > 1024) {
ErrorResponse(c, "最小存储空间必须在100-1024GB之间", http.StatusBadRequest) ErrorResponse(c, "最小存储空间必须在100-1024GB之间", http.StatusBadRequest)
return return
} }
@@ -118,29 +132,37 @@ func UpdateSystemConfig(c *gin.Context) {
// 调试信息 // 调试信息
utils.Info("接收到的配置请求: %+v", req) utils.Info("接收到的配置请求: %+v", req)
// 获取当前配置作为备份
currentConfigs, err := repoManager.SystemConfigRepository.FindAll()
if err != nil {
utils.Error("获取当前配置失败: %v", err)
} else {
utils.Info("当前配置数量: %d", len(currentConfigs))
}
// 验证参数 - 只验证提交的字段 // 验证参数 - 只验证提交的字段
if req.SiteTitle != "" && (len(req.SiteTitle) < 1 || len(req.SiteTitle) > 100) { if req.SiteTitle != nil && (len(*req.SiteTitle) < 1 || len(*req.SiteTitle) > 100) {
ErrorResponse(c, "网站标题长度必须在1-100字符之间", http.StatusBadRequest) ErrorResponse(c, "网站标题长度必须在1-100字符之间", http.StatusBadRequest)
return return
} }
if req.AutoProcessInterval != 0 && (req.AutoProcessInterval < 1 || req.AutoProcessInterval > 1440) { if req.AutoProcessInterval != nil && (*req.AutoProcessInterval < 1 || *req.AutoProcessInterval > 1440) {
ErrorResponse(c, "自动处理间隔必须在1-1440分钟之间", http.StatusBadRequest) ErrorResponse(c, "自动处理间隔必须在1-1440分钟之间", http.StatusBadRequest)
return return
} }
if req.PageSize != 0 && (req.PageSize < 10 || req.PageSize > 500) { if req.PageSize != nil && (*req.PageSize < 10 || *req.PageSize > 500) {
ErrorResponse(c, "每页显示数量必须在10-500之间", http.StatusBadRequest) ErrorResponse(c, "每页显示数量必须在10-500之间", http.StatusBadRequest)
return return
} }
// 验证自动转存配置 // 验证自动转存配置
if req.AutoTransferLimitDays != 0 && (req.AutoTransferLimitDays < 0 || req.AutoTransferLimitDays > 365) { if req.AutoTransferLimitDays != nil && (*req.AutoTransferLimitDays < 0 || *req.AutoTransferLimitDays > 365) {
ErrorResponse(c, "自动转存限制天数必须在0-365之间", http.StatusBadRequest) ErrorResponse(c, "自动转存限制天数必须在0-365之间", http.StatusBadRequest)
return return
} }
if req.AutoTransferMinSpace != 0 && (req.AutoTransferMinSpace < 100 || req.AutoTransferMinSpace > 1024) { if req.AutoTransferMinSpace != nil && (*req.AutoTransferMinSpace < 100 || *req.AutoTransferMinSpace > 1024) {
ErrorResponse(c, "最小存储空间必须在100-1024GB之间", http.StatusBadRequest) ErrorResponse(c, "最小存储空间必须在100-1024GB之间", http.StatusBadRequest)
return return
} }
@@ -152,13 +174,24 @@ func UpdateSystemConfig(c *gin.Context) {
return return
} }
utils.Info("准备更新配置,配置项数量: %d", len(configs))
// 保存配置 // 保存配置
err := repoManager.SystemConfigRepository.UpsertConfigs(configs) err = repoManager.SystemConfigRepository.UpsertConfigs(configs)
if err != nil { if err != nil {
utils.Error("保存系统配置失败: %v", err)
ErrorResponse(c, "保存系统配置失败", http.StatusInternalServerError) ErrorResponse(c, "保存系统配置失败", http.StatusInternalServerError)
return return
} }
utils.Info("配置保存成功")
// 安全刷新系统配置缓存
if err := repoManager.SystemConfigRepository.SafeRefreshConfigCache(); err != nil {
utils.Error("刷新配置缓存失败: %v", err)
// 不返回错误,因为配置已经保存成功
}
// 刷新系统配置缓存 // 刷新系统配置缓存
pan.RefreshSystemConfigCache() pan.RefreshSystemConfigCache()
@@ -174,16 +207,30 @@ func UpdateSystemConfig(c *gin.Context) {
repoManager.CategoryRepository, repoManager.CategoryRepository,
) )
if scheduler != nil { if scheduler != nil {
scheduler.UpdateSchedulerStatusWithAutoTransfer(req.AutoFetchHotDramaEnabled, req.AutoProcessReadyResources, req.AutoTransferEnabled) // 只更新被设置的配置
var autoFetchHotDrama, autoProcessReady, autoTransfer bool
if req.AutoFetchHotDramaEnabled != nil {
autoFetchHotDrama = *req.AutoFetchHotDramaEnabled
}
if req.AutoProcessReadyResources != nil {
autoProcessReady = *req.AutoProcessReadyResources
}
if req.AutoTransferEnabled != nil {
autoTransfer = *req.AutoTransferEnabled
}
scheduler.UpdateSchedulerStatusWithAutoTransfer(autoFetchHotDrama, autoProcessReady, autoTransfer)
} }
// 返回更新后的配置 // 返回更新后的配置
updatedConfigs, err := repoManager.SystemConfigRepository.FindAll() updatedConfigs, err := repoManager.SystemConfigRepository.FindAll()
if err != nil { if err != nil {
utils.Error("获取更新后的配置失败: %v", err)
ErrorResponse(c, "获取更新后的配置失败", http.StatusInternalServerError) ErrorResponse(c, "获取更新后的配置失败", http.StatusInternalServerError)
return return
} }
utils.Info("配置更新完成,当前配置数量: %d", len(updatedConfigs))
configResponse := converter.SystemConfigToResponse(updatedConfigs) configResponse := converter.SystemConfigToResponse(updatedConfigs)
SuccessResponse(c, configResponse) SuccessResponse(c, configResponse)
} }
@@ -199,6 +246,36 @@ func GetPublicSystemConfig(c *gin.Context) {
SuccessResponse(c, configResponse) SuccessResponse(c, configResponse)
} }
// 新增:配置监控端点
func GetConfigStatus(c *gin.Context) {
// 获取配置统计信息
configs, err := repoManager.SystemConfigRepository.FindAll()
if err != nil {
ErrorResponse(c, "获取配置状态失败", http.StatusInternalServerError)
return
}
// 验证配置完整性
integrityErr := repoManager.SystemConfigRepository.ValidateConfigIntegrity()
// 获取缓存状态
cachedConfigs := repoManager.SystemConfigRepository.GetCachedConfigs()
status := map[string]interface{}{
"total_configs": len(configs),
"cached_configs": len(cachedConfigs),
"integrity_check": integrityErr == nil,
"integrity_error": "",
"last_check_time": utils.GetCurrentTimeString(),
}
if integrityErr != nil {
status["integrity_error"] = integrityErr.Error()
}
SuccessResponse(c, status)
}
// 新增:切换自动处理配置 // 新增:切换自动处理配置
func ToggleAutoProcess(c *gin.Context) { func ToggleAutoProcess(c *gin.Context) {
var req struct { var req struct {

View File

@@ -215,6 +215,7 @@ func main() {
// 系统配置路由 // 系统配置路由
api.GET("/system/config", middleware.AuthMiddleware(), middleware.AdminMiddleware(), handlers.GetSystemConfig) api.GET("/system/config", middleware.AuthMiddleware(), middleware.AdminMiddleware(), handlers.GetSystemConfig)
api.POST("/system/config", middleware.AuthMiddleware(), middleware.AdminMiddleware(), handlers.UpdateSystemConfig) api.POST("/system/config", middleware.AuthMiddleware(), middleware.AdminMiddleware(), handlers.UpdateSystemConfig)
api.GET("/system/config/status", middleware.AuthMiddleware(), middleware.AdminMiddleware(), handlers.GetConfigStatus)
api.POST("/system/config/toggle-auto-process", middleware.AuthMiddleware(), middleware.AdminMiddleware(), handlers.ToggleAutoProcess) api.POST("/system/config/toggle-auto-process", middleware.AuthMiddleware(), middleware.AdminMiddleware(), handlers.ToggleAutoProcess)
api.GET("/public/system-config", handlers.GetPublicSystemConfig) api.GET("/public/system-config", handlers.GetPublicSystemConfig)

1
web/components.d.ts vendored
View File

@@ -41,6 +41,7 @@ declare module 'vue' {
NSpace: typeof import('naive-ui')['NSpace'] NSpace: typeof import('naive-ui')['NSpace']
NSpin: typeof import('naive-ui')['NSpin'] NSpin: typeof import('naive-ui')['NSpin']
NSwitch: typeof import('naive-ui')['NSwitch'] NSwitch: typeof import('naive-ui')['NSwitch']
NTable: typeof import('naive-ui')['NTable']
NTabPane: typeof import('naive-ui')['NTabPane'] NTabPane: typeof import('naive-ui')['NTabPane']
NTabs: typeof import('naive-ui')['NTabs'] NTabs: typeof import('naive-ui')['NTabs']
NTag: typeof import('naive-ui')['NTag'] NTag: typeof import('naive-ui')['NTag']

View File

@@ -170,8 +170,9 @@ export const useSearchStatsApi = () => {
export const useSystemConfigApi = () => { export const useSystemConfigApi = () => {
const getSystemConfig = () => useApiFetch('/system/config').then(parseApiResponse) const getSystemConfig = () => useApiFetch('/system/config').then(parseApiResponse)
const updateSystemConfig = (data: any) => useApiFetch('/system/config', { method: 'POST', body: data }).then(parseApiResponse) const updateSystemConfig = (data: any) => useApiFetch('/system/config', { method: 'POST', body: data }).then(parseApiResponse)
const getConfigStatus = () => useApiFetch('/system/config/status').then(parseApiResponse)
const toggleAutoProcess = (enabled: boolean) => useApiFetch('/system/config/toggle-auto-process', { method: 'POST', body: { auto_process_ready_resources: enabled } }).then(parseApiResponse) const toggleAutoProcess = (enabled: boolean) => useApiFetch('/system/config/toggle-auto-process', { method: 'POST', body: { auto_process_ready_resources: enabled } }).then(parseApiResponse)
return { getSystemConfig, updateSystemConfig, toggleAutoProcess } return { getSystemConfig, updateSystemConfig, getConfigStatus, toggleAutoProcess }
} }
export const useHotDramaApi = () => { export const useHotDramaApi = () => {

View File

@@ -0,0 +1,307 @@
import { ref } from 'vue'
import type { Ref } from 'vue'
export interface ConfigChangeDetectionOptions {
// 是否启用自动检测
autoDetect?: boolean
// 是否在控制台输出调试信息
debug?: boolean
// 自定义比较函数
customCompare?: (key: string, currentValue: any, originalValue: any) => boolean
// 配置项映射(前端字段名 -> 后端字段名)
fieldMapping?: Record<string, string>
}
export interface ConfigSubmitOptions {
// 是否只提交改动的字段
onlyChanged?: boolean
// 是否包含所有配置项(用于后端识别)
includeAllFields?: boolean
// 自定义提交数据转换
transformSubmitData?: (data: any) => any
}
export const useConfigChangeDetection = <T extends Record<string, any>>(
options: ConfigChangeDetectionOptions = {}
) => {
const { autoDetect = true, debug = false, customCompare, fieldMapping = {} } = options
// 原始配置数据
const originalConfig = ref<T>({} as T)
// 当前配置数据
const currentConfig = ref<T>({} as T)
// 是否已初始化
const isInitialized = ref(false)
/**
* 设置原始配置数据
*/
const setOriginalConfig = (config: T) => {
originalConfig.value = { ...config }
currentConfig.value = { ...config }
isInitialized.value = true
if (debug) {
console.log('useConfigChangeDetection - 设置原始配置:', config)
}
}
/**
* 更新当前配置数据
*/
const updateCurrentConfig = (config: Partial<T>) => {
currentConfig.value = { ...currentConfig.value, ...config }
if (debug) {
console.log('useConfigChangeDetection - 更新当前配置:', config)
}
}
/**
* 检测配置改动
*/
const getChangedConfig = (): Partial<T> => {
if (!isInitialized.value) {
if (debug) {
console.warn('useConfigChangeDetection - 配置未初始化')
}
return {}
}
const changedConfig: Partial<T> = {}
// 遍历所有配置项
for (const key in currentConfig.value) {
const currentValue = currentConfig.value[key]
const originalValue = originalConfig.value[key]
// 使用自定义比较函数或默认比较
const hasChanged = customCompare
? customCompare(key, currentValue, originalValue)
: currentValue !== originalValue
if (hasChanged) {
changedConfig[key as keyof T] = currentValue
}
}
if (debug) {
console.log('useConfigChangeDetection - 检测到的改动:', changedConfig)
}
return changedConfig
}
/**
* 检查是否有改动
*/
const hasChanges = (): boolean => {
const changedConfig = getChangedConfig()
return Object.keys(changedConfig).length > 0
}
/**
* 获取改动的字段列表
*/
const getChangedFields = (): string[] => {
const changedConfig = getChangedConfig()
return Object.keys(changedConfig)
}
/**
* 获取改动的详细信息
*/
const getChangedDetails = (): Array<{
key: string
originalValue: any
currentValue: any
}> => {
if (!isInitialized.value) {
return []
}
const details: Array<{
key: string
originalValue: any
currentValue: any
}> = []
for (const key in currentConfig.value) {
const currentValue = currentConfig.value[key]
const originalValue = originalConfig.value[key]
const hasChanged = customCompare
? customCompare(key, currentValue, originalValue)
: currentValue !== originalValue
if (hasChanged) {
details.push({
key,
originalValue,
currentValue
})
}
}
return details
}
/**
* 重置为原始配置
*/
const resetToOriginal = () => {
currentConfig.value = { ...originalConfig.value }
if (debug) {
console.log('useConfigChangeDetection - 重置为原始配置')
}
}
/**
* 更新原始配置(通常在保存成功后调用)
*/
const updateOriginalConfig = () => {
originalConfig.value = { ...currentConfig.value }
if (debug) {
console.log('useConfigChangeDetection - 更新原始配置')
}
}
/**
* 获取配置快照
*/
const getSnapshot = () => {
return {
original: { ...originalConfig.value },
current: { ...currentConfig.value },
changed: getChangedConfig(),
hasChanges: hasChanges()
}
}
/**
* 准备提交数据
*/
const prepareSubmitData = (submitOptions: ConfigSubmitOptions = {}): any => {
const { onlyChanged = true, includeAllFields = true, transformSubmitData } = submitOptions
let submitData: any = {}
if (onlyChanged) {
// 只提交改动的字段
submitData = getChangedConfig()
} else {
// 提交所有字段
submitData = { ...currentConfig.value }
}
// 应用字段映射
if (Object.keys(fieldMapping).length > 0) {
const mappedData: any = {}
for (const [frontendKey, backendKey] of Object.entries(fieldMapping)) {
if (submitData[frontendKey] !== undefined) {
mappedData[backendKey] = submitData[frontendKey]
}
}
submitData = mappedData
}
// 如果包含所有字段添加未改动的字段值为undefined让后端知道这些字段存在但未改动
if (includeAllFields && onlyChanged) {
for (const key in originalConfig.value) {
if (submitData[key] === undefined) {
submitData[key] = undefined
}
}
}
// 应用自定义转换
if (transformSubmitData) {
submitData = transformSubmitData(submitData)
}
if (debug) {
console.log('useConfigChangeDetection - 准备提交数据:', submitData)
}
return submitData
}
/**
* 通用配置保存函数
*/
const saveConfig = async (
apiFunction: (data: any) => Promise<any>,
submitOptions: ConfigSubmitOptions = {},
onSuccess?: () => void,
onError?: (error: any) => void
) => {
try {
// 检测是否有改动
if (!hasChanges()) {
if (debug) {
console.log('useConfigChangeDetection - 没有检测到改动,跳过保存')
}
return { success: true, message: '没有检测到任何改动' }
}
// 准备提交数据
const submitData = prepareSubmitData(submitOptions)
if (debug) {
console.log('useConfigChangeDetection - 提交数据:', submitData)
}
// 调用API
const response = await apiFunction(submitData)
// 更新原始配置
updateOriginalConfig()
if (debug) {
console.log('useConfigChangeDetection - 保存成功')
}
// 调用成功回调
if (onSuccess) {
onSuccess()
}
return { success: true, response }
} catch (error) {
if (debug) {
console.error('useConfigChangeDetection - 保存失败:', error)
}
// 调用错误回调
if (onError) {
onError(error)
}
throw error
}
}
return {
// 响应式数据
originalConfig: originalConfig as Ref<T>,
currentConfig: currentConfig as Ref<T>,
isInitialized,
// 方法
setOriginalConfig,
updateCurrentConfig,
getChangedConfig,
hasChanges,
getChangedFields,
getChangedDetails,
resetToOriginal,
updateOriginalConfig,
getSnapshot,
prepareSubmitData,
saveConfig
}
}

View File

@@ -189,12 +189,34 @@
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import { useConfigChangeDetection } from '~/composables/useConfigChangeDetection'
// 设置页面布局 // 设置页面布局
definePageMeta({ definePageMeta({
layout: 'admin', layout: 'admin',
ssr: false ssr: false
}) })
// 定义配置表单类型
interface BotConfigForm {
api_token: string
}
// 使用配置改动检测
const {
setOriginalConfig,
updateCurrentConfig,
getChangedConfig,
hasChanges,
updateOriginalConfig,
saveConfig: saveConfigWithDetection
} = useConfigChangeDetection<BotConfigForm>({
debug: true,
fieldMapping: {
api_token: 'api_token'
}
})
const notification = useNotification() const notification = useNotification()
const activeTab = ref('qq') const activeTab = ref('qq')
@@ -215,8 +237,13 @@ const fetchApiToken = async () => {
const systemConfigApi = useSystemConfigApi() const systemConfigApi = useSystemConfigApi()
const response = await systemConfigApi.getSystemConfig() const response = await systemConfigApi.getSystemConfig()
if (response && (response as any).api_token) { if (response) {
apiToken.value = (response as any).api_token const configData = {
api_token: (response as any).api_token || ''
}
apiToken.value = configData.api_token || '未配置API Token'
setOriginalConfig(configData)
} else { } else {
apiToken.value = '未配置API Token' apiToken.value = '未配置API Token'
} }

View File

@@ -76,17 +76,39 @@
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import { useConfigChangeDetection } from '~/composables/useConfigChangeDetection'
// 设置页面布局 // 设置页面布局
definePageMeta({ definePageMeta({
layout: 'admin', layout: 'admin',
ssr: false ssr: false
}) })
// 定义配置表单类型
interface DevConfigForm {
api_token: string
}
// 使用配置改动检测
const {
setOriginalConfig,
updateCurrentConfig,
getChangedConfig,
hasChanges,
updateOriginalConfig,
saveConfig: saveConfigWithDetection
} = useConfigChangeDetection<DevConfigForm>({
debug: true,
fieldMapping: {
api_token: 'api_token'
}
})
const notification = useNotification() const notification = useNotification()
const saving = ref(false) const saving = ref(false)
// 配置表单数据 // 配置表单数据
const configForm = ref({ const configForm = ref<DevConfigForm>({
api_token: '' api_token: ''
}) })
@@ -98,9 +120,12 @@ const fetchConfig = async () => {
const response = await systemConfigApi.getSystemConfig() const response = await systemConfigApi.getSystemConfig()
if (response) { if (response) {
configForm.value = { const configData = {
api_token: response.api_token || '' api_token: (response as any).api_token || ''
} }
configForm.value = { ...configData }
setOriginalConfig(configData)
} }
} catch (error) { } catch (error) {
console.error('获取系统配置失败:', error) console.error('获取系统配置失败:', error)
@@ -116,28 +141,50 @@ const saveConfig = async () => {
try { try {
saving.value = true saving.value = true
const { useSystemConfigApi } = await import('~/composables/useApi') // 更新当前配置数据
const systemConfigApi = useSystemConfigApi() updateCurrentConfig({
await systemConfigApi.updateSystemConfig({
api_token: configForm.value.api_token api_token: configForm.value.api_token
}) })
notification.success({ const { useSystemConfigApi } = await import('~/composables/useApi')
content: '开发配置保存成功', const systemConfigApi = useSystemConfigApi()
duration: 3000
})
// 刷新系统配置状态,确保顶部导航同步更新 // 使用通用保存函数
const { useSystemConfigStore } = await import('~/stores/systemConfig') const result = await saveConfigWithDetection(
const systemConfigStore = useSystemConfigStore() systemConfigApi.updateSystemConfig,
await systemConfigStore.initConfig(true, true) // 强制刷新使用管理员API {
} catch (error) { onlyChanged: true,
console.error('保存开发配置失败:', error) includeAllFields: true
notification.error({ },
content: '保存开发配置失败', // 成功回调
duration: 3000 async () => {
}) notification.success({
content: '开发配置保存成功',
duration: 3000
})
// 刷新系统配置状态,确保顶部导航同步更新
const { useSystemConfigStore } = await import('~/stores/systemConfig')
const systemConfigStore = useSystemConfigStore()
await systemConfigStore.initConfig(true, true)
},
// 错误回调
(error) => {
console.error('保存开发配置失败:', error)
notification.error({
content: '保存开发配置失败',
duration: 3000
})
}
)
// 如果没有改动,显示提示
if (result && result.message === '没有检测到任何改动') {
notification.info({
content: '没有检测到任何改动',
duration: 3000
})
}
} finally { } finally {
saving.value = false saving.value = false
} }

View File

@@ -165,18 +165,55 @@
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import { ref, onMounted } from 'vue'
import { useNotification } from 'naive-ui'
import { useConfigChangeDetection } from '~/composables/useConfigChangeDetection'
// 设置页面布局 // 设置页面布局
definePageMeta({ definePageMeta({
layout: 'admin', layout: 'admin',
ssr: false ssr: false
}) })
// 配置表单数据类型
interface FeatureConfigForm {
auto_process_enabled: boolean
auto_process_interval: string
auto_transfer_enabled: boolean
auto_transfer_min_space: string
ad_keywords: string
auto_insert_ad: string
hot_drama_auto_fetch: boolean
}
// 使用配置改动检测
const {
setOriginalConfig,
updateCurrentConfig,
getChangedConfig,
hasChanges,
updateOriginalConfig,
saveConfig: saveConfigWithDetection
} = useConfigChangeDetection<FeatureConfigForm>({
debug: true,
// 字段映射:前端字段名 -> 后端字段名
fieldMapping: {
auto_process_enabled: 'auto_process_ready_resources',
auto_process_interval: 'auto_process_interval',
auto_transfer_enabled: 'auto_transfer_enabled',
auto_transfer_min_space: 'auto_transfer_min_space',
ad_keywords: 'ad_keywords',
auto_insert_ad: 'auto_insert_ad',
hot_drama_auto_fetch: 'auto_fetch_hot_drama_enabled'
}
})
const notification = useNotification() const notification = useNotification()
const saving = ref(false) const saving = ref(false)
const activeTab = ref('resource') const activeTab = ref('resource')
// 配置表单数据 // 配置表单数据
const configForm = ref({ const configForm = ref<FeatureConfigForm>({
auto_process_enabled: false, auto_process_enabled: false,
auto_process_interval: '30', auto_process_interval: '30',
auto_transfer_enabled: false, auto_transfer_enabled: false,
@@ -197,7 +234,7 @@ const fetchConfig = async () => {
const response = await systemConfigApi.getSystemConfig() as any const response = await systemConfigApi.getSystemConfig() as any
if (response) { if (response) {
configForm.value = { const configData = {
auto_process_enabled: response.auto_process_ready_resources || false, auto_process_enabled: response.auto_process_ready_resources || false,
auto_process_interval: String(response.auto_process_interval || 30), auto_process_interval: String(response.auto_process_interval || 30),
auto_transfer_enabled: response.auto_transfer_enabled || false, auto_transfer_enabled: response.auto_transfer_enabled || false,
@@ -206,6 +243,9 @@ const fetchConfig = async () => {
auto_insert_ad: response.auto_insert_ad || '', auto_insert_ad: response.auto_insert_ad || '',
hot_drama_auto_fetch: response.auto_fetch_hot_drama_enabled || false hot_drama_auto_fetch: response.auto_fetch_hot_drama_enabled || false
} }
configForm.value = { ...configData }
setOriginalConfig(configData)
} }
} catch (error) { } catch (error) {
console.error('获取系统配置失败:', error) console.error('获取系统配置失败:', error)
@@ -221,34 +261,67 @@ const saveConfig = async () => {
try { try {
saving.value = true saving.value = true
// 更新当前配置数据
updateCurrentConfig({
auto_process_enabled: configForm.value.auto_process_enabled,
auto_process_interval: configForm.value.auto_process_interval,
auto_transfer_enabled: configForm.value.auto_transfer_enabled,
auto_transfer_min_space: configForm.value.auto_transfer_min_space,
ad_keywords: configForm.value.ad_keywords,
auto_insert_ad: configForm.value.auto_insert_ad,
hot_drama_auto_fetch: configForm.value.hot_drama_auto_fetch
})
const { useSystemConfigApi } = await import('~/composables/useApi') const { useSystemConfigApi } = await import('~/composables/useApi')
const systemConfigApi = useSystemConfigApi() const systemConfigApi = useSystemConfigApi()
await systemConfigApi.updateSystemConfig({ // 使用通用保存函数
auto_process_ready_resources: configForm.value.auto_process_enabled, const result = await saveConfigWithDetection(
auto_process_interval: parseInt(configForm.value.auto_process_interval) || 30, systemConfigApi.updateSystemConfig,
auto_transfer_enabled: configForm.value.auto_transfer_enabled, {
auto_transfer_min_space: parseInt(configForm.value.auto_transfer_min_space) || 500, onlyChanged: true,
ad_keywords: configForm.value.ad_keywords, includeAllFields: true,
auto_insert_ad: configForm.value.auto_insert_ad, // 自定义数据转换
auto_fetch_hot_drama_enabled: configForm.value.hot_drama_auto_fetch transformSubmitData: (data) => {
}) // 转换字符串为数字
if (data.auto_process_interval !== undefined) {
data.auto_process_interval = parseInt(data.auto_process_interval) || 30
}
if (data.auto_transfer_min_space !== undefined) {
data.auto_transfer_min_space = parseInt(data.auto_transfer_min_space) || 500
}
return data
}
},
// 成功回调
async () => {
notification.success({
content: '功能配置保存成功',
duration: 3000
})
notification.success({ // 刷新系统配置状态,确保顶部导航同步更新
content: '功能配置保存成功', const { useSystemConfigStore } = await import('~/stores/systemConfig')
duration: 3000 const systemConfigStore = useSystemConfigStore()
}) await systemConfigStore.initConfig(true, true)
},
// 错误回调
(error) => {
console.error('保存功能配置失败:', error)
notification.error({
content: '保存功能配置失败',
duration: 3000
})
}
)
// 刷新系统配置状态,确保顶部导航同步更新 // 如果没有改动,显示提示
const { useSystemConfigStore } = await import('~/stores/systemConfig') if (result && result.message === '没有检测到任何改动') {
const systemConfigStore = useSystemConfigStore() notification.info({
await systemConfigStore.initConfig(true, true) // 强制刷新使用管理员API content: '没有检测到任何改动',
} catch (error) { duration: 3000
console.error('保存功能配置失败:', error) })
notification.error({ }
content: '保存功能配置失败',
duration: 3000
})
} finally { } finally {
saving.value = false saving.value = false
} }

View File

@@ -282,6 +282,7 @@ definePageMeta({
import { useImageUrl } from '~/composables/useImageUrl' import { useImageUrl } from '~/composables/useImageUrl'
import { useConfigChangeDetection } from '~/composables/useConfigChangeDetection'
const notification = useNotification() const notification = useNotification()
const { getImageUrl } = useImageUrl() const { getImageUrl } = useImageUrl()
@@ -304,8 +305,8 @@ const pagination = ref({
pageSizes: [10, 20, 50, 100] pageSizes: [10, 20, 50, 100]
}) })
// 配置表单数据 // 配置表单数据类型
const configForm = ref<{ interface SiteConfigForm {
site_title: string site_title: string
site_description: string site_description: string
keywords: string keywords: string
@@ -316,14 +317,43 @@ const configForm = ref<{
forbidden_words: string forbidden_words: string
enable_sitemap: boolean enable_sitemap: boolean
sitemap_update_frequency: string sitemap_update_frequency: string
}>({ }
// 使用配置改动检测
const {
setOriginalConfig,
updateCurrentConfig,
getChangedConfig,
hasChanges,
getChangedDetails,
updateOriginalConfig,
saveConfig: saveConfigWithDetection
} = useConfigChangeDetection<SiteConfigForm>({
debug: true,
// 字段映射:前端字段名 -> 后端字段名
fieldMapping: {
site_title: 'site_title',
site_description: 'site_description',
keywords: 'keywords',
copyright: 'copyright',
site_logo: 'site_logo',
maintenance_mode: 'maintenance_mode',
enable_register: 'enable_register',
forbidden_words: 'forbidden_words',
enable_sitemap: 'enable_sitemap',
sitemap_update_frequency: 'sitemap_update_frequency'
}
})
// 配置表单数据
const configForm = ref<SiteConfigForm>({
site_title: '', site_title: '',
site_description: '', site_description: '',
keywords: '', keywords: '',
copyright: '', copyright: '',
site_logo: '', site_logo: '',
maintenance_mode: false, maintenance_mode: false,
enable_register: false, // 新增:开启注册开关 enable_register: false,
forbidden_words: '', forbidden_words: '',
enable_sitemap: false, enable_sitemap: false,
sitemap_update_frequency: 'daily' sitemap_update_frequency: 'daily'
@@ -353,18 +383,22 @@ const fetchConfig = async () => {
const response = await systemConfigApi.getSystemConfig() as any const response = await systemConfigApi.getSystemConfig() as any
if (response) { if (response) {
configForm.value = { const configData = {
site_title: response.site_title || '', site_title: response.site_title || '',
site_description: response.site_description || '', site_description: response.site_description || '',
keywords: response.keywords || '', keywords: response.keywords || '',
copyright: response.copyright || '', copyright: response.copyright || '',
site_logo: response.site_logo || '', site_logo: response.site_logo || '',
maintenance_mode: response.maintenance_mode || false, maintenance_mode: response.maintenance_mode || false,
enable_register: response.enable_register || false, // 新增:获取开启注册开关 enable_register: response.enable_register || false,
forbidden_words: response.forbidden_words || '', forbidden_words: response.forbidden_words || '',
enable_sitemap: response.enable_sitemap || false, enable_sitemap: response.enable_sitemap || false,
sitemap_update_frequency: response.sitemap_update_frequency || 'daily' sitemap_update_frequency: response.sitemap_update_frequency || 'daily'
} }
// 设置表单数据和原始数据
configForm.value = { ...configData }
setOriginalConfig(configData)
} }
} catch (error) { } catch (error) {
console.error('获取系统配置失败:', error) console.error('获取系统配置失败:', error)
@@ -375,43 +409,68 @@ const fetchConfig = async () => {
} }
} }
// 保存配置 // 保存配置
const saveConfig = async () => { const saveConfig = async () => {
try { try {
await formRef.value?.validate() await formRef.value?.validate()
saving.value = true saving.value = true
const { useSystemConfigApi } = await import('~/composables/useApi') // 更新当前配置数据
const systemConfigApi = useSystemConfigApi() updateCurrentConfig({
await systemConfigApi.updateSystemConfig({
site_title: configForm.value.site_title, site_title: configForm.value.site_title,
site_description: configForm.value.site_description, site_description: configForm.value.site_description,
keywords: configForm.value.keywords, keywords: configForm.value.keywords,
copyright: configForm.value.copyright, copyright: configForm.value.copyright,
site_logo: configForm.value.site_logo, site_logo: configForm.value.site_logo,
maintenance_mode: configForm.value.maintenance_mode, maintenance_mode: configForm.value.maintenance_mode,
enable_register: configForm.value.enable_register, // 新增:保存开启注册开关 enable_register: configForm.value.enable_register,
forbidden_words: configForm.value.forbidden_words, forbidden_words: configForm.value.forbidden_words,
enable_sitemap: configForm.value.enable_sitemap, enable_sitemap: configForm.value.enable_sitemap,
sitemap_update_frequency: configForm.value.sitemap_update_frequency sitemap_update_frequency: configForm.value.sitemap_update_frequency
}) })
notification.success({ const { useSystemConfigApi } = await import('~/composables/useApi')
content: '站点配置保存成功', const systemConfigApi = useSystemConfigApi()
duration: 3000
})
// 刷新系统配置状态,确保顶部导航同步更新 // 使用通用保存函数
const { useSystemConfigStore } = await import('~/stores/systemConfig') const result = await saveConfigWithDetection(
const systemConfigStore = useSystemConfigStore() systemConfigApi.updateSystemConfig,
await systemConfigStore.initConfig(true, true) // 强制刷新使用管理员API {
} catch (error) { onlyChanged: true,
console.error('保存站点配置失败:', error) includeAllFields: true
notification.error({ },
content: '保存站点配置失败', // 成功回调
duration: 3000 async () => {
}) notification.success({
content: '站点配置保存成功',
duration: 3000
})
// 刷新系统配置状态,确保顶部导航同步更新
const { useSystemConfigStore } = await import('~/stores/systemConfig')
const systemConfigStore = useSystemConfigStore()
await systemConfigStore.initConfig(true, true)
},
// 错误回调
(error) => {
console.error('保存站点配置失败:', error)
notification.error({
content: '保存站点配置失败',
duration: 3000
})
}
)
// 如果没有改动,显示提示
if (result && result.message === '没有检测到任何改动') {
notification.info({
content: '没有检测到任何改动',
duration: 3000
})
}
} finally { } finally {
saving.value = false saving.value = false
} }

View File

@@ -320,12 +320,33 @@
<script setup lang="ts"> <script setup lang="ts">
import { ref, onMounted } from 'vue' import { ref, onMounted } from 'vue'
import { useMessage } from 'naive-ui' import { useMessage } from 'naive-ui'
import { useConfigChangeDetection } from '~/composables/useConfigChangeDetection'
// 页面配置 // 页面配置
definePageMeta({ definePageMeta({
layout: 'admin' layout: 'admin'
}) })
// 定义配置表单类型
interface ThirdPartyStatsForm {
third_party_stats_code: string
}
// 使用配置改动检测
const {
setOriginalConfig,
updateCurrentConfig,
getChangedConfig,
hasChanges,
updateOriginalConfig,
saveConfig: saveConfigWithDetection
} = useConfigChangeDetection<ThirdPartyStatsForm>({
debug: true,
fieldMapping: {
third_party_stats_code: 'third_party_stats_code'
}
})
// 状态管理 // 状态管理
const message = useMessage() const message = useMessage()
const statsCode = ref('') const statsCode = ref('')
@@ -338,8 +359,13 @@ const fetchConfig = async () => {
const systemConfigApi = useSystemConfigApi() const systemConfigApi = useSystemConfigApi()
const response = await systemConfigApi.getSystemConfig() const response = await systemConfigApi.getSystemConfig()
if (response && response.third_party_stats_code) { if (response) {
statsCode.value = response.third_party_stats_code const configData = {
third_party_stats_code: (response as any).third_party_stats_code || ''
}
statsCode.value = configData.third_party_stats_code
setOriginalConfig(configData)
} }
} catch (error) { } catch (error) {
console.error('获取配置失败:', error) console.error('获取配置失败:', error)
@@ -349,18 +375,39 @@ const fetchConfig = async () => {
// 保存配置 // 保存配置
const saveCode = async () => { const saveCode = async () => {
saving.value = true
try { try {
const { useSystemConfigApi } = await import('~/composables/useApi') saving.value = true
const systemConfigApi = useSystemConfigApi()
await systemConfigApi.updateSystemConfig({ // 更新当前配置
updateCurrentConfig({
third_party_stats_code: statsCode.value third_party_stats_code: statsCode.value
}) })
message.success('配置保存成功') const { useSystemConfigApi } = await import('~/composables/useApi')
} catch (error) { const systemConfigApi = useSystemConfigApi()
console.error('保存配置失败:', error)
message.error('保存配置失败') // 使用通用保存函数
const result = await saveConfigWithDetection(
systemConfigApi.updateSystemConfig,
{
onlyChanged: true,
includeAllFields: true
},
// 成功回调
() => {
message.success('配置保存成功')
},
// 错误回调
(error) => {
console.error('保存配置失败:', error)
message.error('保存配置失败')
}
)
// 如果没有改动,显示提示
if (result && result.message === '没有检测到任何改动') {
message.info('没有检测到任何改动')
}
} finally { } finally {
saving.value = false saving.value = false
} }