From 0bd6a9289ce3a0a22c3a27b139357228b31b1dc5 Mon Sep 17 00:00:00 2001 From: tbphp Date: Thu, 3 Jul 2025 17:53:56 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E9=85=8D=E7=BD=AE=E4=BC=98=E5=8C=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- internal/channel/factory.go | 24 +++++++-- internal/channel/gemini_channel.go | 5 +- internal/channel/openai_channel.go | 5 +- internal/config/manager.go | 78 ++++++------------------------ internal/config/system_settings.go | 39 +++++++++++++-- internal/types/types.go | 8 --- 6 files changed, 77 insertions(+), 82 deletions(-) diff --git a/internal/channel/factory.go b/internal/channel/factory.go index 611149a..892838c 100644 --- a/internal/channel/factory.go +++ b/internal/channel/factory.go @@ -2,25 +2,29 @@ package channel import ( "fmt" + "gpt-load/internal/config" "gpt-load/internal/models" "net/http" "net/url" + "time" + + "gorm.io/datatypes" ) // GetChannel returns a channel proxy based on the group's channel type. func GetChannel(group *models.Group) (ChannelProxy, error) { switch group.ChannelType { case "openai": - return NewOpenAIChannel(group.Upstreams) + return NewOpenAIChannel(group.Upstreams, group.Config) case "gemini": - return NewGeminiChannel(group.Upstreams) + return NewGeminiChannel(group.Upstreams, group.Config) default: return nil, fmt.Errorf("unsupported channel type: %s", group.ChannelType) } } // newBaseChannelWithUpstreams is a helper function to create and configure a BaseChannel. -func newBaseChannelWithUpstreams(name string, upstreams []string) (BaseChannel, error) { +func newBaseChannelWithUpstreams(name string, upstreams []string, groupConfig datatypes.JSONMap) (BaseChannel, error) { if len(upstreams) == 0 { return BaseChannel{}, fmt.Errorf("at least one upstream is required for %s channel", name) } @@ -34,9 +38,21 @@ func newBaseChannelWithUpstreams(name string, upstreams []string) (BaseChannel, upstreamURLs = append(upstreamURLs, u) } + // Get effective settings by merging system and group configs + settingsManager := config.GetSystemSettingsManager() + effectiveSettings := settingsManager.GetEffectiveConfig(groupConfig) + + // Configure the HTTP client with the effective timeouts + httpClient := &http.Client{ + Transport: &http.Transport{ + IdleConnTimeout: time.Duration(effectiveSettings.IdleConnTimeout) * time.Second, + }, + Timeout: time.Duration(effectiveSettings.RequestTimeout) * time.Second, + } + return BaseChannel{ Name: name, Upstreams: upstreamURLs, - HTTPClient: &http.Client{}, + HTTPClient: httpClient, }, nil } diff --git a/internal/channel/gemini_channel.go b/internal/channel/gemini_channel.go index 37c57e4..79d2880 100644 --- a/internal/channel/gemini_channel.go +++ b/internal/channel/gemini_channel.go @@ -5,14 +5,15 @@ import ( "net/http" "github.com/gin-gonic/gin" + "gorm.io/datatypes" ) type GeminiChannel struct { BaseChannel } -func NewGeminiChannel(upstreams []string) (*GeminiChannel, error) { - base, err := newBaseChannelWithUpstreams("gemini", upstreams) +func NewGeminiChannel(upstreams []string, config datatypes.JSONMap) (*GeminiChannel, error) { + base, err := newBaseChannelWithUpstreams("gemini", upstreams, config) if err != nil { return nil, err } diff --git a/internal/channel/openai_channel.go b/internal/channel/openai_channel.go index da6ae5a..85e3063 100644 --- a/internal/channel/openai_channel.go +++ b/internal/channel/openai_channel.go @@ -5,14 +5,15 @@ import ( "net/http" "github.com/gin-gonic/gin" + "gorm.io/datatypes" ) type OpenAIChannel struct { BaseChannel } -func NewOpenAIChannel(upstreams []string) (*OpenAIChannel, error) { - base, err := newBaseChannelWithUpstreams("openai", upstreams) +func NewOpenAIChannel(upstreams []string, config datatypes.JSONMap) (*OpenAIChannel, error) { + base, err := newBaseChannelWithUpstreams("openai", upstreams, config) if err != nil { return nil, err } diff --git a/internal/config/manager.go b/internal/config/manager.go index f1483a8..a086ebd 100644 --- a/internal/config/manager.go +++ b/internal/config/manager.go @@ -6,7 +6,6 @@ import ( "os" "strconv" "strings" - "sync/atomic" "gpt-load/internal/errors" "gpt-load/internal/types" @@ -37,8 +36,7 @@ var DefaultConstants = Constants{ // Manager implements the ConfigManager interface type Manager struct { - config *Config - roundRobinCounter uint64 + config *Config } // Config represents the application configuration @@ -67,25 +65,27 @@ func (m *Manager) ReloadConfig() error { logrus.Info("Info: Create .env file to support environment variable configuration") } + // Get business logic defaults from the single source of truth + defaultSettings := DefaultSystemSettings() + config := &Config{ Server: types.ServerConfig{ Port: parseInteger(os.Getenv("PORT"), 3000), Host: getEnvOrDefault("HOST", "0.0.0.0"), // Server timeout configs now come from system settings, not environment - // Using defaults here, will be overridden by system settings - ReadTimeout: 120, - WriteTimeout: 1800, - IdleTimeout: 120, - GracefulShutdownTimeout: 60, + // Using defaults from SystemSettings struct as the initial value + ReadTimeout: defaultSettings.ServerReadTimeout, + WriteTimeout: defaultSettings.ServerWriteTimeout, + IdleTimeout: defaultSettings.ServerIdleTimeout, + GracefulShutdownTimeout: defaultSettings.ServerGracefulShutdownTimeout, }, OpenAI: types.OpenAIConfig{ - // OPENAI_BASE_URL is removed from environment config - // Base URLs will be configured per group - BaseURLs: []string{}, // Will be set per group + // BaseURLs will be configured per group + BaseURLs: []string{}, // Timeout configs now come from system settings - RequestTimeout: 30, - ResponseTimeout: 30, - IdleConnTimeout: 120, + RequestTimeout: defaultSettings.RequestTimeout, + ResponseTimeout: defaultSettings.ResponseTimeout, + IdleConnTimeout: defaultSettings.IdleConnTimeout, }, Auth: types.AuthConfig{ Key: os.Getenv("AUTH_KEY"), @@ -122,24 +122,6 @@ func (m *Manager) ReloadConfig() error { return nil } -// GetServerConfig returns server configuration -// func (m *Manager) GetServerConfig() types.ServerConfig { -// return m.config.Server -// } - -// GetOpenAIConfig returns OpenAI configuration -// func (m *Manager) GetOpenAIConfig() types.OpenAIConfig { -// config := m.config.OpenAI -// if len(config.BaseURLs) > 1 { -// // Use atomic counter for thread-safe round-robin -// index := atomic.AddUint64(&m.roundRobinCounter, 1) - 1 -// config.BaseURL = config.BaseURLs[index%uint64(len(config.BaseURLs))] -// } else if len(config.BaseURLs) == 1 { -// config.BaseURL = config.BaseURLs[0] -// } -// return config -// } - // GetAuthConfig returns authentication configuration func (m *Manager) GetAuthConfig() types.AuthConfig { return m.config.Auth @@ -164,7 +146,7 @@ func (m *Manager) GetLogConfig() types.LogConfig { func (m *Manager) GetEffectiveServerConfig() types.ServerConfig { config := m.config.Server - // Merge with system settings + // Merge with system settings from database settingsManager := GetSystemSettingsManager() systemSettings := settingsManager.GetSettings() @@ -176,35 +158,6 @@ func (m *Manager) GetEffectiveServerConfig() types.ServerConfig { return config } -// GetEffectiveOpenAIConfig returns OpenAI configuration merged with system settings and group config -func (m *Manager) GetEffectiveOpenAIConfig(groupConfig map[string]any) types.OpenAIConfig { - config := m.config.OpenAI - - // Merge with system settings - settingsManager := GetSystemSettingsManager() - effectiveSettings := settingsManager.GetEffectiveConfig(groupConfig) - - config.RequestTimeout = effectiveSettings.RequestTimeout - config.ResponseTimeout = effectiveSettings.ResponseTimeout - config.IdleConnTimeout = effectiveSettings.IdleConnTimeout - - // Apply round-robin for multiple URLs if configured - if len(config.BaseURLs) > 1 { - index := atomic.AddUint64(&m.roundRobinCounter, 1) - 1 - config.BaseURL = config.BaseURLs[index%uint64(len(config.BaseURLs))] - } else if len(config.BaseURLs) == 1 { - config.BaseURL = config.BaseURLs[0] - } - - return config -} - -// GetEffectiveLogConfig returns log configuration (now uses environment config only) -// func (m *Manager) GetEffectiveLogConfig() types.LogConfig { -// // Log configuration is now managed via environment variables only -// return m.config.Log -// } - // Validate validates the configuration func (m *Manager) Validate() error { var validationErrors []string @@ -232,7 +185,6 @@ func (m *Manager) Validate() error { // DisplayConfig displays current configuration information func (m *Manager) DisplayConfig() { serverConfig := m.GetEffectiveServerConfig() - // openaiConfig := m.GetOpenAIConfig() authConfig := m.GetAuthConfig() corsConfig := m.GetCORSConfig() perfConfig := m.GetPerformanceConfig() diff --git a/internal/config/system_settings.go b/internal/config/system_settings.go index d6f0082..00d5a16 100644 --- a/internal/config/system_settings.go +++ b/internal/config/system_settings.go @@ -10,6 +10,7 @@ import ( "sync" "github.com/sirupsen/logrus" + "gorm.io/datatypes" "gorm.io/gorm/clause" ) @@ -220,7 +221,7 @@ func (sm *SystemSettingsManager) UpdateSettings(settingsMap map[string]string) e } // GetEffectiveConfig 获取有效配置 (系统配置 + 分组覆盖) -func (sm *SystemSettingsManager) GetEffectiveConfig(groupConfig map[string]any) SystemSettings { +func (sm *SystemSettingsManager) GetEffectiveConfig(groupConfig datatypes.JSONMap) SystemSettings { sm.mu.RLock() defer sm.mu.RUnlock() @@ -244,8 +245,19 @@ func (sm *SystemSettingsManager) GetEffectiveConfig(groupConfig map[string]any) if fieldName, ok := jsonToField[key]; ok { fieldValue := v.FieldByName(fieldName) if fieldValue.IsValid() && fieldValue.CanSet() { - if intVal, err := interfaceToInt(val); err == nil { - fieldValue.SetInt(int64(intVal)) + 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) + } } } } @@ -360,3 +372,24 @@ func interfaceToInt(val interface{}) (int, error) { return 0, fmt.Errorf("cannot convert to int: %v", val) } } + +func interfaceToString(val interface{}) (string, bool) { + s, ok := val.(string) + return s, ok +} + +func interfaceToBool(val interface{}) (bool, bool) { + switch v := val.(type) { + case bool: + return v, true + case string: + lowerV := strings.ToLower(v) + if lowerV == "true" || lowerV == "1" || lowerV == "on" { + return true, true + } + if lowerV == "false" || lowerV == "0" || lowerV == "off" { + return false, true + } + } + return false, false +} diff --git a/internal/types/types.go b/internal/types/types.go index 0a3db7e..abf5da8 100644 --- a/internal/types/types.go +++ b/internal/types/types.go @@ -7,19 +7,11 @@ import ( // ConfigManager defines the interface for configuration management type ConfigManager interface { - // GetServerConfig() ServerConfig - // GetOpenAIConfig() OpenAIConfig GetAuthConfig() AuthConfig GetCORSConfig() CORSConfig GetPerformanceConfig() PerformanceConfig GetLogConfig() LogConfig - - // Effective configuration methods that merge system settings GetEffectiveServerConfig() ServerConfig - GetEffectiveOpenAIConfig(groupConfig map[string]any) OpenAIConfig - // GetEffectivePerformanceConfig() PerformanceConfig - // GetEffectiveLogConfig() LogConfig - Validate() error DisplayConfig() ReloadConfig() error