feat: 引入dig服务依赖管理
This commit is contained in:
@@ -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("")
|
||||
|
||||
if err := server.ListenAndServe(); err != nil && err != http.ErrServerClosed {
|
||||
logrus.Fatalf("Server startup failed: %v", err)
|
||||
// Initialzie global logger
|
||||
if err := container.Invoke(func(configManager types.ConfigManager) {
|
||||
config.SetupLogger(configManager)
|
||||
}); err != nil {
|
||||
logrus.Fatalf("Failed to setup logger: %v", err)
|
||||
}
|
||||
}()
|
||||
|
||||
// Wait for interrupt signal to gracefully shutdown the server
|
||||
// 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 for graceful shutdown
|
||||
quit := make(chan os.Signal, 1)
|
||||
signal.Notify(quit, syscall.SIGINT, syscall.SIGTERM)
|
||||
<-quit
|
||||
logrus.Info("Shutting down server...")
|
||||
|
||||
// Give outstanding requests a deadline for completion
|
||||
ctx, cancel := context.WithTimeout(context.Background(), time.Duration(serverConfig.GracefulShutdownTimeout)*time.Second)
|
||||
// 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)
|
||||
}
|
||||
}
|
||||
|
1
go.mod
1
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
|
||||
|
2
go.sum
2
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=
|
||||
|
181
internal/app/app.go
Normal file
181
internal/app/app.go
Normal 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
|
||||
}
|
||||
}
|
||||
}
|
||||
}()
|
||||
}
|
@@ -3,7 +3,9 @@ package config
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
@@ -37,6 +39,7 @@ var DefaultConstants = Constants{
|
||||
// Manager implements the ConfigManager interface
|
||||
type Manager struct {
|
||||
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))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -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{}
|
||||
})
|
||||
|
87
internal/container/container.go
Normal file
87
internal/container/container.go
Normal 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
|
||||
}
|
@@ -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")
|
||||
|
@@ -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,
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -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{
|
||||
|
@@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -2,21 +2,25 @@ 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 {
|
||||
db *gorm.DB
|
||||
settingsManager *config.SystemSettingsManager
|
||||
stopCh chan struct{}
|
||||
}
|
||||
|
||||
// NewLogCleanupService 创建新的日志清理服务
|
||||
func NewLogCleanupService() *LogCleanupService {
|
||||
func NewLogCleanupService(db *gorm.DB, settingsManager *config.SystemSettingsManager) *LogCleanupService {
|
||||
return &LogCleanupService{
|
||||
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
|
||||
|
Reference in New Issue
Block a user