feat: 引入dig服务依赖管理

This commit is contained in:
tbphp
2025-07-06 22:44:41 +08:00
parent a992c28593
commit 3b6468af83
12 changed files with 404 additions and 266 deletions

View File

@@ -4,29 +4,18 @@ package main
import ( import (
"context" "context"
"embed" "embed"
"fmt"
"io"
"net/http"
"os" "os"
"os/signal" "os/signal"
"path/filepath"
"sync"
"syscall" "syscall"
"time" "time"
"gpt-load/internal/channel" "gpt-load/internal/app"
"gpt-load/internal/config" "gpt-load/internal/config"
"gpt-load/internal/db" "gpt-load/internal/container"
"gpt-load/internal/handler"
"gpt-load/internal/models" "gpt-load/internal/models"
"gpt-load/internal/proxy"
"gpt-load/internal/router"
"gpt-load/internal/services"
"gpt-load/internal/store"
"gpt-load/internal/types" "gpt-load/internal/types"
"github.com/sirupsen/logrus" "github.com/sirupsen/logrus"
"gorm.io/gorm"
) )
//go:embed dist //go:embed dist
@@ -36,217 +25,53 @@ var buildFS embed.FS
var indexPage []byte var indexPage []byte
func main() { func main() {
// Load configuration // Build the dependency injection container
configManager, err := config.NewManager() container, err := container.BuildContainer()
if err != nil { if err != nil {
logrus.Fatalf("Failed to load configuration: %v", err) logrus.Fatalf("Failed to build container: %v", err)
} }
// Setup logger // Provide UI assets to the container
setupLogger(configManager) if err := container.Provide(func() embed.FS { return buildFS }); err != nil {
logrus.Fatalf("Failed to provide buildFS: %v", err)
// Initialize database }
database, err := db.InitDB(configManager) if err := container.Provide(func() []byte { return indexPage }); err != nil {
if err != nil { logrus.Fatalf("Failed to provide indexPage: %v", err)
logrus.Fatalf("Failed to initialize database: %v", err)
} }
// Initialize system settings after database is ready // Provide the request log channel as a value
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 ---
requestLogChan := make(chan models.RequestLog, 1000) requestLogChan := make(chan models.RequestLog, 1000)
var wg sync.WaitGroup if err := container.Provide(func() chan models.RequestLog { return requestLogChan }); err != nil {
wg.Add(1) logrus.Fatalf("Failed to provide request log channel: %v", err)
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
} }
// Start server // Initialzie global logger
go func() { if err := container.Invoke(func(configManager types.ConfigManager) {
logrus.Info("GPT-Load proxy server started successfully") config.SetupLogger(configManager)
logrus.Infof("Server address: http://%s:%d", serverConfig.Host, serverConfig.Port) }); err != nil {
logrus.Infof("Statistics: http://%s:%d/stats", serverConfig.Host, serverConfig.Port) logrus.Fatalf("Failed to setup logger: %v", err)
logrus.Infof("Health check: http://%s:%d/health", serverConfig.Host, serverConfig.Port) }
logrus.Info("")
if err := server.ListenAndServe(); err != nil && err != http.ErrServerClosed { // Create and run the application
logrus.Fatalf("Server startup failed: %v", err) 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 // Wait for interrupt signal for graceful shutdown
quit := make(chan os.Signal, 1) quit := make(chan os.Signal, 1)
signal.Notify(quit, syscall.SIGINT, syscall.SIGTERM) signal.Notify(quit, syscall.SIGINT, syscall.SIGTERM)
<-quit <-quit
logrus.Info("Shutting down server...")
// Give outstanding requests a deadline for completion // Create a context with timeout for shutdown
ctx, cancel := context.WithTimeout(context.Background(), time.Duration(serverConfig.GracefulShutdownTimeout)*time.Second) serverConfig := configManager.GetEffectiveServerConfig()
defer cancel() shutdownCtx, cancel := context.WithTimeout(context.Background(), time.Duration(serverConfig.GracefulShutdownTimeout)*time.Second)
defer cancel()
// Attempt graceful shutdown // Perform graceful shutdown
if err := server.Shutdown(ctx); err != nil { application.Stop(shutdownCtx)
logrus.Errorf("Server forced to shutdown: %v", err)
}
// Close the request log channel and wait for the logger to finish }); err != nil {
logrus.Info("Closing request log channel...") logrus.Fatalf("Failed to run application: %v", err)
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
}
}
} }
} }

