package config import ( "fmt" "gpt-load/internal/db" "gpt-load/internal/models" "gpt-load/internal/store" "gpt-load/internal/syncer" "os" "reflect" "strconv" "strings" "github.com/sirupsen/logrus" "gorm.io/datatypes" "gorm.io/gorm/clause" ) // SystemSettings 定义所有系统配置项 // 使用结构体标签作为唯一事实来源 type SystemSettings struct { // 基础参数 AppUrl string `json:"app_url" default:"" name:"项目地址" category:"基础参数" desc:"项目的基础 URL,用于拼接分组终端节点地址。系统配置优先于环境变量 APP_URL。"` RequestLogRetentionDays int `json:"request_log_retention_days" default:"30" name:"日志保留天数" category:"基础参数" desc:"请求日志在数据库中的保留天数" validate:"min=1"` // 服务超时 ServerReadTimeout int `json:"server_read_timeout" default:"120" name:"读取超时" category:"服务超时" desc:"HTTP 服务器读取超时时间(秒)" validate:"min=1"` ServerWriteTimeout int `json:"server_write_timeout" default:"1800" name:"写入超时" category:"服务超时" desc:"HTTP 服务器写入超时时间(秒)" validate:"min=1"` ServerIdleTimeout int `json:"server_idle_timeout" default:"120" name:"空闲超时" category:"服务超时" desc:"HTTP 服务器空闲超时时间(秒)" validate:"min=1"` ServerGracefulShutdownTimeout int `json:"server_graceful_shutdown_timeout" default:"60" name:"优雅关闭超时" category:"服务超时" desc:"服务优雅关闭的等待超时时间(秒)" validate:"min=1"` // 请求超时 RequestTimeout int `json:"request_timeout" default:"30" name:"请求超时" category:"请求超时" desc:"请求处理的总体超时时间(秒)" validate:"min=1"` ResponseTimeout int `json:"response_timeout" default:"30" name:"响应超时" category:"请求超时" desc:"TLS 握手和响应头的超时时间(秒)" validate:"min=1"` IdleConnTimeout int `json:"idle_conn_timeout" default:"120" name:"空闲连接超时" category:"请求超时" desc:"空闲连接的超时时间(秒)" validate:"min=1"` // 密钥配置 MaxRetries int `json:"max_retries" default:"3" name:"最大重试次数" category:"密钥配置" desc:"单个请求使用不同 Key 的最大重试次数" validate:"min=0"` BlacklistThreshold int `json:"blacklist_threshold" default:"1" name:"黑名单阈值" category:"密钥配置" desc:"一个 Key 连续失败多少次后进入黑名单" validate:"min=0"` KeyValidationIntervalMinutes int `json:"key_validation_interval_minutes" default:"60" name:"定时验证周期" category:"密钥配置" desc:"后台定时验证密钥的默认周期(分钟)" validate:"min=5"` KeyValidationTaskTimeoutMinutes int `json:"key_validation_task_timeout_minutes" default:"60" name:"手动验证超时" category:"密钥配置" desc:"手动触发的全量验证任务的超时时间(分钟)" validate:"min=10"` } // GenerateSettingsMetadata 使用反射从 SystemSettings 结构体动态生成元数据 func GenerateSettingsMetadata(s *SystemSettings) []models.SystemSettingInfo { var settingsInfo []models.SystemSettingInfo v := reflect.ValueOf(s).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 jsonTag == "" { continue } nameTag := field.Tag.Get("name") descTag := field.Tag.Get("desc") defaultTag := field.Tag.Get("default") validateTag := field.Tag.Get("validate") categoryTag := field.Tag.Get("category") var minValue *int if strings.HasPrefix(validateTag, "min=") { valStr := strings.TrimPrefix(validateTag, "min=") if val, err := strconv.Atoi(valStr); err == nil { minValue = &val } } info := models.SystemSettingInfo{ Key: jsonTag, Name: nameTag, Value: fieldValue.Interface(), Type: field.Type.String(), DefaultValue: defaultTag, Description: descTag, Category: categoryTag, MinValue: minValue, } settingsInfo = append(settingsInfo, info) } return settingsInfo } // DefaultSystemSettings 返回默认的系统配置 func DefaultSystemSettings() SystemSettings { s := SystemSettings{} v := reflect.ValueOf(&s).Elem() t := v.Type() for i := 0; i < t.NumField(); i++ { field := t.Field(i) defaultTag := field.Tag.Get("default") if defaultTag == "" { continue } fieldValue := v.Field(i) if fieldValue.CanSet() { if err := setFieldFromString(fieldValue, defaultTag); err != nil { logrus.Warnf("Failed to set default value for field %s: %v", field.Name, err) } } } return s } // SystemSettingsManager 管理系统配置 type SystemSettingsManager struct { syncer *syncer.CacheSyncer[SystemSettings] } const SettingsUpdateChannel = "system_settings:updated" // NewSystemSettingsManager creates a new, uninitialized SystemSettingsManager. func NewSystemSettingsManager() (*SystemSettingsManager, error) { return &SystemSettingsManager{}, nil } // Initialize initializes the SystemSettingsManager with database and store dependencies. func (sm *SystemSettingsManager) Initialize(store store.Store) error { settingsLoader := func() (SystemSettings, error) { var dbSettings []models.SystemSetting if err := db.DB.Find(&dbSettings).Error; err != nil { return SystemSettings{}, fmt.Errorf("failed to load system settings from db: %w", err) } settingsMap := make(map[string]string) for _, setting := range dbSettings { settingsMap[setting.SettingKey] = setting.SettingValue } // Start with default settings, then override with values from the database. settings := DefaultSystemSettings() v := reflect.ValueOf(&settings).Elem() t := v.Type() jsonToField := make(map[string]string) for i := 0; i < t.NumField(); i++ { field := t.Field(i) jsonTag := strings.Split(field.Tag.Get("json"), ",")[0] if jsonTag != "" { jsonToField[jsonTag] = field.Name } } for key, valStr := range settingsMap { if fieldName, ok := jsonToField[key]; ok { fieldValue := v.FieldByName(fieldName) if fieldValue.IsValid() && fieldValue.CanSet() { if err := setFieldFromString(fieldValue, valStr); err != nil { logrus.Warnf("Failed to set value from map for field %s: %v", fieldName, err) } } } } return settings, nil } syncer, err := syncer.NewCacheSyncer( settingsLoader, store, SettingsUpdateChannel, logrus.WithField("syncer", "system_settings"), ) if err != nil { return fmt.Errorf("failed to create system settings syncer: %w", err) } sm.syncer = syncer return nil } // Stop gracefully stops the SystemSettingsManager's background syncer. func (sm *SystemSettingsManager) Stop() { if sm.syncer != nil { sm.syncer.Stop() } } // EnsureSettingsInitialized 确保数据库中存在所有系统设置的记录。 func (sm *SystemSettingsManager) EnsureSettingsInitialized() error { defaultSettings := DefaultSystemSettings() metadata := GenerateSettingsMetadata(&defaultSettings) for _, meta := range metadata { var existing models.SystemSetting err := db.DB.Where("setting_key = ?", meta.Key).First(&existing).Error if err != nil { value := fmt.Sprintf("%v", meta.DefaultValue) if meta.Key == "app_url" { // Special handling for app_url initialization if appURL := os.Getenv("APP_URL"); appURL != "" { value = appURL } else { host := os.Getenv("HOST") if host == "" || host == "0.0.0.0" { host = "localhost" } port := os.Getenv("PORT") if port == "" { port = "3000" } value = fmt.Sprintf("http://%s:%s", host, port) } } setting := models.SystemSetting{ SettingKey: meta.Key, SettingValue: value, Description: meta.Description, } if err := db.DB.Create(&setting).Error; err != nil { logrus.Errorf("Failed to initialize setting %s: %v", setting.SettingKey, err) return err } logrus.Infof("Initialized system setting: %s = %s", setting.SettingKey, setting.SettingValue) } } return nil } // GetSettings 获取当前系统配置 // If the syncer is not initialized, it returns default settings. func (sm *SystemSettingsManager) GetSettings() SystemSettings { if sm.syncer == nil { logrus.Warn("SystemSettingsManager is not initialized, returning default settings.") return DefaultSystemSettings() } return sm.syncer.Get() } // GetAppUrl returns the effective App URL. // It prioritizes the value from system settings (database) over the APP_URL environment variable. func (sm *SystemSettingsManager) GetAppUrl() string { // 1. 优先级: 数据库中的系统配置 settings := sm.GetSettings() if settings.AppUrl != "" { return settings.AppUrl } // 2. 回退: 环境变量 return os.Getenv("APP_URL") } // UpdateSettings 更新系统配置 func (sm *SystemSettingsManager) UpdateSettings(settingsMap map[string]any) error { // 验证配置项 if err := sm.ValidateSettings(settingsMap); err != nil { return err } // 更新数据库 var settingsToUpdate []models.SystemSetting for key, value := range settingsMap { settingsToUpdate = append(settingsToUpdate, models.SystemSetting{ SettingKey: key, SettingValue: fmt.Sprintf("%v", value), // Convert any to string }) } if len(settingsToUpdate) > 0 { if err := db.DB.Clauses(clause.OnConflict{ Columns: []clause.Column{{Name: "setting_key"}}, DoUpdates: clause.AssignmentColumns([]string{"setting_value", "updated_at"}), }).Create(&settingsToUpdate).Error; err != nil { return fmt.Errorf("failed to update system settings: %w", err) } } // 触发所有实例重新加载 return sm.syncer.Invalidate() } // GetEffectiveConfig 获取有效配置 (系统配置 + 分组覆盖) func (sm *SystemSettingsManager) GetEffectiveConfig(groupConfig datatypes.JSONMap) SystemSettings { // 从系统配置开始 effectiveConfig := sm.GetSettings() v := reflect.ValueOf(&effectiveConfig).Elem() t := v.Type() // 创建一个从 json 标签到字段名的映射 jsonToField := make(map[string]string) for i := 0; i < t.NumField(); i++ { field := t.Field(i) jsonTag := strings.Split(field.Tag.Get("json"), ",")[0] if jsonTag != "" { jsonToField[jsonTag] = field.Name } } // 应用分组配置覆盖 for key, val := range groupConfig { if fieldName, ok := jsonToField[key]; ok { fieldValue := v.FieldByName(fieldName) if fieldValue.IsValid() && fieldValue.CanSet() { switch fieldValue.Kind() { case reflect.Int: if intVal, err := interfaceToInt(val); err == nil { fieldValue.SetInt(int64(intVal)) } case reflect.String: if strVal, ok := interfaceToString(val); ok { fieldValue.SetString(strVal) } case reflect.Bool: if boolVal, ok := interfaceToBool(val); ok { fieldValue.SetBool(boolVal) } } } } } return effectiveConfig } // ValidateSettings 验证系统配置的有效性 func (sm *SystemSettingsManager) ValidateSettings(settingsMap map[string]any) error { tempSettings := DefaultSystemSettings() v := reflect.ValueOf(&tempSettings).Elem() t := v.Type() jsonToField := make(map[string]reflect.StructField) for i := 0; i < t.NumField(); i++ { field := t.Field(i) jsonTag := field.Tag.Get("json") if jsonTag != "" { jsonToField[jsonTag] = field } } for key, value := range settingsMap { field, ok := jsonToField[key] if !ok { return fmt.Errorf("invalid setting key: %s", key) } validateTag := field.Tag.Get("validate") switch field.Type.Kind() { case reflect.Int: // JSON numbers are decoded as float64 floatVal, ok := value.(float64) if !ok { return fmt.Errorf("invalid type for %s: expected a number, got %T", key, value) } intVal := int(floatVal) if floatVal != float64(intVal) { return fmt.Errorf("invalid value for %s: must be an integer", key) } if strings.HasPrefix(validateTag, "min=") { minValStr := strings.TrimPrefix(validateTag, "min=") minVal, _ := strconv.Atoi(minValStr) if intVal < minVal { return fmt.Errorf("value for %s (%d) is below minimum value (%d)", key, intVal, minVal) } } case reflect.Bool: if _, ok := value.(bool); !ok { return fmt.Errorf("invalid type for %s: expected a boolean, got %T", key, value) } case reflect.String: if _, ok := value.(string); !ok { return fmt.Errorf("invalid type for %s: expected a string, got %T", key, value) } default: return fmt.Errorf("unsupported type for setting key validation: %s", key) } } return nil } // DisplayCurrentSettings 显示当前系统配置信息 func (sm *SystemSettingsManager) DisplayCurrentSettings() { settings := sm.GetSettings() logrus.Info("Current System Settings:") logrus.Infof(" App URL: %s", settings.AppUrl) logrus.Infof(" Blacklist threshold: %d", settings.BlacklistThreshold) logrus.Infof(" Max retries: %d", settings.MaxRetries) logrus.Infof(" Server timeouts: read=%ds, write=%ds, idle=%ds, shutdown=%ds", settings.ServerReadTimeout, settings.ServerWriteTimeout, settings.ServerIdleTimeout, settings.ServerGracefulShutdownTimeout) logrus.Infof(" Request timeouts: request=%ds, response=%ds, idle_conn=%ds", settings.RequestTimeout, settings.ResponseTimeout, settings.IdleConnTimeout) logrus.Infof(" Request log retention: %d days", settings.RequestLogRetentionDays) logrus.Infof(" Key validation: interval=%dmin, task_timeout=%dmin", settings.KeyValidationIntervalMinutes, settings.KeyValidationTaskTimeoutMinutes) } // setFieldFromString sets a struct field's value from a string, based on the field's kind. func setFieldFromString(fieldValue reflect.Value, value string) error { if !fieldValue.CanSet() { return fmt.Errorf("field cannot be set") } switch fieldValue.Kind() { case reflect.Int: intVal, err := strconv.ParseInt(value, 10, 64) if err != nil { return fmt.Errorf("invalid integer value '%s': %w", value, err) } fieldValue.SetInt(int64(intVal)) case reflect.Bool: boolVal, err := strconv.ParseBool(value) if err != nil { return fmt.Errorf("invalid boolean value '%s': %w", value, err) } fieldValue.SetBool(boolVal) case reflect.String: fieldValue.SetString(value) default: return fmt.Errorf("unsupported field kind: %s", fieldValue.Kind()) } return nil } // 工具函数 func interfaceToInt(val any) (int, error) { switch v := val.(type) { case int: return v, nil case float64: // JSON unmarshals numbers into float64 if v != float64(int(v)) { return 0, fmt.Errorf("value is a float, not an integer: %v", v) } return int(v), nil case string: return strconv.Atoi(v) default: return 0, fmt.Errorf("cannot convert %T to int", v) } } // interfaceToString is kept for GetEffectiveConfig func interfaceToString(val any) (string, bool) { s, ok := val.(string) return s, ok } // interfaceToBool is kept for GetEffectiveConfig func interfaceToBool(val any) (bool, bool) { switch v := val.(type) { case bool: return v, true case string: b, err := strconv.ParseBool(v) if err == nil { return b, true } } return false, false }