Initial commit
This commit is contained in:
240
internal/settings/settings.go
Normal file
240
internal/settings/settings.go
Normal file
@@ -0,0 +1,240 @@
|
||||
// file: gemini-balancer\internal\settings\settings.go
|
||||
package settings
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"gemini-balancer/internal/models"
|
||||
"gemini-balancer/internal/store"
|
||||
"gemini-balancer/internal/syncer"
|
||||
"reflect"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/sirupsen/logrus"
|
||||
"gorm.io/gorm"
|
||||
"gorm.io/gorm/clause"
|
||||
)
|
||||
|
||||
const SettingsUpdateChannel = "system_settings:updated"
|
||||
const DefaultGeminiEndpoint = "https://generativelanguage.googleapis.com/v1beta/models"
|
||||
|
||||
// SettingsManager [核心修正] syncer现在缓存正确的“蓝图”类型
|
||||
type SettingsManager struct {
|
||||
db *gorm.DB
|
||||
syncer *syncer.CacheSyncer[*models.SystemSettings]
|
||||
logger *logrus.Entry
|
||||
jsonToFieldType map[string]reflect.Type // 用于将JSON字段映射到Go类型
|
||||
}
|
||||
|
||||
func NewSettingsManager(db *gorm.DB, store store.Store, logger *logrus.Logger) (*SettingsManager, error) {
|
||||
sm := &SettingsManager{
|
||||
db: db,
|
||||
logger: logger.WithField("component", "SettingsManager⚙️"),
|
||||
jsonToFieldType: make(map[string]reflect.Type),
|
||||
}
|
||||
// settingsLoader 的职责:读取“砖块”,组装并返回“蓝图”
|
||||
settingsType := reflect.TypeOf(models.SystemSettings{})
|
||||
for i := 0; i < settingsType.NumField(); i++ {
|
||||
field := settingsType.Field(i)
|
||||
jsonTag := field.Tag.Get("json")
|
||||
if jsonTag != "" && jsonTag != "-" {
|
||||
sm.jsonToFieldType[jsonTag] = field.Type
|
||||
}
|
||||
}
|
||||
// settingsLoader 的职责:读取“砖块”,智能组装成“蓝图”
|
||||
settingsLoader := func() (*models.SystemSettings, error) {
|
||||
sm.logger.Info("Loading system settings from database...")
|
||||
var dbRecords []models.Setting
|
||||
if err := sm.db.Find(&dbRecords).Error; err != nil {
|
||||
return nil, fmt.Errorf("failed to load system settings from db: %w", err)
|
||||
}
|
||||
settingsMap := make(map[string]string)
|
||||
for _, record := range dbRecords {
|
||||
settingsMap[record.Key] = record.Value
|
||||
}
|
||||
// 从一个包含了所有“出厂设置”的“蓝图”开始
|
||||
settings := defaultSystemSettings()
|
||||
v := reflect.ValueOf(settings).Elem()
|
||||
t := v.Type()
|
||||
// [智能卸货]
|
||||
for i := 0; i < t.NumField(); i++ {
|
||||
field := t.Field(i)
|
||||
fieldValue := v.Field(i)
|
||||
jsonTag := field.Tag.Get("json")
|
||||
if dbValue, ok := settingsMap[jsonTag]; ok {
|
||||
|
||||
if err := parseAndSetField(fieldValue, dbValue); err != nil {
|
||||
sm.logger.Warnf("Failed to set config field '%s' from DB value '%s': %v. Using default.", field.Name, dbValue, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if settings.BaseKeyCheckEndpoint == DefaultGeminiEndpoint || settings.BaseKeyCheckEndpoint == "" {
|
||||
if settings.DefaultUpstreamURL != "" {
|
||||
// 如果全局上游URL已设置,则基于它构建新的检查端点。
|
||||
originalEndpoint := settings.BaseKeyCheckEndpoint
|
||||
derivedEndpoint := strings.TrimSuffix(settings.DefaultUpstreamURL, "/") + "/models"
|
||||
settings.BaseKeyCheckEndpoint = derivedEndpoint
|
||||
sm.logger.Infof(
|
||||
"BaseKeyCheckEndpoint is dynamically derived from DefaultUpstreamURL. Original: '%s', New: '%s'",
|
||||
originalEndpoint, derivedEndpoint,
|
||||
)
|
||||
}
|
||||
} else {
|
||||
sm.logger.Infof("BaseKeyCheckEndpoint is using a user-defined override: %s", settings.BaseKeyCheckEndpoint)
|
||||
}
|
||||
|
||||
sm.logger.Info("System settings loaded and cached.")
|
||||
sm.DisplaySettings(settings)
|
||||
return settings, nil
|
||||
}
|
||||
s, err := syncer.NewCacheSyncer(settingsLoader, store, SettingsUpdateChannel)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to create system settings syncer: %w", err)
|
||||
}
|
||||
sm.syncer = s
|
||||
go sm.ensureSettingsInitialized()
|
||||
return sm, nil
|
||||
}
|
||||
|
||||
// GetSettings [核心修正] 现在它正确地返回我们需要的“蓝图”
|
||||
func (sm *SettingsManager) GetSettings() *models.SystemSettings {
|
||||
return sm.syncer.Get()
|
||||
}
|
||||
|
||||
// UpdateSettings [核心修正] 它接收更新,并将它们转换为“砖块”存入数据库
|
||||
func (sm *SettingsManager) UpdateSettings(settingsMap map[string]interface{}) error {
|
||||
var settingsToUpdate []models.Setting
|
||||
for key, value := range settingsMap {
|
||||
fieldType, ok := sm.jsonToFieldType[key]
|
||||
if !ok {
|
||||
sm.logger.Warnf("Received update for unknown setting key '%s', ignoring.", key)
|
||||
continue
|
||||
}
|
||||
var dbValue string
|
||||
// [智能打包]
|
||||
// 如果字段是 slice 或 map,我们就将传入的 interface{} “打包”成 JSON string
|
||||
kind := fieldType.Kind()
|
||||
if kind == reflect.Slice || kind == reflect.Map {
|
||||
jsonBytes, marshalErr := json.Marshal(value)
|
||||
if marshalErr != nil {
|
||||
// [真正的错误处理] 如果打包失败,我们记录日志,并跳过这个“坏掉的集装箱”。
|
||||
sm.logger.Warnf("Failed to marshal setting '%s' to JSON: %v, skipping update.", key, marshalErr)
|
||||
continue // 跳过,继续处理下一个key
|
||||
}
|
||||
dbValue = string(jsonBytes)
|
||||
} else if kind == reflect.Bool {
|
||||
if b, ok := value.(bool); ok {
|
||||
dbValue = strconv.FormatBool(b)
|
||||
} else {
|
||||
dbValue = "false"
|
||||
}
|
||||
} else {
|
||||
dbValue = fmt.Sprintf("%v", value)
|
||||
}
|
||||
settingsToUpdate = append(settingsToUpdate, models.Setting{
|
||||
Key: key,
|
||||
Value: dbValue,
|
||||
})
|
||||
}
|
||||
if len(settingsToUpdate) > 0 {
|
||||
err := sm.db.Clauses(clause.OnConflict{
|
||||
Columns: []clause.Column{{Name: "key"}},
|
||||
DoUpdates: clause.AssignmentColumns([]string{"value"}),
|
||||
}).Create(&settingsToUpdate).Error
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to update settings in db: %w", err)
|
||||
}
|
||||
}
|
||||
return sm.syncer.Invalidate()
|
||||
}
|
||||
|
||||
// ensureSettingsInitialized [核心修正] 确保DB中有所有“砖块”的定义
|
||||
func (sm *SettingsManager) ensureSettingsInitialized() {
|
||||
defaults := defaultSystemSettings()
|
||||
v := reflect.ValueOf(defaults).Elem()
|
||||
t := v.Type()
|
||||
for i := 0; i < t.NumField(); i++ {
|
||||
field := t.Field(i)
|
||||
fieldValue := v.Field(i)
|
||||
key := field.Tag.Get("json")
|
||||
if key == "" || key == "-" {
|
||||
continue
|
||||
}
|
||||
var existing models.Setting
|
||||
if err := sm.db.Where("key = ?", key).First(&existing).Error; err == gorm.ErrRecordNotFound {
|
||||
|
||||
var defaultValue string
|
||||
kind := fieldValue.Kind()
|
||||
// [智能初始化]
|
||||
if kind == reflect.Slice || kind == reflect.Map {
|
||||
// 为复杂类型,生成一个“空的”JSON字符串,例如 "[]" 或 "{}"
|
||||
jsonBytes, _ := json.Marshal(fieldValue.Interface())
|
||||
defaultValue = string(jsonBytes)
|
||||
} else {
|
||||
defaultValue = field.Tag.Get("default")
|
||||
}
|
||||
setting := models.Setting{
|
||||
Key: key,
|
||||
Value: defaultValue,
|
||||
Name: field.Tag.Get("name"),
|
||||
Description: field.Tag.Get("desc"),
|
||||
Category: field.Tag.Get("category"),
|
||||
DefaultValue: field.Tag.Get("default"), // 元数据中的default,永远来自tag
|
||||
}
|
||||
if err := sm.db.Create(&setting).Error; err != nil {
|
||||
sm.logger.Errorf("Failed to initialize setting '%s': %v", key, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ResetAndSaveSettings [核心新增] 將所有配置重置為其在 'default' 標籤中定義的值。
|
||||
|
||||
func (sm *SettingsManager) ResetAndSaveSettings() (*models.SystemSettings, error) {
|
||||
defaults := defaultSystemSettings()
|
||||
v := reflect.ValueOf(defaults).Elem()
|
||||
t := v.Type()
|
||||
var settingsToSave []models.Setting
|
||||
for i := 0; i < t.NumField(); i++ {
|
||||
field := t.Field(i)
|
||||
fieldValue := v.Field(i)
|
||||
key := field.Tag.Get("json")
|
||||
if key == "" || key == "-" {
|
||||
continue
|
||||
}
|
||||
|
||||
var defaultValue string
|
||||
kind := fieldValue.Kind()
|
||||
// [智能重置]
|
||||
if kind == reflect.Slice || kind == reflect.Map {
|
||||
jsonBytes, _ := json.Marshal(fieldValue.Interface())
|
||||
defaultValue = string(jsonBytes)
|
||||
} else {
|
||||
defaultValue = field.Tag.Get("default")
|
||||
}
|
||||
setting := models.Setting{
|
||||
Key: key,
|
||||
Value: defaultValue,
|
||||
Name: field.Tag.Get("name"),
|
||||
Description: field.Tag.Get("desc"),
|
||||
Category: field.Tag.Get("category"),
|
||||
DefaultValue: field.Tag.Get("default"),
|
||||
}
|
||||
settingsToSave = append(settingsToSave, setting)
|
||||
}
|
||||
if len(settingsToSave) > 0 {
|
||||
err := sm.db.Clauses(clause.OnConflict{
|
||||
Columns: []clause.Column{{Name: "key"}},
|
||||
DoUpdates: clause.AssignmentColumns([]string{"value", "name", "description", "category", "default_value"}),
|
||||
}).Create(&settingsToSave).Error
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to reset settings in db: %w", err)
|
||||
}
|
||||
}
|
||||
if err := sm.syncer.Invalidate(); err != nil {
|
||||
sm.logger.Errorf("Failed to invalidate settings cache after reset: %v", err)
|
||||
}
|
||||
return defaults, nil
|
||||
}
|
||||
Reference in New Issue
Block a user