1
go.mod
View File

@@ -12,6 +12,7 @@ require (
github.com/joho/godotenv v1.5.1 github.com/joho/godotenv v1.5.1
github.com/redis/go-redis/v9 v9.5.3 github.com/redis/go-redis/v9 v9.5.3
github.com/sirupsen/logrus v1.9.3 github.com/sirupsen/logrus v1.9.3
go.uber.org/dig v1.19.0
gorm.io/datatypes v1.2.1 gorm.io/datatypes v1.2.1
gorm.io/driver/mysql v1.6.0 gorm.io/driver/mysql v1.6.0
gorm.io/gorm v1.30.0 gorm.io/gorm v1.30.0

2
go.sum
View File

@@ -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/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 h1:Qd2W2sQawAfG8XSvzwhBeoGq71zXOC/Q1E9y/wUcsUA=
github.com/ugorji/go/codec v1.3.0/go.mod h1:pRBVtBSKl77K30Bv8R2P+cLSGaTtex6fsA2Wjqmfxj4= 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 h1:WN9poc33zL4AzGxqf8VtpKUnGvMi8O9lhNyBMF/85qc=
golang.org/x/arch v0.18.0/go.mod h1:bdwinDaKcfZUGpH09BB7ZmOfhalA8lQdzl62l8gGWsk= golang.org/x/arch v0.18.0/go.mod h1:bdwinDaKcfZUGpH09BB7ZmOfhalA8lQdzl62l8gGWsk=
golang.org/x/crypto v0.39.0 h1:SHs+kF4LP+f+p14esP5jAoDpHU8Gu/v9lFRK6IT5imM= golang.org/x/crypto v0.39.0 h1:SHs+kF4LP+f+p14esP5jAoDpHU8Gu/v9lFRK6IT5imM=

181
internal/app/app.go Normal file
View File

@@ -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
}
}
}
}()
}

View File

@@ -3,7 +3,9 @@ package config
import ( import (
"fmt" "fmt"
"io"
"os" "os"
"path/filepath"
"strconv" "strconv"
"strings" "strings"
@@ -36,7 +38,8 @@ var DefaultConstants = Constants{
// Manager implements the ConfigManager interface // Manager implements the ConfigManager interface
type Manager struct { type Manager struct {
config *Config config *Config
settingsManager *SystemSettingsManager
} }
// Config represents the application configuration // Config represents the application configuration
@@ -52,8 +55,10 @@ type Config struct {
} }
// NewManager creates a new configuration manager // NewManager creates a new configuration manager
func NewManager() (types.ConfigManager, error) { func NewManager(settingsManager *SystemSettingsManager) (types.ConfigManager, error) {
manager := &Manager{} manager := &Manager{
settingsManager: settingsManager,
}
if err := manager.ReloadConfig(); err != nil { if err := manager.ReloadConfig(); err != nil {
return nil, err return nil, err
} }
@@ -170,8 +175,7 @@ func (m *Manager) GetEffectiveServerConfig() types.ServerConfig {
config := m.config.Server config := m.config.Server
// Merge with system settings from database // Merge with system settings from database
settingsManager := GetSystemSettingsManager() systemSettings := m.settingsManager.GetSettings()
systemSettings := settingsManager.GetSettings()
config.ReadTimeout = systemSettings.ServerReadTimeout config.ReadTimeout = systemSettings.ServerReadTimeout
config.WriteTimeout = systemSettings.ServerWriteTimeout config.WriteTimeout = systemSettings.ServerWriteTimeout
@@ -313,3 +317,45 @@ func (s *SystemSettingsManager) GetInt(key string, defaultValue int) int {
} }
return defaultValue 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))
}
}
}
}

View File

