Initial commit

This commit is contained in:
XOF
2025-11-20 12:12:26 +08:00
commit 179a58b55a
169 changed files with 64463 additions and 0 deletions

View File

@@ -0,0 +1,107 @@
// Filename: internal/settings/manager.go
package settings
import (
"encoding/json"
"fmt"
"gemini-balancer/internal/models"
"gemini-balancer/internal/pkg/reflectutil"
"reflect"
"time"
)
// ========= 便利的读取器 (Convenience Accessors) =========
func (sm *SettingsManager) IsIPBanEnabled() bool {
return sm.GetSettings().EnableIPBanning
}
func (sm *SettingsManager) GetMaxLoginAttempts() int {
return sm.GetSettings().MaxLoginAttempts
}
func (sm *SettingsManager) GetIPBanDuration() time.Duration {
minutes := sm.GetSettings().IPBanDurationMinutes
return time.Duration(minutes) * time.Minute
}
// GetSettingsCopy 返回当前系统设置的一个值副本,确保线程安全。
func (sm *SettingsManager) GetSettingsCopy() models.SystemSettings {
// 从 syncer 获取当前的设置指针
currentSettingsPtr := sm.GetSettings()
if currentSettingsPtr == nil {
// 在 syncer 初始化完成前,返回一个安全的默认值
return *defaultSystemSettings()
}
// 返回指针指向的值Go会自动创建一个副本
return *currentSettingsPtr
}
// ========= 辅助与调试函数 (Helpers & Debugging) =========
// [ DisplaySettings 现在接收一个参数,以便在加载后立即打印
func (sm *SettingsManager) DisplaySettings(settings *models.SystemSettings) {
if settings == nil {
sm.logger.Warn("Cannot display settings, current settings object is nil.")
return
}
sm.logger.Info("")
sm.logger.Info("========= Runtime System Settings (from SettingsManager) =========")
sm.logger.Infof(" - Request Timeout: %d seconds", settings.RequestTimeoutSeconds)
sm.logger.Infof(" - Connect Timeout: %d seconds", settings.ConnectTimeoutSeconds)
sm.logger.Infof(" - Max Retries: %d", settings.MaxRetries)
sm.logger.Infof(" - Blacklist Threshold: %d", settings.BlacklistThreshold)
sm.logger.Info("==================================================================")
sm.logger.Info("")
}
// defaultSystemSettings
func defaultSystemSettings() *models.SystemSettings {
settings := &models.SystemSettings{}
settings.CustomHeaders = make(map[string]string)
v := reflect.ValueOf(settings).Elem()
t := v.Type()
for i := 0; i < t.NumField(); i++ {
field := t.Field(i)
fieldValue := v.Field(i)
// 我们只对“简单组织”动刀
kind := fieldValue.Kind()
if kind == reflect.Int || kind == reflect.Int64 || kind == reflect.String || kind == reflect.Bool {
defaultValue := field.Tag.Get("default")
if defaultValue != "" {
if err := reflectutil.SetFieldFromString(fieldValue, defaultValue); err != nil {
panic(fmt.Sprintf("FATAL: Invalid default tag for primitive field %s ('%s'): %v", field.Name, defaultValue, err))
}
}
}
}
return settings
}
// parseAndSetField “扫描”数据库字符串并将其“开箱”到正确的Go类型中
func parseAndSetField(fieldValue reflect.Value, dbValue string) error {
if !fieldValue.CanSet() {
return fmt.Errorf("field is not settable")
}
// 如果数据库值为空我们不进行任何操作以保留defaultSystemSettings设置的默认值
if dbValue == "" {
return nil
}
kind := fieldValue.Kind()
switch kind {
case reflect.Slice, reflect.Map:
// 如果是 slice 或 map我们就用“开箱器 (json.Unmarshal)”
// fieldValue.Addr().Interface() 获取到的是指向该字段的指针正是Unmarshal所需要的
return json.Unmarshal([]byte(dbValue), fieldValue.Addr().Interface())
default:
// 对于所有原始类型,我们信任并复用现有的 reflectutil 工具
return reflectutil.SetFieldFromString(fieldValue, dbValue)
}
}
// Stop 优雅地停止SettingsManager的后台syncer。
func (sm *SettingsManager) Stop() {
if sm.syncer != nil {
sm.logger.Info("Stopping SettingsManager's syncer...")
sm.syncer.Stop()
}
}

View 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
}