Files
gpt-load/internal/config/system_settings.go

459 lines
15 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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
}