@@ -117,8 +117,8 @@ type SystemSettingsManager struct {
var globalSystemSettings *SystemSettingsManager var globalSystemSettings *SystemSettingsManager
var once sync.Once var once sync.Once
// GetSystemSettingsManager 获取全局系统配置管理器单例 // NewSystemSettingsManager 获取全局系统配置管理器单例
func GetSystemSettingsManager() *SystemSettingsManager { func NewSystemSettingsManager() *SystemSettingsManager {
once.Do(func() { once.Do(func() {
globalSystemSettings = &SystemSettingsManager{} globalSystemSettings = &SystemSettingsManager{}
}) })

View File

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

View File

@@ -15,7 +15,7 @@ import (
var DB *gorm.DB var DB *gorm.DB
func InitDB(configManager types.ConfigManager) (*gorm.DB, error) { func NewDB(configManager types.ConfigManager) (*gorm.DB, error) {
dbConfig := configManager.GetDatabaseConfig() dbConfig := configManager.GetDatabaseConfig()
if dbConfig.DSN == "" { if dbConfig.DSN == "" {
return nil, fmt.Errorf("DATABASE_DSN is not configured") return nil, fmt.Errorf("DATABASE_DSN is not configured")

View File

@@ -11,6 +11,7 @@ import (
"gpt-load/internal/types" "gpt-load/internal/types"
"github.com/gin-gonic/gin" "github.com/gin-gonic/gin"
"go.uber.org/dig"
"gorm.io/gorm" "gorm.io/gorm"
) )
@@ -26,25 +27,30 @@ type Server struct {
CommonHandler *CommonHandler CommonHandler *CommonHandler
} }
// NewServer creates a new handler instance // NewServerParams defines the dependencies for the NewServer constructor.
func NewServer( type NewServerParams struct {
db *gorm.DB, dig.In
config types.ConfigManager, DB *gorm.DB
settingsManager *config.SystemSettingsManager, Config types.ConfigManager
keyValidatorService *services.KeyValidatorService, SettingsManager *config.SystemSettingsManager
keyManualValidationService *services.KeyManualValidationService, KeyValidatorService *services.KeyValidatorService
taskService *services.TaskService, KeyManualValidationService *services.KeyManualValidationService
keyService *services.KeyService, TaskService *services.TaskService
) *Server { KeyService *services.KeyService
CommonHandler *CommonHandler
}
// NewServer creates a new handler instance with dependencies injected by dig.
func NewServer(params NewServerParams) *Server {
return &Server{ return &Server{
DB: db, DB: params.DB,
config: config, config: params.Config,
SettingsManager: settingsManager, SettingsManager: params.SettingsManager,
KeyValidatorService: keyValidatorService, KeyValidatorService: params.KeyValidatorService,
KeyManualValidationService: keyManualValidationService, KeyManualValidationService: params.KeyManualValidationService,
TaskService: taskService, TaskService: params.TaskService,
KeyService: keyService, KeyService: params.KeyService,
CommonHandler: NewCommonHandler(), CommonHandler: params.CommonHandler,
} }
} }

View File

@@ -12,9 +12,8 @@ import (
// GetSettings handles the GET /api/settings request. // GetSettings handles the GET /api/settings request.
// It retrieves all system settings, groups them by category, and returns them. // It retrieves all system settings, groups them by category, and returns them.
func GetSettings(c *gin.Context) { func (s *Server) GetSettings(c *gin.Context) {
settingsManager := config.GetSystemSettingsManager() currentSettings := s.SettingsManager.GetSettings()
currentSettings := settingsManager.GetSettings()
settingsInfo := config.GenerateSettingsMetadata(&currentSettings) settingsInfo := config.GenerateSettingsMetadata(&currentSettings)
// Group settings by category while preserving order // Group settings by category while preserving order
@@ -42,7 +41,7 @@ func GetSettings(c *gin.Context) {
// UpdateSettings handles the PUT /api/settings request. // UpdateSettings handles the PUT /api/settings request.
// It receives a key-value JSON object and updates system settings. // It receives a key-value JSON object and updates system settings.
// After updating, it triggers a configuration reload. // After updating, it triggers a configuration reload.
func UpdateSettings(c *gin.Context) { func (s *Server) UpdateSettings(c *gin.Context) {
var settingsMap map[string]any var settingsMap map[string]any
if err := c.ShouldBindJSON(&settingsMap); err != nil { if err := c.ShouldBindJSON(&settingsMap); err != nil {
response.Error(c, app_errors.NewAPIError(app_errors.ErrInvalidJSON, err.Error())) response.Error(c, app_errors.NewAPIError(app_errors.ErrInvalidJSON, err.Error()))
@@ -54,22 +53,20 @@ func UpdateSettings(c *gin.Context) {
return 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())) response.Error(c, app_errors.NewAPIError(app_errors.ErrDatabase, err.Error()))
return return
} }
// 重载系统配置 // 重载系统配置
if err := settingsManager.LoadFromDatabase(); err != nil { if err := s.SettingsManager.LoadFromDatabase(); err != nil {
logrus.Errorf("Failed to reload system settings: %v", err) 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")) response.Error(c, app_errors.NewAPIError(app_errors.ErrInternalServer, "Failed to reload system settings after update"))
return return
} }
settingsManager.DisplayCurrentSettings() s.SettingsManager.DisplayCurrentSettings()
logrus.Info("Configuration reloaded successfully via API") logrus.Info("Configuration reloaded successfully via API")
response.Success(c, gin.H{ response.Success(c, gin.H{

View File

@@ -36,7 +36,7 @@ func EmbedFolder(fsEmbed embed.FS, targetPath string) static.ServeFileSystem {
} }
} }
func New( func NewRouter(
serverHandler *handler.Server, serverHandler *handler.Server,
proxyServer *proxy.ProxyServer, proxyServer *proxy.ProxyServer,
logCleanupHandler *handler.LogCleanupHandler, logCleanupHandler *handler.LogCleanupHandler,
@@ -142,8 +142,8 @@ func registerProtectedAPIRoutes(api *gin.RouterGroup, serverHandler *handler.Ser
// 设置 // 设置
settings := api.Group("/settings") settings := api.Group("/settings")
{ {
settings.GET("", handler.GetSettings) settings.GET("", serverHandler.GetSettings)
settings.PUT("", handler.UpdateSettings) settings.PUT("", serverHandler.UpdateSettings)
} }
} }

View File

@@ -2,22 +2,26 @@ package services
import ( import (
"gpt-load/internal/config" "gpt-load/internal/config"
"gpt-load/internal/db"
"gpt-load/internal/models" "gpt-load/internal/models"
"time" "time"
"github.com/sirupsen/logrus" "github.com/sirupsen/logrus"
"gorm.io/gorm"
) )
// LogCleanupService 负责清理过期的请求日志 // LogCleanupService 负责清理过期的请求日志
type LogCleanupService struct { type LogCleanupService struct {
stopCh chan struct{} db *gorm.DB
settingsManager *config.SystemSettingsManager
stopCh chan struct{}
} }
// NewLogCleanupService 创建新的日志清理服务 // NewLogCleanupService 创建新的日志清理服务
func NewLogCleanupService() *LogCleanupService { func NewLogCleanupService(db *gorm.DB, settingsManager *config.SystemSettingsManager) *LogCleanupService {
return &LogCleanupService{ return &LogCleanupService{
stopCh: make(chan struct{}), db: db,
settingsManager: settingsManager,
stopCh: make(chan struct{}),
} }
} }
@@ -54,19 +58,8 @@ func (s *LogCleanupService) run() {
// cleanupExpiredLogs 清理过期的请求日志 // cleanupExpiredLogs 清理过期的请求日志
func (s *LogCleanupService) cleanupExpiredLogs() { func (s *LogCleanupService) cleanupExpiredLogs() {
if db.DB == nil {
logrus.Error("Database connection is not available for log cleanup")
return
}
// 获取日志保留天数配置 // 获取日志保留天数配置
settingsManager := config.GetSystemSettingsManager() settings := s.settingsManager.GetSettings()
if settingsManager == nil {
logrus.Error("System settings manager is not available for log cleanup")
return
}
settings := settingsManager.GetSettings()
retentionDays := settings.RequestLogRetentionDays retentionDays := settings.RequestLogRetentionDays
if retentionDays <= 0 { if retentionDays <= 0 {
@@ -78,7 +71,7 @@ func (s *LogCleanupService) cleanupExpiredLogs() {
cutoffTime := time.Now().AddDate(0, 0, -retentionDays).UTC() 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 { if result.Error != nil {
logrus.WithError(result.Error).Error("Failed to cleanup expired request logs") logrus.WithError(result.Error).Error("Failed to cleanup expired request logs")
return return