diff --git a/cmd/gpt-load/main.go b/cmd/gpt-load/main.go index b4108dc..4e221c1 100644 --- a/cmd/gpt-load/main.go +++ b/cmd/gpt-load/main.go @@ -4,29 +4,18 @@ package main import ( "context" "embed" - "fmt" - "io" - "net/http" "os" "os/signal" - "path/filepath" - "sync" "syscall" "time" - "gpt-load/internal/channel" + "gpt-load/internal/app" "gpt-load/internal/config" - "gpt-load/internal/db" - "gpt-load/internal/handler" + "gpt-load/internal/container" "gpt-load/internal/models" - "gpt-load/internal/proxy" - "gpt-load/internal/router" - "gpt-load/internal/services" - "gpt-load/internal/store" "gpt-load/internal/types" "github.com/sirupsen/logrus" - "gorm.io/gorm" ) //go:embed dist @@ -36,217 +25,53 @@ var buildFS embed.FS var indexPage []byte func main() { - // Load configuration - configManager, err := config.NewManager() + // Build the dependency injection container + container, err := container.BuildContainer() if err != nil { - logrus.Fatalf("Failed to load configuration: %v", err) + logrus.Fatalf("Failed to build container: %v", err) } - // Setup logger - setupLogger(configManager) - - // Initialize database - database, err := db.InitDB(configManager) - if err != nil { - logrus.Fatalf("Failed to initialize database: %v", err) + // Provide UI assets to the container + if err := container.Provide(func() embed.FS { return buildFS }); err != nil { + logrus.Fatalf("Failed to provide buildFS: %v", err) + } + if err := container.Provide(func() []byte { return indexPage }); err != nil { + logrus.Fatalf("Failed to provide indexPage: %v", err) } - // Initialize system settings after database is ready - settingsManager := config.GetSystemSettingsManager() - if err := settingsManager.InitializeSystemSettings(); err != nil { - logrus.Fatalf("Failed to initialize system settings: %v", err) - } - logrus.Info("System settings initialized") - - // Display current system settings - settingsManager.DisplayCurrentSettings() - - // Display startup information - configManager.DisplayConfig() - - // Start log cleanup service - logCleanupService := services.NewLogCleanupService() - logCleanupService.Start() - defer logCleanupService.Stop() - - // --- Asynchronous Request Logging Setup --- + // Provide the request log channel as a value requestLogChan := make(chan models.RequestLog, 1000) - var wg sync.WaitGroup - wg.Add(1) - go startRequestLogger(database, requestLogChan, &wg) - // --- - - // --- Service Initialization --- - // Initialize the store first, as other services depend on it. - storage, err := store.NewStore(configManager) - if err != nil { - logrus.Fatalf("Failed to initialize store: %v", err) - } - defer storage.Close() - - taskService := services.NewTaskService(storage) - channelFactory := channel.NewFactory(settingsManager) - keyValidatorService := services.NewKeyValidatorService(database, channelFactory, settingsManager) - - // --- Global Key Validation Pool --- - KeyValidationPool := services.NewKeyValidationPool(keyValidatorService, configManager) - KeyValidationPool.Start() - defer KeyValidationPool.Stop() - // --- - - keyManualValidationService := services.NewKeyManualValidationService(database, keyValidatorService, taskService, settingsManager, configManager) - keyCronService := services.NewKeyCronService(database, settingsManager, KeyValidationPool, storage) - keyCronService.Start() - defer keyCronService.Stop() - - keyService := services.NewKeyService(database) - // --- - - // Create proxy server - proxyServer, err := proxy.NewProxyServer(database, channelFactory, requestLogChan) - if err != nil { - logrus.Fatalf("Failed to create proxy server: %v", err) - } - defer proxyServer.Close() - - // Create handlers - serverHandler := handler.NewServer(database, configManager, settingsManager, keyValidatorService, keyManualValidationService, taskService, keyService) - logCleanupHandler := handler.NewLogCleanupHandler(logCleanupService) - - // Setup routes using the new router package - appRouter := router.New(serverHandler, proxyServer, logCleanupHandler, configManager, buildFS, indexPage) - - // Create HTTP server with optimized timeout configuration - serverConfig := configManager.GetEffectiveServerConfig() - server := &http.Server{ - Addr: fmt.Sprintf("%s:%d", serverConfig.Host, serverConfig.Port), - Handler: appRouter, - ReadTimeout: time.Duration(serverConfig.ReadTimeout) * time.Second, - WriteTimeout: time.Duration(serverConfig.WriteTimeout) * time.Second, - IdleTimeout: time.Duration(serverConfig.IdleTimeout) * time.Second, - MaxHeaderBytes: 1 << 20, // 1MB header limit + if err := container.Provide(func() chan models.RequestLog { return requestLogChan }); err != nil { + logrus.Fatalf("Failed to provide request log channel: %v", err) } - // Start server - go func() { - logrus.Info("GPT-Load proxy server started successfully") - logrus.Infof("Server address: http://%s:%d", serverConfig.Host, serverConfig.Port) - logrus.Infof("Statistics: http://%s:%d/stats", serverConfig.Host, serverConfig.Port) - logrus.Infof("Health check: http://%s:%d/health", serverConfig.Host, serverConfig.Port) - logrus.Info("") + // Initialzie global logger + if err := container.Invoke(func(configManager types.ConfigManager) { + config.SetupLogger(configManager) + }); err != nil { + logrus.Fatalf("Failed to setup logger: %v", err) + } - if err := server.ListenAndServe(); err != nil && err != http.ErrServerClosed { - logrus.Fatalf("Server startup failed: %v", err) + // Create and run the application + if err := container.Invoke(func(application *app.App, configManager types.ConfigManager) { + if err := application.Start(); err != nil { + logrus.Fatalf("Failed to start application: %v", err) } - }() - // Wait for interrupt signal to gracefully shutdown the server - quit := make(chan os.Signal, 1) - signal.Notify(quit, syscall.SIGINT, syscall.SIGTERM) - <-quit - logrus.Info("Shutting down server...") + // Wait for interrupt signal for graceful shutdown + quit := make(chan os.Signal, 1) + signal.Notify(quit, syscall.SIGINT, syscall.SIGTERM) + <-quit - // Give outstanding requests a deadline for completion - ctx, cancel := context.WithTimeout(context.Background(), time.Duration(serverConfig.GracefulShutdownTimeout)*time.Second) - defer cancel() + // Create a context with timeout for shutdown + serverConfig := configManager.GetEffectiveServerConfig() + shutdownCtx, cancel := context.WithTimeout(context.Background(), time.Duration(serverConfig.GracefulShutdownTimeout)*time.Second) + defer cancel() - // Attempt graceful shutdown - if err := server.Shutdown(ctx); err != nil { - logrus.Errorf("Server forced to shutdown: %v", err) - } + // Perform graceful shutdown + application.Stop(shutdownCtx) - // Close the request log channel and wait for the logger to finish - logrus.Info("Closing request log channel...") - close(requestLogChan) - wg.Wait() - logrus.Info("All logs have been written.") - - logrus.Info("Server exited gracefully") -} - -// setupLogger, displayStartupInfo, and startRequestLogger functions remain unchanged. -// The old setupRoutes and ServeUI functions are now removed from this file. - -// setupLogger configures the logging system -func setupLogger(configManager types.ConfigManager) { - logConfig := configManager.GetLogConfig() - - // Set log level - level, err := logrus.ParseLevel(logConfig.Level) - if err != nil { - logrus.Warn("Invalid log level, using info") - level = logrus.InfoLevel - } - logrus.SetLevel(level) - - // Set log format - if logConfig.Format == "json" { - logrus.SetFormatter(&logrus.JSONFormatter{ - TimestampFormat: time.RFC3339, - }) - } else { - logrus.SetFormatter(&logrus.TextFormatter{ - ForceColors: true, - FullTimestamp: true, - TimestampFormat: "2006-01-02 15:04:05", - }) - } - - // Setup file logging if enabled - if logConfig.EnableFile { - // Create log directory if it doesn't exist - logDir := filepath.Dir(logConfig.FilePath) - if err := os.MkdirAll(logDir, 0755); err != nil { - logrus.Warnf("Failed to create log directory: %v", err) - } else { - // Open log file - logFile, err := os.OpenFile(logConfig.FilePath, os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0666) - if err != nil { - logrus.Warnf("Failed to open log file: %v", err) - } else { - // Use both file and stdout - logrus.SetOutput(io.MultiWriter(os.Stdout, logFile)) - } - } - } -} - -// startRequestLogger runs a background goroutine to batch-insert request logs. -func startRequestLogger(db *gorm.DB, logChan <-chan models.RequestLog, wg *sync.WaitGroup) { - defer wg.Done() - ticker := time.NewTicker(5 * time.Second) - defer ticker.Stop() - - logBuffer := make([]models.RequestLog, 0, 100) - - for { - select { - case logEntry, ok := <-logChan: - if !ok { - // Channel closed, flush remaining logs and exit - if len(logBuffer) > 0 { - if err := db.Create(&logBuffer).Error; err != nil { - logrus.Errorf("Failed to write remaining request logs: %v", err) - } - } - logrus.Info("Request logger stopped.") - return - } - logBuffer = append(logBuffer, logEntry) - if len(logBuffer) >= 100 { - if err := db.Create(&logBuffer).Error; err != nil { - logrus.Errorf("Failed to write request logs: %v", err) - } - logBuffer = make([]models.RequestLog, 0, 100) // Reset buffer - } - case <-ticker.C: - // Flush logs periodically - if len(logBuffer) > 0 { - if err := db.Create(&logBuffer).Error; err != nil { - logrus.Errorf("Failed to write request logs on tick: %v", err) - } - logBuffer = make([]models.RequestLog, 0, 100) // Reset buffer - } - } + }); err != nil { + logrus.Fatalf("Failed to run application: %v", err) } } diff --git a/go.mod b/go.mod index a3292a4..ed1e946 100644 --- a/go.mod +++ b/go.mod @@ -12,6 +12,7 @@ require ( github.com/joho/godotenv v1.5.1 github.com/redis/go-redis/v9 v9.5.3 github.com/sirupsen/logrus v1.9.3 + go.uber.org/dig v1.19.0 gorm.io/datatypes v1.2.1 gorm.io/driver/mysql v1.6.0 gorm.io/gorm v1.30.0 diff --git a/go.sum b/go.sum index df7cfbd..0de0a56 100644 --- a/go.sum +++ b/go.sum @@ -110,6 +110,8 @@ github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08= github.com/ugorji/go/codec v1.3.0 h1:Qd2W2sQawAfG8XSvzwhBeoGq71zXOC/Q1E9y/wUcsUA= github.com/ugorji/go/codec v1.3.0/go.mod h1:pRBVtBSKl77K30Bv8R2P+cLSGaTtex6fsA2Wjqmfxj4= +go.uber.org/dig v1.19.0 h1:BACLhebsYdpQ7IROQ1AGPjrXcP5dF80U3gKoFzbaq/4= +go.uber.org/dig v1.19.0/go.mod h1:Us0rSJiThwCv2GteUN0Q7OKvU7n5J4dxZ9JKUXozFdE= golang.org/x/arch v0.18.0 h1:WN9poc33zL4AzGxqf8VtpKUnGvMi8O9lhNyBMF/85qc= golang.org/x/arch v0.18.0/go.mod h1:bdwinDaKcfZUGpH09BB7ZmOfhalA8lQdzl62l8gGWsk= golang.org/x/crypto v0.39.0 h1:SHs+kF4LP+f+p14esP5jAoDpHU8Gu/v9lFRK6IT5imM= diff --git a/internal/app/app.go b/internal/app/app.go new file mode 100644 index 0000000..79ee07e --- /dev/null +++ b/internal/app/app.go @@ -0,0 +1,181 @@ +// Package app provides the main application logic and lifecycle management. +package app + +import ( + "context" + "fmt" + "net/http" + "sync" + "time" + + "gpt-load/internal/config" + "gpt-load/internal/models" + "gpt-load/internal/proxy" + "gpt-load/internal/services" + "gpt-load/internal/store" + "gpt-load/internal/types" + + "github.com/gin-gonic/gin" + "github.com/sirupsen/logrus" + "go.uber.org/dig" + "gorm.io/gorm" +) + +// App holds all services and manages the application lifecycle. +type App struct { + engine *gin.Engine + configManager types.ConfigManager + settingsManager *config.SystemSettingsManager + logCleanupService *services.LogCleanupService + keyCronService *services.KeyCronService + keyValidationPool *services.KeyValidationPool + proxyServer *proxy.ProxyServer + storage store.Store + db *gorm.DB + httpServer *http.Server + requestLogChan chan models.RequestLog + wg sync.WaitGroup +} + +// AppParams defines the dependencies for the App. +type AppParams struct { + dig.In + Engine *gin.Engine + ConfigManager types.ConfigManager + SettingsManager *config.SystemSettingsManager + LogCleanupService *services.LogCleanupService + KeyCronService *services.KeyCronService + KeyValidationPool *services.KeyValidationPool + ProxyServer *proxy.ProxyServer + Storage store.Store + DB *gorm.DB + RequestLogChan chan models.RequestLog +} + +// NewApp is the constructor for App, with dependencies injected by dig. +func NewApp(params AppParams) *App { + return &App{ + engine: params.Engine, + configManager: params.ConfigManager, + settingsManager: params.SettingsManager, + logCleanupService: params.LogCleanupService, + keyCronService: params.KeyCronService, + keyValidationPool: params.KeyValidationPool, + proxyServer: params.ProxyServer, + storage: params.Storage, + db: params.DB, + requestLogChan: params.RequestLogChan, + } +} + +// Start runs the application, it is a non-blocking call. +func (a *App) Start() error { + // Initialize system settings + if err := a.settingsManager.InitializeSystemSettings(); err != nil { + return fmt.Errorf("failed to initialize system settings: %w", err) + } + logrus.Info("System settings initialized") + a.settingsManager.DisplayCurrentSettings() + a.configManager.DisplayConfig() + + // Start background services + a.startRequestLogger() + a.logCleanupService.Start() + a.keyValidationPool.Start() + a.keyCronService.Start() + + // Create HTTP server + serverConfig := a.configManager.GetEffectiveServerConfig() + a.httpServer = &http.Server{ + Addr: fmt.Sprintf("%s:%d", serverConfig.Host, serverConfig.Port), + Handler: a.engine, + ReadTimeout: time.Duration(serverConfig.ReadTimeout) * time.Second, + WriteTimeout: time.Duration(serverConfig.WriteTimeout) * time.Second, + IdleTimeout: time.Duration(serverConfig.IdleTimeout) * time.Second, + MaxHeaderBytes: 1 << 20, // 1MB header limit + } + + // Start HTTP server in a new goroutine + go func() { + logrus.Info("GPT-Load proxy server started successfully") + logrus.Infof("Server address: http://%s:%d", serverConfig.Host, serverConfig.Port) + logrus.Infof("Statistics: http://%s:%d/stats", serverConfig.Host, serverConfig.Port) + logrus.Infof("Health check: http://%s:%d/health", serverConfig.Host, serverConfig.Port) + logrus.Info("") + if err := a.httpServer.ListenAndServe(); err != nil && err != http.ErrServerClosed { + logrus.Fatalf("Server startup failed: %v", err) + } + }() + + return nil +} + +// Stop gracefully shuts down the application. +func (a *App) Stop(ctx context.Context) { + logrus.Info("Shutting down server...") + + // Shutdown http server + if err := a.httpServer.Shutdown(ctx); err != nil { + logrus.Errorf("Server forced to shutdown: %v", err) + } + + // Stop background services + a.keyCronService.Stop() + a.keyValidationPool.Stop() + a.logCleanupService.Stop() + + // Close resources + a.proxyServer.Close() + a.storage.Close() + + // Wait for the logger to finish writing all logs + logrus.Info("Closing request log channel...") + close(a.requestLogChan) + a.wg.Wait() + logrus.Info("All logs have been written.") + + logrus.Info("Server exited gracefully") +} + +// startRequestLogger runs a background goroutine to batch-insert request logs. +func (a *App) startRequestLogger() { + a.wg.Add(1) + go func() { + defer a.wg.Done() + ticker := time.NewTicker(5 * time.Second) + defer ticker.Stop() + + logBuffer := make([]models.RequestLog, 0, 100) + + for { + select { + case logEntry, ok := <-a.requestLogChan: + if !ok { + // Channel closed, flush remaining logs and exit + if len(logBuffer) > 0 { + if err := a.db.Create(&logBuffer).Error; err != nil { + logrus.Errorf("Failed to write remaining request logs: %v", err) + } + } + logrus.Info("Request logger stopped.") + return + } + logBuffer = append(logBuffer, logEntry) + if len(logBuffer) >= 100 { + if err := a.db.Create(&logBuffer).Error; err != nil { + logrus.Errorf("Failed to write request logs: %v", err) + } + logBuffer = make([]models.RequestLog, 0, 100) // Reset buffer + } + case <-ticker.C: + // Flush logs periodically + if len(logBuffer) > 0 { + if err := a.db.Create(&logBuffer).Error; err != nil { + logrus.Errorf("Failed to write request logs on tick: %v", err) + } + logBuffer = make([]models.RequestLog, 0, 100) // Reset buffer + } + } + } + }() +} diff --git a/internal/config/manager.go b/internal/config/manager.go index d079edf..f57cbd6 100644 --- a/internal/config/manager.go +++ b/internal/config/manager.go @@ -3,7 +3,9 @@ package config import ( "fmt" + "io" "os" + "path/filepath" "strconv" "strings" @@ -36,7 +38,8 @@ var DefaultConstants = Constants{ // Manager implements the ConfigManager interface type Manager struct { - config *Config + config *Config + settingsManager *SystemSettingsManager } // Config represents the application configuration @@ -52,8 +55,10 @@ type Config struct { } // NewManager creates a new configuration manager -func NewManager() (types.ConfigManager, error) { - manager := &Manager{} +func NewManager(settingsManager *SystemSettingsManager) (types.ConfigManager, error) { + manager := &Manager{ + settingsManager: settingsManager, + } if err := manager.ReloadConfig(); err != nil { return nil, err } @@ -170,8 +175,7 @@ func (m *Manager) GetEffectiveServerConfig() types.ServerConfig { config := m.config.Server // Merge with system settings from database - settingsManager := GetSystemSettingsManager() - systemSettings := settingsManager.GetSettings() + systemSettings := m.settingsManager.GetSettings() config.ReadTimeout = systemSettings.ServerReadTimeout config.WriteTimeout = systemSettings.ServerWriteTimeout @@ -313,3 +317,45 @@ func (s *SystemSettingsManager) GetInt(key string, defaultValue int) int { } return defaultValue } + + +// SetupLogger configures the logging system based on the provided configuration. +func SetupLogger(configManager types.ConfigManager) { + logConfig := configManager.GetLogConfig() + + // Set log level + level, err := logrus.ParseLevel(logConfig.Level) + if err != nil { + logrus.Warn("Invalid log level, using info") + level = logrus.InfoLevel + } + logrus.SetLevel(level) + + // Set log format + if logConfig.Format == "json" { + logrus.SetFormatter(&logrus.JSONFormatter{ + TimestampFormat: "2006-01-02T15:04:05.000Z07:00", // ISO 8601 format + }) + } else { + logrus.SetFormatter(&logrus.TextFormatter{ + ForceColors: true, + FullTimestamp: true, + TimestampFormat: "2006-01-02 15:04:05", + }) + } + + // Setup file logging if enabled + if logConfig.EnableFile { + logDir := filepath.Dir(logConfig.FilePath) + if err := os.MkdirAll(logDir, 0755); err != nil { + logrus.Warnf("Failed to create log directory: %v", err) + } else { + logFile, err := os.OpenFile(logConfig.FilePath, os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0666) + if err != nil { + logrus.Warnf("Failed to open log file: %v", err) + } else { + logrus.SetOutput(io.MultiWriter(os.Stdout, logFile)) + } + } + } +} diff --git a/internal/config/system_settings.go b/internal/config/system_settings.go index c323917..e8fcb90 100644 --- a/internal/config/system_settings.go +++ b/internal/config/system_settings.go @@ -117,8 +117,8 @@ type SystemSettingsManager struct { var globalSystemSettings *SystemSettingsManager var once sync.Once -// GetSystemSettingsManager 获取全局系统配置管理器单例 -func GetSystemSettingsManager() *SystemSettingsManager { +// NewSystemSettingsManager 获取全局系统配置管理器单例 +func NewSystemSettingsManager() *SystemSettingsManager { once.Do(func() { globalSystemSettings = &SystemSettingsManager{} }) diff --git a/internal/container/container.go b/internal/container/container.go new file mode 100644 index 0000000..b46c19e --- /dev/null +++ b/internal/container/container.go @@ -0,0 +1,87 @@ +// Package container provides a dependency injection container for the application. +package container + +import ( + "gpt-load/internal/app" + "gpt-load/internal/channel" + "gpt-load/internal/config" + "gpt-load/internal/db" + "gpt-load/internal/handler" + "gpt-load/internal/proxy" + "gpt-load/internal/router" + "gpt-load/internal/services" + "gpt-load/internal/store" + + "go.uber.org/dig" +) + +// BuildContainer creates a new dependency injection container and provides all the application's services. +func BuildContainer() (*dig.Container, error) { + container := dig.New() + + // Infrastructure Services + if err := container.Provide(config.NewManager); err != nil { + return nil, err + } + if err := container.Provide(db.NewDB); err != nil { + return nil, err + } + if err := container.Provide(config.NewSystemSettingsManager); err != nil { + return nil, err + } + if err := container.Provide(store.NewStore); err != nil { + return nil, err + } + if err := container.Provide(channel.NewFactory); err != nil { + return nil, err + } + + // Business Services + if err := container.Provide(services.NewTaskService); err != nil { + return nil, err + } + if err := container.Provide(services.NewKeyValidatorService); err != nil { + return nil, err + } + if err := container.Provide(services.NewKeyValidationPool); err != nil { + return nil, err + } + if err := container.Provide(services.NewKeyManualValidationService); err != nil { + return nil, err + } + if err := container.Provide(services.NewKeyCronService); err != nil { + return nil, err + } + if err := container.Provide(services.NewKeyService); err != nil { + return nil, err + } + if err := container.Provide(services.NewLogCleanupService); err != nil { + return nil, err + } + + // Handlers + if err := container.Provide(handler.NewServer); err != nil { + return nil, err + } + if err := container.Provide(handler.NewLogCleanupHandler); err != nil { + return nil, err + } + if err := container.Provide(handler.NewCommonHandler); err != nil { + return nil, err + } + + // Proxy & Router + if err := container.Provide(proxy.NewProxyServer); err != nil { + return nil, err + } + if err := container.Provide(router.NewRouter); err != nil { + return nil, err + } + + // Application Layer + if err := container.Provide(app.NewApp); err != nil { + return nil, err + } + + return container, nil +} diff --git a/internal/db/database.go b/internal/db/database.go index 06207d2..6747960 100644 --- a/internal/db/database.go +++ b/internal/db/database.go @@ -15,7 +15,7 @@ import ( var DB *gorm.DB -func InitDB(configManager types.ConfigManager) (*gorm.DB, error) { +func NewDB(configManager types.ConfigManager) (*gorm.DB, error) { dbConfig := configManager.GetDatabaseConfig() if dbConfig.DSN == "" { return nil, fmt.Errorf("DATABASE_DSN is not configured") diff --git a/internal/handler/handler.go b/internal/handler/handler.go index ff50232..82d5411 100644 --- a/internal/handler/handler.go +++ b/internal/handler/handler.go @@ -11,6 +11,7 @@ import ( "gpt-load/internal/types" "github.com/gin-gonic/gin" + "go.uber.org/dig" "gorm.io/gorm" ) @@ -26,25 +27,30 @@ type Server struct { CommonHandler *CommonHandler } -// NewServer creates a new handler instance -func NewServer( - db *gorm.DB, - config types.ConfigManager, - settingsManager *config.SystemSettingsManager, - keyValidatorService *services.KeyValidatorService, - keyManualValidationService *services.KeyManualValidationService, - taskService *services.TaskService, - keyService *services.KeyService, -) *Server { +// NewServerParams defines the dependencies for the NewServer constructor. +type NewServerParams struct { + dig.In + DB *gorm.DB + Config types.ConfigManager + SettingsManager *config.SystemSettingsManager + KeyValidatorService *services.KeyValidatorService + KeyManualValidationService *services.KeyManualValidationService + TaskService *services.TaskService + KeyService *services.KeyService + CommonHandler *CommonHandler +} + +// NewServer creates a new handler instance with dependencies injected by dig. +func NewServer(params NewServerParams) *Server { return &Server{ - DB: db, - config: config, - SettingsManager: settingsManager, - KeyValidatorService: keyValidatorService, - KeyManualValidationService: keyManualValidationService, - TaskService: taskService, - KeyService: keyService, - CommonHandler: NewCommonHandler(), + DB: params.DB, + config: params.Config, + SettingsManager: params.SettingsManager, + KeyValidatorService: params.KeyValidatorService, + KeyManualValidationService: params.KeyManualValidationService, + TaskService: params.TaskService, + KeyService: params.KeyService, + CommonHandler: params.CommonHandler, } } diff --git a/internal/handler/settings_handler.go b/internal/handler/settings_handler.go index 2e38eee..a58b6c5 100644 --- a/internal/handler/settings_handler.go +++ b/internal/handler/settings_handler.go @@ -12,9 +12,8 @@ import ( // GetSettings handles the GET /api/settings request. // It retrieves all system settings, groups them by category, and returns them. -func GetSettings(c *gin.Context) { - settingsManager := config.GetSystemSettingsManager() - currentSettings := settingsManager.GetSettings() +func (s *Server) GetSettings(c *gin.Context) { + currentSettings := s.SettingsManager.GetSettings() settingsInfo := config.GenerateSettingsMetadata(¤tSettings) // Group settings by category while preserving order @@ -42,7 +41,7 @@ func GetSettings(c *gin.Context) { // UpdateSettings handles the PUT /api/settings request. // It receives a key-value JSON object and updates system settings. // After updating, it triggers a configuration reload. -func UpdateSettings(c *gin.Context) { +func (s *Server) UpdateSettings(c *gin.Context) { var settingsMap map[string]any if err := c.ShouldBindJSON(&settingsMap); err != nil { response.Error(c, app_errors.NewAPIError(app_errors.ErrInvalidJSON, err.Error())) @@ -54,22 +53,20 @@ func UpdateSettings(c *gin.Context) { return } - settingsManager := config.GetSystemSettingsManager() - // 更新配置 - if err := settingsManager.UpdateSettings(settingsMap); err != nil { + if err := s.SettingsManager.UpdateSettings(settingsMap); err != nil { response.Error(c, app_errors.NewAPIError(app_errors.ErrDatabase, err.Error())) return } // 重载系统配置 - if err := settingsManager.LoadFromDatabase(); err != nil { + if err := s.SettingsManager.LoadFromDatabase(); err != nil { logrus.Errorf("Failed to reload system settings: %v", err) response.Error(c, app_errors.NewAPIError(app_errors.ErrInternalServer, "Failed to reload system settings after update")) return } - settingsManager.DisplayCurrentSettings() + s.SettingsManager.DisplayCurrentSettings() logrus.Info("Configuration reloaded successfully via API") response.Success(c, gin.H{ diff --git a/internal/router/router.go b/internal/router/router.go index 91e4abe..62a7e0d 100644 --- a/internal/router/router.go +++ b/internal/router/router.go @@ -36,7 +36,7 @@ func EmbedFolder(fsEmbed embed.FS, targetPath string) static.ServeFileSystem { } } -func New( +func NewRouter( serverHandler *handler.Server, proxyServer *proxy.ProxyServer, logCleanupHandler *handler.LogCleanupHandler, @@ -142,8 +142,8 @@ func registerProtectedAPIRoutes(api *gin.RouterGroup, serverHandler *handler.Ser // 设置 settings := api.Group("/settings") { - settings.GET("", handler.GetSettings) - settings.PUT("", handler.UpdateSettings) + settings.GET("", serverHandler.GetSettings) + settings.PUT("", serverHandler.UpdateSettings) } } diff --git a/internal/services/log_cleanup.go b/internal/services/log_cleanup.go index fa34091..2550dda 100644 --- a/internal/services/log_cleanup.go +++ b/internal/services/log_cleanup.go @@ -2,22 +2,26 @@ package services import ( "gpt-load/internal/config" - "gpt-load/internal/db" "gpt-load/internal/models" "time" "github.com/sirupsen/logrus" + "gorm.io/gorm" ) // LogCleanupService 负责清理过期的请求日志 type LogCleanupService struct { - stopCh chan struct{} + db *gorm.DB + settingsManager *config.SystemSettingsManager + stopCh chan struct{} } // NewLogCleanupService 创建新的日志清理服务 -func NewLogCleanupService() *LogCleanupService { +func NewLogCleanupService(db *gorm.DB, settingsManager *config.SystemSettingsManager) *LogCleanupService { return &LogCleanupService{ - stopCh: make(chan struct{}), + db: db, + settingsManager: settingsManager, + stopCh: make(chan struct{}), } } @@ -54,19 +58,8 @@ func (s *LogCleanupService) run() { // cleanupExpiredLogs 清理过期的请求日志 func (s *LogCleanupService) cleanupExpiredLogs() { - if db.DB == nil { - logrus.Error("Database connection is not available for log cleanup") - return - } - // 获取日志保留天数配置 - settingsManager := config.GetSystemSettingsManager() - if settingsManager == nil { - logrus.Error("System settings manager is not available for log cleanup") - return - } - - settings := settingsManager.GetSettings() + settings := s.settingsManager.GetSettings() retentionDays := settings.RequestLogRetentionDays if retentionDays <= 0 { @@ -78,7 +71,7 @@ func (s *LogCleanupService) cleanupExpiredLogs() { cutoffTime := time.Now().AddDate(0, 0, -retentionDays).UTC() // 执行删除操作 - result := db.DB.Where("timestamp < ?", cutoffTime).Delete(&models.RequestLog{}) + result := s.db.Where("timestamp < ?", cutoffTime).Delete(&models.RequestLog{}) if result.Error != nil { logrus.WithError(result.Error).Error("Failed to cleanup expired request logs") return