feat: 引入dig服务依赖管理
This commit is contained in:
@@ -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
1
go.mod
@@ -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
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/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
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 (
|
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))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@@ -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{}
|
||||||
})
|
})
|
||||||
|
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
|
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")
|
||||||
|
@@ -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,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -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(¤tSettings)
|
settingsInfo := config.GenerateSettingsMetadata(¤tSettings)
|
||||||
|
|
||||||
// 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{
|
||||||
|
@@ -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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -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
|
||||||
|
Reference in New Issue
Block a user