feat: 配置分离
This commit is contained in:
86
.env.example
86
.env.example
@@ -1,88 +1,28 @@
|
||||
# ===========================================
|
||||
# OpenAI 兼容 API 多密钥代理服务器配置文件 (Go版本)
|
||||
# ===========================================
|
||||
|
||||
# ===========================================
|
||||
# 服务器配置
|
||||
# ===========================================
|
||||
# 服务器端口
|
||||
PORT=3000
|
||||
|
||||
# 服务器主机地址
|
||||
HOST=0.0.0.0
|
||||
|
||||
# ===========================================
|
||||
# OpenAI 兼容 API 配置
|
||||
# ===========================================
|
||||
# 上游 API 地址
|
||||
OPENAI_BASE_URL=https://api.openai.com
|
||||
|
||||
# ===========================================
|
||||
# 性能优化配置
|
||||
# ===========================================
|
||||
# 最大并发请求数
|
||||
MAX_CONCURRENT_REQUESTS=100
|
||||
|
||||
# 启用 Gzip 压缩
|
||||
ENABLE_GZIP=true
|
||||
|
||||
# ===========================================
|
||||
# 日志配置
|
||||
# ===========================================
|
||||
# 日志级别 (debug, info, warn, error)
|
||||
LOG_LEVEL=info
|
||||
|
||||
# 日志格式 (text, json)
|
||||
LOG_FORMAT=text
|
||||
|
||||
# 启用文件日志
|
||||
LOG_ENABLE_FILE=false
|
||||
|
||||
# 日志文件路径
|
||||
LOG_FILE_PATH=logs/app.log
|
||||
|
||||
# 启用请求日志(生产环境可设为 false 以提高性能)
|
||||
LOG_ENABLE_REQUEST=true
|
||||
|
||||
# ===========================================
|
||||
# 认证配置
|
||||
# ===========================================
|
||||
# 项目认证密钥(可选,如果设置则启用认证)
|
||||
AUTH_KEY=sk-123456
|
||||
|
||||
# ===========================================
|
||||
# CORS 配置
|
||||
# ===========================================
|
||||
# 是否启用 CORS
|
||||
# CORS配置
|
||||
ENABLE_CORS=true
|
||||
|
||||
# 允许的来源(逗号分隔,* 表示允许所有)
|
||||
ALLOWED_ORIGINS=*
|
||||
ALLOWED_METHODS=GET,POST,PUT,DELETE,OPTIONS
|
||||
ALLOWED_HEADERS=*
|
||||
ALLOW_CREDENTIALS=false
|
||||
|
||||
# ===========================================
|
||||
# 超时配置
|
||||
# ===========================================
|
||||
# 服务器读取超时时间(秒)
|
||||
SERVER_READ_TIMEOUT=120
|
||||
# 性能配置
|
||||
MAX_CONCURRENT_REQUESTS=100
|
||||
ENABLE_GZIP=true
|
||||
|
||||
# 服务器写入超时时间(秒)
|
||||
SERVER_WRITE_TIMEOUT=1800
|
||||
# 数据库配置
|
||||
DATABASE_DSN=user:password@tcp(localhost:3306)/gpt_load?charset=utf8mb4&parseTime=True&loc=Local
|
||||
DB_AUTO_MIGRATE=true
|
||||
|
||||
# 服务器空闲超时时间(秒)
|
||||
SERVER_IDLE_TIMEOUT=120
|
||||
|
||||
# 服务器优雅关闭超时时间(秒)
|
||||
SERVER_GRACEFUL_SHUTDOWN_TIMEOUT=60
|
||||
|
||||
# 请求超时时间(秒)
|
||||
REQUEST_TIMEOUT=30
|
||||
|
||||
# 响应超时时间(秒)- 控制TLS握手和响应头接收超时
|
||||
RESPONSE_TIMEOUT=30
|
||||
|
||||
# 空闲连接超时时间(秒)- 控制连接池中空闲连接的生存时间
|
||||
IDLE_CONN_TIMEOUT=120
|
||||
DATABASE_DSN="user:password@tcp(127.0.0.1:3306)/dbname?charset=utf8mb4&parseTime=True&loc=Local"
|
||||
# 日志配置
|
||||
LOG_LEVEL=info
|
||||
LOG_FORMAT=text
|
||||
LOG_ENABLE_FILE=false
|
||||
LOG_FILE_PATH=logs/app.log
|
||||
LOG_ENABLE_REQUEST=true
|
||||
|
@@ -19,7 +19,8 @@ import (
|
||||
"gpt-load/internal/handler"
|
||||
"gpt-load/internal/models"
|
||||
"gpt-load/internal/proxy"
|
||||
"gpt-load/internal/router" // <-- 引入新的 router 包
|
||||
"gpt-load/internal/router"
|
||||
"gpt-load/internal/services"
|
||||
"gpt-load/internal/types"
|
||||
|
||||
"github.com/sirupsen/logrus"
|
||||
@@ -48,8 +49,23 @@ func main() {
|
||||
logrus.Fatalf("Failed to initialize database: %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
|
||||
displayStartupInfo(configManager)
|
||||
configManager.DisplayConfig()
|
||||
|
||||
// Start log cleanup service
|
||||
logCleanupService := services.NewLogCleanupService()
|
||||
logCleanupService.Start()
|
||||
defer logCleanupService.Stop()
|
||||
|
||||
// --- Asynchronous Request Logging Setup ---
|
||||
requestLogChan := make(chan models.RequestLog, 1000)
|
||||
@@ -67,12 +83,13 @@ func main() {
|
||||
|
||||
// Create handlers
|
||||
serverHandler := handler.NewServer(database, configManager)
|
||||
logCleanupHandler := handler.NewLogCleanupHandler(logCleanupService)
|
||||
|
||||
// Setup routes using the new router package
|
||||
appRouter := router.New(serverHandler, proxyServer, configManager, buildFS, indexPage)
|
||||
appRouter := router.New(serverHandler, proxyServer, logCleanupHandler, configManager, buildFS, indexPage)
|
||||
|
||||
// Create HTTP server with optimized timeout configuration
|
||||
serverConfig := configManager.GetServerConfig()
|
||||
serverConfig := configManager.GetEffectiveServerConfig()
|
||||
server := &http.Server{
|
||||
Addr: fmt.Sprintf("%s:%d", serverConfig.Host, serverConfig.Port),
|
||||
Handler: appRouter,
|
||||
@@ -166,48 +183,6 @@ func setupLogger(configManager types.ConfigManager) {
|
||||
}
|
||||
}
|
||||
|
||||
// displayStartupInfo shows startup information
|
||||
func displayStartupInfo(configManager types.ConfigManager) {
|
||||
serverConfig := configManager.GetServerConfig()
|
||||
openaiConfig := configManager.GetOpenAIConfig()
|
||||
authConfig := configManager.GetAuthConfig()
|
||||
corsConfig := configManager.GetCORSConfig()
|
||||
perfConfig := configManager.GetPerformanceConfig()
|
||||
logConfig := configManager.GetLogConfig()
|
||||
|
||||
logrus.Info("Current Configuration:")
|
||||
logrus.Infof(" Server: %s:%d", serverConfig.Host, serverConfig.Port)
|
||||
logrus.Infof(" Upstream URL: %s", openaiConfig.BaseURL)
|
||||
logrus.Infof(" Request timeout: %ds", openaiConfig.RequestTimeout)
|
||||
logrus.Infof(" Response timeout: %ds", openaiConfig.ResponseTimeout)
|
||||
logrus.Infof(" Idle connection timeout: %ds", openaiConfig.IdleConnTimeout)
|
||||
|
||||
authStatus := "disabled"
|
||||
if authConfig.Enabled {
|
||||
authStatus = "enabled"
|
||||
}
|
||||
logrus.Infof(" Authentication: %s", authStatus)
|
||||
|
||||
corsStatus := "disabled"
|
||||
if corsConfig.Enabled {
|
||||
corsStatus = "enabled"
|
||||
}
|
||||
logrus.Infof(" CORS: %s", corsStatus)
|
||||
logrus.Infof(" Max concurrent requests: %d", perfConfig.MaxConcurrentRequests)
|
||||
|
||||
gzipStatus := "disabled"
|
||||
if perfConfig.EnableGzip {
|
||||
gzipStatus = "enabled"
|
||||
}
|
||||
logrus.Infof(" Gzip compression: %s", gzipStatus)
|
||||
|
||||
requestLogStatus := "enabled"
|
||||
if !logConfig.EnableRequest {
|
||||
requestLogStatus = "disabled"
|
||||
}
|
||||
logrus.Infof(" Request logging: %s", requestLogStatus)
|
||||
}
|
||||
|
||||
// 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()
|
||||
|
6
go.mod
6
go.mod
@@ -5,6 +5,8 @@ go 1.23.0
|
||||
toolchain go1.24.3
|
||||
|
||||
require (
|
||||
github.com/gin-contrib/gzip v1.2.3
|
||||
github.com/gin-contrib/static v1.1.5
|
||||
github.com/gin-gonic/gin v1.10.1
|
||||
github.com/joho/godotenv v1.5.1
|
||||
github.com/sirupsen/logrus v1.9.3
|
||||
@@ -16,12 +18,9 @@ require (
|
||||
filippo.io/edwards25519 v1.1.0 // indirect
|
||||
github.com/bytedance/sonic v1.13.3 // indirect
|
||||
github.com/bytedance/sonic/loader v0.2.4 // indirect
|
||||
github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311 // indirect
|
||||
github.com/cloudwego/base64x v0.1.5 // indirect
|
||||
github.com/gabriel-vasile/mimetype v1.4.9 // indirect
|
||||
github.com/gin-contrib/gzip v1.2.3 // indirect
|
||||
github.com/gin-contrib/sse v1.1.0 // indirect
|
||||
github.com/gin-contrib/static v1.1.5 // indirect
|
||||
github.com/go-playground/locales v0.14.1 // indirect
|
||||
github.com/go-playground/universal-translator v0.18.1 // indirect
|
||||
github.com/go-playground/validator/v10 v10.27.0 // indirect
|
||||
@@ -31,6 +30,7 @@ require (
|
||||
github.com/jinzhu/now v1.1.5 // indirect
|
||||
github.com/json-iterator/go v1.1.12 // indirect
|
||||
github.com/klauspost/cpuid/v2 v2.2.11 // indirect
|
||||
github.com/kr/text v0.2.0 // indirect
|
||||
github.com/leodido/go-urn v1.4.0 // indirect
|
||||
github.com/mattn/go-isatty v0.0.20 // indirect
|
||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
|
||||
|
91
go.sum
91
go.sum
@@ -1,44 +1,25 @@
|
||||
filippo.io/edwards25519 v1.1.0 h1:FNf4tywRC1HmFuKW5xopWpigGjJKiJSV0Cqo0cJWDaA=
|
||||
filippo.io/edwards25519 v1.1.0/go.mod h1:BxyFTGdWcka3PhytdK4V28tE5sGfRvvvRV7EaN4VDT4=
|
||||
github.com/bytedance/sonic v1.5.0/go.mod h1:ED5hyg4y6t3/9Ku1R6dU/4KyJ48DZ4jPhfY1O2AihPM=
|
||||
github.com/bytedance/sonic v1.9.1 h1:6iJ6NqdoxCDr6mbY8h18oSO+cShGSMRGCEo7F2h0x8s=
|
||||
github.com/bytedance/sonic v1.9.1/go.mod h1:i736AoUSYt75HyZLoJW9ERYxcy6eaN6h4BZXU064P/U=
|
||||
github.com/bytedance/sonic v1.13.2 h1:8/H1FempDZqC4VqjptGo14QQlJx8VdZJegxs6wwfqpQ=
|
||||
github.com/bytedance/sonic v1.13.2/go.mod h1:o68xyaF9u2gvVBuGHPlUVCy+ZfmNNO5ETf1+KgkJhz4=
|
||||
github.com/bytedance/sonic v1.13.3 h1:MS8gmaH16Gtirygw7jV91pDCN33NyMrPbN7qiYhEsF0=
|
||||
github.com/bytedance/sonic v1.13.3/go.mod h1:o68xyaF9u2gvVBuGHPlUVCy+ZfmNNO5ETf1+KgkJhz4=
|
||||
github.com/bytedance/sonic/loader v0.1.1/go.mod h1:ncP89zfokxS5LZrJxl5z0UJcsk4M4yY2JpfqGeCtNLU=
|
||||
github.com/bytedance/sonic/loader v0.2.4 h1:ZWCw4stuXUsn1/+zQDqeE7JKP+QO47tz7QCNan80NzY=
|
||||
github.com/bytedance/sonic/loader v0.2.4/go.mod h1:N8A3vUdtUebEY2/VQC0MyhYeKUFosQU6FxH2JmUe6VI=
|
||||
github.com/chenzhuoyu/base64x v0.0.0-20211019084208-fb5309c8db06/go.mod h1:DH46F32mSOjUmXrMHnKwZdA8wcEefY7UVqBKYGjpdQY=
|
||||
github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311 h1:qSGYFH7+jGhDF8vLC+iwCD4WpbV1EBDSzWkJODFLams=
|
||||
github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311/go.mod h1:b583jCggY9gE99b6G5LEC39OIiVsWj+R97kbl5odCEk=
|
||||
github.com/cloudwego/base64x v0.1.5 h1:XPciSp1xaq2VCSt6lF0phncD4koWyULpl5bUxbfCyP4=
|
||||
github.com/cloudwego/base64x v0.1.5/go.mod h1:0zlkT4Wn5C6NdauXdJRhSKRlJvmclQ1hhJgA0rcu/8w=
|
||||
github.com/cloudwego/iasm v0.2.0/go.mod h1:8rXZaNYT2n95jn+zTI1sDr+IgcD2GVs0nlbbQPiEFhY=
|
||||
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
|
||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/gabriel-vasile/mimetype v1.4.2 h1:w5qFW6JKBz9Y393Y4q372O9A7cUSequkh1Q7OhCmWKU=
|
||||
github.com/gabriel-vasile/mimetype v1.4.2/go.mod h1:zApsH/mKG4w07erKIaJPFiX0Tsq9BFQgN3qGY5GnNgA=
|
||||
github.com/gabriel-vasile/mimetype v1.4.8 h1:FfZ3gj38NjllZIeJAmMhr+qKL8Wu+nOoI3GqacKw1NM=
|
||||
github.com/gabriel-vasile/mimetype v1.4.8/go.mod h1:ByKUIKGjh1ODkGM1asKUbQZOLGrPjydw3hYPU2YU9t8=
|
||||
github.com/gabriel-vasile/mimetype v1.4.9 h1:5k+WDwEsD9eTLL8Tz3L0VnmVh9QxGjRmjBvAG7U/oYY=
|
||||
github.com/gabriel-vasile/mimetype v1.4.9/go.mod h1:WnSQhFKJuBlRyLiKohA/2DtIlPFAbguNaG7QCHcyGok=
|
||||
github.com/gin-contrib/gzip v1.2.3 h1:dAhT722RuEG330ce2agAs75z7yB+NKvX/ZM1r8w0u2U=
|
||||
github.com/gin-contrib/gzip v1.2.3/go.mod h1:ad72i4Bzmaypk8M762gNXa2wkxxjbz0icRNnuLJ9a/c=
|
||||
github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE=
|
||||
github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI=
|
||||
github.com/gin-contrib/sse v1.0.0 h1:y3bT1mUWUxDpW4JLQg/HnTqV4rozuW4tC9eFKTxYI9E=
|
||||
github.com/gin-contrib/sse v1.0.0/go.mod h1:zNuFdwarAygJBht0NTKiSi3jRf6RbqeILZ9Sp6Slhe0=
|
||||
github.com/gin-contrib/sse v1.1.0 h1:n0w2GMuUpWDVp7qSpvze6fAu9iRxJY4Hmj6AmBOU05w=
|
||||
github.com/gin-contrib/sse v1.1.0/go.mod h1:hxRZ5gVpWMT7Z0B0gSNYqqsSCNIJMjzvm6fqCz9vjwM=
|
||||
github.com/gin-contrib/static v1.1.5 h1:bAPqT4KTZN+4uDY1b90eSrD1t8iNzod7Jj8njwmnzz4=
|
||||
github.com/gin-contrib/static v1.1.5/go.mod h1:8JSEXwZHcQ0uCrLPcsvnAJ4g+ODxeupP8Zetl9fd8wM=
|
||||
github.com/gin-gonic/gin v1.9.1 h1:4idEAncQnU5cB7BeOkPtxjfCSye0AAm1R0RVIqJ+Jmg=
|
||||
github.com/gin-gonic/gin v1.9.1/go.mod h1:hPrL7YrpYKXt5YId3A/Tnip5kqbEAP+KLuI3SUcPTeU=
|
||||
github.com/gin-gonic/gin v1.10.0 h1:nTuyha1TYqgedzytsKYqna+DfLos46nTv2ygFy86HFU=
|
||||
github.com/gin-gonic/gin v1.10.0/go.mod h1:4PMNQiOhvDRa013RKVbsiNwoyezlm2rm0uX/T7kzp5Y=
|
||||
github.com/gin-gonic/gin v1.10.1 h1:T0ujvqyCSqRopADpgPgiTT63DUQVSfojyME59Ei63pQ=
|
||||
github.com/gin-gonic/gin v1.10.1/go.mod h1:4PMNQiOhvDRa013RKVbsiNwoyezlm2rm0uX/T7kzp5Y=
|
||||
github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s=
|
||||
@@ -47,21 +28,14 @@ github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/o
|
||||
github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY=
|
||||
github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY=
|
||||
github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY=
|
||||
github.com/go-playground/validator/v10 v10.14.0 h1:vgvQWe3XCz3gIeFDm/HnTIbj6UGmg/+t63MyGU2n5js=
|
||||
github.com/go-playground/validator/v10 v10.14.0/go.mod h1:9iXMNT7sEkjXb0I+enO7QXmzG6QCsPWY4zveKFVRSyU=
|
||||
github.com/go-playground/validator/v10 v10.26.0 h1:SP05Nqhjcvz81uJaRfEV0YBSSSGMc/iMaVtFbr3Sw2k=
|
||||
github.com/go-playground/validator/v10 v10.26.0/go.mod h1:I5QpIEbmr8On7W0TktmJAumgzX4CA1XNl4ZmDuVHKKo=
|
||||
github.com/go-playground/validator/v10 v10.27.0 h1:w8+XrWVMhGkxOaaowyKH35gFydVHOvC0/uWoy2Fzwn4=
|
||||
github.com/go-playground/validator/v10 v10.27.0/go.mod h1:I5QpIEbmr8On7W0TktmJAumgzX4CA1XNl4ZmDuVHKKo=
|
||||
github.com/go-sql-driver/mysql v1.8.1 h1:LedoTUt/eveggdHS9qUFC1EFSa8bU2+1pZjSRpvNJ1Y=
|
||||
github.com/go-sql-driver/mysql v1.8.1/go.mod h1:wEBSXgmK//2ZFJyE+qWnIsVGmvmEKlqwuVSjsCm7DZg=
|
||||
github.com/goccy/go-json v0.10.2 h1:CrxCmQqYDkv1z7lO7Wbh2HN93uovUHgrECaO5ZrCXAU=
|
||||
github.com/goccy/go-json v0.10.2/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I=
|
||||
github.com/goccy/go-json v0.10.5 h1:Fq85nIqj+gXn/S5ahsiTlK3TmC85qgirsdTP/+DeaC4=
|
||||
github.com/goccy/go-json v0.10.5/go.mod h1:oq7eo15ShAhp70Anwd5lgX2pLfOS3QCiwU/PULtXL6M=
|
||||
github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
|
||||
github.com/google/go-cmp v0.5.5 h1:Khx7svrCpmxxtHBq5j2mp/xVjsi8hQMfNLvJFAlrGgU=
|
||||
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
|
||||
github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
|
||||
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
|
||||
github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E=
|
||||
github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc=
|
||||
@@ -72,19 +46,15 @@ github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwA
|
||||
github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
|
||||
github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
|
||||
github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg=
|
||||
github.com/klauspost/cpuid/v2 v2.2.4 h1:acbojRNwl3o09bUq+yDCtZFc1aiwaAAxtcn8YkZXnvk=
|
||||
github.com/klauspost/cpuid/v2 v2.2.4/go.mod h1:RVVoqg1df56z8g3pUjL/3lE5UfnlrJX8tyFgg4nqhuY=
|
||||
github.com/klauspost/cpuid/v2 v2.2.10 h1:tBs3QSyvjDyFTq3uoc/9xFpCuOsJQFNPiAhYdw2skhE=
|
||||
github.com/klauspost/cpuid/v2 v2.2.10/go.mod h1:hqwkgyIinND0mEev00jJYCxPNVRVXFQeu1XKlok6oO0=
|
||||
github.com/klauspost/cpuid/v2 v2.2.11 h1:0OwqZRYI2rFrjS4kvkDnqJkKHdHaRnCm68/DY4OxRzU=
|
||||
github.com/klauspost/cpuid/v2 v2.2.11/go.mod h1:hqwkgyIinND0mEev00jJYCxPNVRVXFQeu1XKlok6oO0=
|
||||
github.com/knz/go-libedit v1.10.1/go.mod h1:MZTVkCWyz0oBc7JOWP3wNAzd002ZbM/5hgShxwh4x8M=
|
||||
github.com/leodido/go-urn v1.2.4 h1:XlAE/cm/ms7TE/VMVoduSpNBoyc2dOxHs5MZSwAN63Q=
|
||||
github.com/leodido/go-urn v1.2.4/go.mod h1:7ZrI8mTSeBSHl/UaRyKQW1qZeMgak41ANeCNaVckg+4=
|
||||
github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0=
|
||||
github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk=
|
||||
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
|
||||
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
|
||||
github.com/leodido/go-urn v1.4.0 h1:WT9HwE9SGECu3lg4d/dIA+jxlljEa1/ffXKmRjqdmIQ=
|
||||
github.com/leodido/go-urn v1.4.0/go.mod h1:bvxc+MVxLKB4z00jd1z+Dvzr47oO32F/QSNjSBOlFxI=
|
||||
github.com/mattn/go-isatty v0.0.19 h1:JITubQf0MOLdlGRuRq+jtsDlekdYPia9ZFsB8h/APPA=
|
||||
github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
|
||||
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
|
||||
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
|
||||
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
||||
@@ -92,81 +62,45 @@ github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w
|
||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
||||
github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M=
|
||||
github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
|
||||
github.com/pelletier/go-toml/v2 v2.0.8 h1:0ctb6s9mE31h0/lhu+J6OPmVeDxJn+kYnJc2jZR9tGQ=
|
||||
github.com/pelletier/go-toml/v2 v2.0.8/go.mod h1:vuYfssBdrU2XDZ9bYydBu6t+6a6PYNcZljzZR9VXg+4=
|
||||
github.com/pelletier/go-toml/v2 v2.2.3 h1:YmeHyLY8mFWbdkNWwpr+qIL2bEqT0o95WSdkNHvL12M=
|
||||
github.com/pelletier/go-toml/v2 v2.2.3/go.mod h1:MfCQTFTvCcUyyvvwm1+G6H/jORL20Xlb6rzQu9GuUkc=
|
||||
github.com/pelletier/go-toml/v2 v2.2.4 h1:mye9XuhQ6gvn5h28+VilKrrPoQVanw5PMw/TB0t5Ec4=
|
||||
github.com/pelletier/go-toml/v2 v2.2.4/go.mod h1:2gIqNv+qfxSVS7cM2xJQKtLSTLUE9V8t9Stt+h56mCY=
|
||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/rogpeppe/go-internal v1.8.0 h1:FCbCCtXNOY3UtUuHUYaghJg4y7Fd14rXifAYUAtL9R8=
|
||||
github.com/rogpeppe/go-internal v1.8.0/go.mod h1:WmiCO8CzOY8rg0OYDC4/i/2WRWAB6poM+XZ2dLUbcbE=
|
||||
github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ=
|
||||
github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=
|
||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
|
||||
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
|
||||
github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA=
|
||||
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
||||
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
|
||||
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
|
||||
github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
|
||||
github.com/stretchr/testify v1.8.3 h1:RP3t2pwF7cMEbC1dqtB6poj3niw/9gnV4Cjg5oW5gtY=
|
||||
github.com/stretchr/testify v1.8.3/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
|
||||
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
|
||||
github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
|
||||
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
|
||||
github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI=
|
||||
github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08=
|
||||
github.com/ugorji/go/codec v1.2.11 h1:BMaWp1Bb6fHwEtbplGBGJ498wD+LKlNSl25MjdZY4dU=
|
||||
github.com/ugorji/go/codec v1.2.11/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg=
|
||||
github.com/ugorji/go/codec v1.2.12 h1:9LC83zGrHhuUA9l16C9AHXAqEV/2wBQ4nkvumAE65EE=
|
||||
github.com/ugorji/go/codec v1.2.12/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg=
|
||||
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=
|
||||
golang.org/x/arch v0.0.0-20210923205945-b76863e36670/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8=
|
||||
golang.org/x/arch v0.3.0 h1:02VY4/ZcO/gBOH6PUaoiptASxtXU10jazRCP865E97k=
|
||||
golang.org/x/arch v0.3.0/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8=
|
||||
golang.org/x/arch v0.15.0 h1:QtOrQd0bTUnhNVNndMpLHNWrDmYzZ2KDqSrEymqInZw=
|
||||
golang.org/x/arch v0.15.0/go.mod h1:JmwW7aLIoRUKgaTzhkiEFxvcEiQGyOg9BMonBJUS7EE=
|
||||
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.9.0 h1:LF6fAI+IutBocDJ2OT0Q1g8plpYljMZ4+lty+dsqw3g=
|
||||
golang.org/x/crypto v0.9.0/go.mod h1:yrmDGqONDYtNj3tH8X9dzUun2m2lzPa9ngI6/RUPGR0=
|
||||
golang.org/x/crypto v0.36.0 h1:AnAEvhDddvBdpY+uR+MyHmuZzzNqXSe/GvuDeob5L34=
|
||||
golang.org/x/crypto v0.36.0/go.mod h1:Y4J0ReaxCR1IMaabaSMugxJES1EpwhBHhv2bDHklZvc=
|
||||
golang.org/x/crypto v0.39.0 h1:SHs+kF4LP+f+p14esP5jAoDpHU8Gu/v9lFRK6IT5imM=
|
||||
golang.org/x/crypto v0.39.0/go.mod h1:L+Xg3Wf6HoL4Bn4238Z6ft6KfEpN0tJGo53AAPC632U=
|
||||
golang.org/x/net v0.10.0 h1:X2//UzNDwYmtCLn7To6G58Wr6f5ahEAQgKNzv9Y951M=
|
||||
golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg=
|
||||
golang.org/x/net v0.38.0 h1:vRMAPTMaeGqVhG5QyLJHqNDwecKTomGeqbnfZyKlBI8=
|
||||
golang.org/x/net v0.38.0/go.mod h1:ivrbrMbzFq5J41QOQh0siUuly180yBYtLp+CKbEaFx8=
|
||||
golang.org/x/net v0.41.0 h1:vBTly1HeNPEn3wtREYfy4GZ/NECgw2Cnl+nK6Nz3uvw=
|
||||
golang.org/x/net v0.41.0/go.mod h1:B/K4NNqkfmg07DQYrbwvSluqCJOOXwUjeb/5lOisjbA=
|
||||
golang.org/x/sys v0.0.0-20220704084225-05e143d24a9e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.8.0 h1:EBmGv8NaZBZTWvrbjNoL6HVt+IVy3QDQpJs7VRIw3tU=
|
||||
golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.31.0 h1:ioabZlmFYtWhL+TRYpcnNlLwhyxaM9kWTDEmfnprqik=
|
||||
golang.org/x/sys v0.31.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
|
||||
golang.org/x/sys v0.33.0 h1:q3i8TbbEz+JRD9ywIRlyRAQbM0qF7hu24q3teo2hbuw=
|
||||
golang.org/x/sys v0.33.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
|
||||
golang.org/x/text v0.20.0 h1:gK/Kv2otX8gz+wn7Rmb3vT96ZwuoxnQlY+HlJVj7Qug=
|
||||
golang.org/x/text v0.20.0/go.mod h1:D4IsuqiFMhST5bX19pQ9ikHC2GsaKyk/oF+pn3ducp4=
|
||||
golang.org/x/text v0.23.0 h1:D71I7dUrlY+VX0gQShAThNGHFxZ13dGLBHQLVl1mJlY=
|
||||
golang.org/x/text v0.23.0/go.mod h1:/BLNzu4aZCJ1+kcD0DNRotWKage4q2rGVAg4o22unh4=
|
||||
golang.org/x/text v0.26.0 h1:P42AVeLghgTYr4+xUnTRKDMqpar+PtX7KWuNQL21L8M=
|
||||
golang.org/x/text v0.26.0/go.mod h1:QK15LZJUUQVJxhz7wXgxSy/CJaTFjd0G+YLonydOVQA=
|
||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4=
|
||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
|
||||
google.golang.org/protobuf v1.30.0 h1:kPPoIgf3TsEvrm0PFe15JQ+570QVxYzEvvHqChK+cng=
|
||||
google.golang.org/protobuf v1.30.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
|
||||
google.golang.org/protobuf v1.36.6 h1:z1NpPI8ku2WgiWnf+t9wTPsn6eP1L7ksHUlkfLvd9xY=
|
||||
google.golang.org/protobuf v1.36.6/go.mod h1:jduwjTPXsFjZGTmRluh+L6NjiWu7pchiJ2/5YcXBHnY=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
|
||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
|
||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
@@ -175,4 +109,3 @@ gorm.io/driver/mysql v1.6.0/go.mod h1:D/oCC2GWK3M/dqoLxnOlaNKmXz8WNTfcS9y5ovaSqK
|
||||
gorm.io/gorm v1.30.0 h1:qbT5aPv1UH8gI99OsRlvDToLxW5zR7FzS9acZDOZcgs=
|
||||
gorm.io/gorm v1.30.0/go.mod h1:8Z33v652h4//uMA76KjeDH8mJXPm1QNCYrMeatR0DOE=
|
||||
nullprogram.com/x/optparse v1.0.0/go.mod h1:KdyPE+Igbe0jQUrVfMqDMeJQIJZEuyV7pjYmp6pbG50=
|
||||
rsc.io/pdf v0.1.1/go.mod h1:n8OzWcQ6Sp37PL01nO98y4iUCRdTGarVfzxY20ICaU4=
|
||||
|
@@ -3,7 +3,6 @@ package config
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/url"
|
||||
"os"
|
||||
"strconv"
|
||||
"strings"
|
||||
@@ -70,18 +69,23 @@ func (m *Manager) ReloadConfig() error {
|
||||
|
||||
config := &Config{
|
||||
Server: types.ServerConfig{
|
||||
Port: parseInteger(os.Getenv("PORT"), 3000),
|
||||
Host: getEnvOrDefault("HOST", "0.0.0.0"),
|
||||
ReadTimeout: parseInteger(os.Getenv("SERVER_READ_TIMEOUT"), 120),
|
||||
WriteTimeout: parseInteger(os.Getenv("SERVER_WRITE_TIMEOUT"), 1800),
|
||||
IdleTimeout: parseInteger(os.Getenv("SERVER_IDLE_TIMEOUT"), 120),
|
||||
GracefulShutdownTimeout: parseInteger(os.Getenv("SERVER_GRACEFUL_SHUTDOWN_TIMEOUT"), 60),
|
||||
Port: parseInteger(os.Getenv("PORT"), 3000),
|
||||
Host: getEnvOrDefault("HOST", "0.0.0.0"),
|
||||
// Server timeout configs now come from system settings, not environment
|
||||
// Using defaults here, will be overridden by system settings
|
||||
ReadTimeout: 120,
|
||||
WriteTimeout: 1800,
|
||||
IdleTimeout: 120,
|
||||
GracefulShutdownTimeout: 60,
|
||||
},
|
||||
OpenAI: types.OpenAIConfig{
|
||||
BaseURLs: parseArray(os.Getenv("OPENAI_BASE_URL"), []string{"https://api.openai.com"}),
|
||||
RequestTimeout: parseInteger(os.Getenv("REQUEST_TIMEOUT"), DefaultConstants.DefaultTimeout),
|
||||
ResponseTimeout: parseInteger(os.Getenv("RESPONSE_TIMEOUT"), 30),
|
||||
IdleConnTimeout: parseInteger(os.Getenv("IDLE_CONN_TIMEOUT"), 120),
|
||||
// OPENAI_BASE_URL is removed from environment config
|
||||
// Base URLs will be configured per group
|
||||
BaseURLs: []string{}, // Will be set per group
|
||||
// Timeout configs now come from system settings
|
||||
RequestTimeout: 30,
|
||||
ResponseTimeout: 30,
|
||||
IdleConnTimeout: 120,
|
||||
},
|
||||
Auth: types.AuthConfig{
|
||||
Key: os.Getenv("AUTH_KEY"),
|
||||
@@ -113,29 +117,28 @@ func (m *Manager) ReloadConfig() error {
|
||||
return err
|
||||
}
|
||||
|
||||
logrus.Info("Configuration reloaded successfully")
|
||||
m.DisplayConfig()
|
||||
logrus.Info("Environment configuration reloaded successfully")
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetServerConfig returns server configuration
|
||||
func (m *Manager) GetServerConfig() types.ServerConfig {
|
||||
return m.config.Server
|
||||
}
|
||||
// func (m *Manager) GetServerConfig() types.ServerConfig {
|
||||
// return m.config.Server
|
||||
// }
|
||||
|
||||
// GetOpenAIConfig returns OpenAI configuration
|
||||
func (m *Manager) GetOpenAIConfig() types.OpenAIConfig {
|
||||
config := m.config.OpenAI
|
||||
if len(config.BaseURLs) > 1 {
|
||||
// Use atomic counter for thread-safe round-robin
|
||||
index := atomic.AddUint64(&m.roundRobinCounter, 1) - 1
|
||||
config.BaseURL = config.BaseURLs[index%uint64(len(config.BaseURLs))]
|
||||
} else if len(config.BaseURLs) == 1 {
|
||||
config.BaseURL = config.BaseURLs[0]
|
||||
}
|
||||
return config
|
||||
}
|
||||
// func (m *Manager) GetOpenAIConfig() types.OpenAIConfig {
|
||||
// config := m.config.OpenAI
|
||||
// if len(config.BaseURLs) > 1 {
|
||||
// // Use atomic counter for thread-safe round-robin
|
||||
// index := atomic.AddUint64(&m.roundRobinCounter, 1) - 1
|
||||
// config.BaseURL = config.BaseURLs[index%uint64(len(config.BaseURLs))]
|
||||
// } else if len(config.BaseURLs) == 1 {
|
||||
// config.BaseURL = config.BaseURLs[0]
|
||||
// }
|
||||
// return config
|
||||
// }
|
||||
|
||||
// GetAuthConfig returns authentication configuration
|
||||
func (m *Manager) GetAuthConfig() types.AuthConfig {
|
||||
@@ -157,6 +160,51 @@ func (m *Manager) GetLogConfig() types.LogConfig {
|
||||
return m.config.Log
|
||||
}
|
||||
|
||||
// GetEffectiveServerConfig returns server configuration merged with system settings
|
||||
func (m *Manager) GetEffectiveServerConfig() types.ServerConfig {
|
||||
config := m.config.Server
|
||||
|
||||
// Merge with system settings
|
||||
settingsManager := GetSystemSettingsManager()
|
||||
systemSettings := settingsManager.GetSettings()
|
||||
|
||||
config.ReadTimeout = systemSettings.ServerReadTimeout
|
||||
config.WriteTimeout = systemSettings.ServerWriteTimeout
|
||||
config.IdleTimeout = systemSettings.ServerIdleTimeout
|
||||
config.GracefulShutdownTimeout = systemSettings.ServerGracefulShutdownTimeout
|
||||
|
||||
return config
|
||||
}
|
||||
|
||||
// GetEffectiveOpenAIConfig returns OpenAI configuration merged with system settings and group config
|
||||
func (m *Manager) GetEffectiveOpenAIConfig(groupConfig map[string]any) types.OpenAIConfig {
|
||||
config := m.config.OpenAI
|
||||
|
||||
// Merge with system settings
|
||||
settingsManager := GetSystemSettingsManager()
|
||||
effectiveSettings := settingsManager.GetEffectiveConfig(groupConfig)
|
||||
|
||||
config.RequestTimeout = effectiveSettings.RequestTimeout
|
||||
config.ResponseTimeout = effectiveSettings.ResponseTimeout
|
||||
config.IdleConnTimeout = effectiveSettings.IdleConnTimeout
|
||||
|
||||
// Apply round-robin for multiple URLs if configured
|
||||
if len(config.BaseURLs) > 1 {
|
||||
index := atomic.AddUint64(&m.roundRobinCounter, 1) - 1
|
||||
config.BaseURL = config.BaseURLs[index%uint64(len(config.BaseURLs))]
|
||||
} else if len(config.BaseURLs) == 1 {
|
||||
config.BaseURL = config.BaseURLs[0]
|
||||
}
|
||||
|
||||
return config
|
||||
}
|
||||
|
||||
// GetEffectiveLogConfig returns log configuration (now uses environment config only)
|
||||
// func (m *Manager) GetEffectiveLogConfig() types.LogConfig {
|
||||
// // Log configuration is now managed via environment variables only
|
||||
// return m.config.Log
|
||||
// }
|
||||
|
||||
// Validate validates the configuration
|
||||
func (m *Manager) Validate() error {
|
||||
var validationErrors []string
|
||||
@@ -166,22 +214,6 @@ func (m *Manager) Validate() error {
|
||||
validationErrors = append(validationErrors, fmt.Sprintf("port must be between %d-%d", DefaultConstants.MinPort, DefaultConstants.MaxPort))
|
||||
}
|
||||
|
||||
// Validate timeout
|
||||
if m.config.OpenAI.RequestTimeout < DefaultConstants.MinTimeout {
|
||||
validationErrors = append(validationErrors, fmt.Sprintf("request timeout cannot be less than %ds", DefaultConstants.MinTimeout))
|
||||
}
|
||||
|
||||
// Validate upstream URL format
|
||||
if len(m.config.OpenAI.BaseURLs) == 0 {
|
||||
validationErrors = append(validationErrors, "at least one upstream API URL is required")
|
||||
}
|
||||
for _, baseURL := range m.config.OpenAI.BaseURLs {
|
||||
if _, err := url.Parse(baseURL); err != nil {
|
||||
validationErrors = append(validationErrors, fmt.Sprintf("invalid upstream API URL format: %s", baseURL))
|
||||
}
|
||||
}
|
||||
|
||||
// Validate performance configuration
|
||||
if m.config.Performance.MaxConcurrentRequests < 1 {
|
||||
validationErrors = append(validationErrors, "max concurrent requests cannot be less than 1")
|
||||
}
|
||||
@@ -199,34 +231,37 @@ func (m *Manager) Validate() error {
|
||||
|
||||
// DisplayConfig displays current configuration information
|
||||
func (m *Manager) DisplayConfig() {
|
||||
serverConfig := m.GetEffectiveServerConfig()
|
||||
// openaiConfig := m.GetOpenAIConfig()
|
||||
authConfig := m.GetAuthConfig()
|
||||
corsConfig := m.GetCORSConfig()
|
||||
perfConfig := m.GetPerformanceConfig()
|
||||
logConfig := m.GetLogConfig()
|
||||
|
||||
logrus.Info("Current Configuration:")
|
||||
logrus.Infof(" Server: %s:%d", m.config.Server.Host, m.config.Server.Port)
|
||||
logrus.Infof(" Upstream URLs: %s", strings.Join(m.config.OpenAI.BaseURLs, ", "))
|
||||
logrus.Infof(" Request timeout: %ds", m.config.OpenAI.RequestTimeout)
|
||||
logrus.Infof(" Response timeout: %ds", m.config.OpenAI.ResponseTimeout)
|
||||
logrus.Infof(" Idle connection timeout: %ds", m.config.OpenAI.IdleConnTimeout)
|
||||
logrus.Infof(" Server: %s:%d", serverConfig.Host, serverConfig.Port)
|
||||
|
||||
authStatus := "disabled"
|
||||
if m.config.Auth.Enabled {
|
||||
if authConfig.Enabled {
|
||||
authStatus = "enabled"
|
||||
}
|
||||
logrus.Infof(" Authentication: %s", authStatus)
|
||||
|
||||
corsStatus := "disabled"
|
||||
if m.config.CORS.Enabled {
|
||||
if corsConfig.Enabled {
|
||||
corsStatus = "enabled"
|
||||
}
|
||||
logrus.Infof(" CORS: %s", corsStatus)
|
||||
logrus.Infof(" Max concurrent requests: %d", m.config.Performance.MaxConcurrentRequests)
|
||||
logrus.Infof(" Max concurrent requests: %d", perfConfig.MaxConcurrentRequests)
|
||||
|
||||
gzipStatus := "disabled"
|
||||
if m.config.Performance.EnableGzip {
|
||||
if perfConfig.EnableGzip {
|
||||
gzipStatus = "enabled"
|
||||
}
|
||||
logrus.Infof(" Gzip compression: %s", gzipStatus)
|
||||
|
||||
requestLogStatus := "enabled"
|
||||
if !m.config.Log.EnableRequest {
|
||||
if !logConfig.EnableRequest {
|
||||
requestLogStatus = "disabled"
|
||||
}
|
||||
logrus.Infof(" Request logging: %s", requestLogStatus)
|
||||
|
362
internal/config/system_settings.go
Normal file
362
internal/config/system_settings.go
Normal file
@@ -0,0 +1,362 @@
|
||||
package config
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"gpt-load/internal/db"
|
||||
"gpt-load/internal/models"
|
||||
"reflect"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
"github.com/sirupsen/logrus"
|
||||
"gorm.io/gorm/clause"
|
||||
)
|
||||
|
||||
// SystemSettings 定义所有系统配置项
|
||||
// 使用结构体标签作为唯一事实来源
|
||||
type SystemSettings struct {
|
||||
// 负载均衡和重试配置
|
||||
BlacklistThreshold int `json:"blacklist_threshold" default:"1" desc:"Error count before blacklisting a key" validate:"min=0"`
|
||||
MaxRetries int `json:"max_retries" default:"3" desc:"Maximum retry attempts with different keys" validate:"min=0"`
|
||||
|
||||
// 服务器超时配置 (秒)
|
||||
ServerReadTimeout int `json:"server_read_timeout" default:"120" desc:"HTTP server read timeout in seconds" validate:"min=1"`
|
||||
ServerWriteTimeout int `json:"server_write_timeout" default:"1800" desc:"HTTP server write timeout in seconds" validate:"min=1"`
|
||||
ServerIdleTimeout int `json:"server_idle_timeout" default:"120" desc:"HTTP server idle timeout in seconds" validate:"min=1"`
|
||||
ServerGracefulShutdownTimeout int `json:"server_graceful_shutdown_timeout" default:"60" desc:"Graceful shutdown timeout in seconds" validate:"min=1"`
|
||||
|
||||
// 请求超时配置 (秒)
|
||||
RequestTimeout int `json:"request_timeout" default:"30" desc:"Request timeout in seconds" validate:"min=1"`
|
||||
ResponseTimeout int `json:"response_timeout" default:"30" desc:"Response timeout in seconds (TLS handshake & response header)" validate:"min=1"`
|
||||
IdleConnTimeout int `json:"idle_conn_timeout" default:"120" desc:"Idle connection timeout in seconds" validate:"min=1"`
|
||||
|
||||
// 性能配置
|
||||
// MaxConcurrentRequests int `json:"max_concurrent_requests" default:"100" desc:"Maximum number of concurrent requests" validate:"min=1"`
|
||||
|
||||
// 请求日志配置(数据库日志)
|
||||
RequestLogRetentionDays int `json:"request_log_retention_days" default:"30" desc:"Number of days to retain request logs in database" validate:"min=1"`
|
||||
}
|
||||
|
||||
// GenerateSettingsMetadata 使用反射从 SystemSettings 结构体动态生成元数据
|
||||
func GenerateSettingsMetadata(s *SystemSettings) []models.SystemSettingInfo {
|
||||
var settingsInfo []models.SystemSettingInfo
|
||||
v := reflect.ValueOf(s).Elem()
|
||||
t := v.Type()
|
||||
|
||||
for i := 0; i < t.NumField(); i++ {
|
||||
field := t.Field(i)
|
||||
fieldValue := v.Field(i)
|
||||
|
||||
jsonTag := field.Tag.Get("json")
|
||||
if jsonTag == "" {
|
||||
continue
|
||||
}
|
||||
|
||||
descTag := field.Tag.Get("desc")
|
||||
defaultTag := field.Tag.Get("default")
|
||||
validateTag := field.Tag.Get("validate")
|
||||
|
||||
var minValue *int
|
||||
if strings.HasPrefix(validateTag, "min=") {
|
||||
valStr := strings.TrimPrefix(validateTag, "min=")
|
||||
if val, err := strconv.Atoi(valStr); err == nil {
|
||||
minValue = &val
|
||||
}
|
||||
}
|
||||
|
||||
info := models.SystemSettingInfo{
|
||||
Key: jsonTag,
|
||||
Value: fieldValue.Interface(),
|
||||
Type: field.Type.String(),
|
||||
DefaultValue: defaultTag,
|
||||
Description: descTag,
|
||||
MinValue: minValue,
|
||||
}
|
||||
settingsInfo = append(settingsInfo, info)
|
||||
}
|
||||
return settingsInfo
|
||||
}
|
||||
|
||||
// DefaultSystemSettings 返回默认的系统配置
|
||||
func DefaultSystemSettings() SystemSettings {
|
||||
s := SystemSettings{}
|
||||
v := reflect.ValueOf(&s).Elem()
|
||||
t := v.Type()
|
||||
|
||||
for i := 0; i < t.NumField(); i++ {
|
||||
field := t.Field(i)
|
||||
defaultTag := field.Tag.Get("default")
|
||||
if defaultTag == "" {
|
||||
continue
|
||||
}
|
||||
|
||||
fieldValue := v.Field(i)
|
||||
if fieldValue.CanSet() {
|
||||
switch fieldValue.Kind() {
|
||||
case reflect.Int:
|
||||
if val, err := strconv.ParseInt(defaultTag, 10, 64); err == nil {
|
||||
fieldValue.SetInt(val)
|
||||
}
|
||||
// Add cases for other types like string, bool if needed
|
||||
}
|
||||
}
|
||||
}
|
||||
return s
|
||||
}
|
||||
|
||||
// SystemSettingsManager 管理系统配置
|
||||
type SystemSettingsManager struct {
|
||||
settings SystemSettings
|
||||
mu sync.RWMutex
|
||||
}
|
||||
|
||||
var globalSystemSettings *SystemSettingsManager
|
||||
var once sync.Once
|
||||
|
||||
// GetSystemSettingsManager 获取全局系统配置管理器单例
|
||||
func GetSystemSettingsManager() *SystemSettingsManager {
|
||||
once.Do(func() {
|
||||
globalSystemSettings = &SystemSettingsManager{}
|
||||
})
|
||||
return globalSystemSettings
|
||||
}
|
||||
|
||||
// InitializeSystemSettings 初始化系统配置到数据库
|
||||
func (sm *SystemSettingsManager) InitializeSystemSettings() error {
|
||||
if db.DB == nil {
|
||||
return fmt.Errorf("database not initialized")
|
||||
}
|
||||
|
||||
defaultSettings := DefaultSystemSettings()
|
||||
metadata := GenerateSettingsMetadata(&defaultSettings)
|
||||
|
||||
for _, meta := range metadata {
|
||||
var existing models.SystemSetting
|
||||
err := db.DB.Where("setting_key = ?", meta.Key).First(&existing).Error
|
||||
if err != nil { // Not found
|
||||
setting := models.SystemSetting{
|
||||
SettingKey: meta.Key,
|
||||
SettingValue: fmt.Sprintf("%v", meta.DefaultValue),
|
||||
Description: meta.Description,
|
||||
}
|
||||
if err := db.DB.Create(&setting).Error; err != nil {
|
||||
logrus.Errorf("Failed to initialize setting %s: %v", setting.SettingKey, err)
|
||||
return err
|
||||
}
|
||||
logrus.Infof("Initialized system setting: %s = %s", setting.SettingKey, setting.SettingValue)
|
||||
}
|
||||
}
|
||||
|
||||
// 加载配置到内存
|
||||
return sm.LoadFromDatabase()
|
||||
}
|
||||
|
||||
// LoadFromDatabase 从数据库加载系统配置到内存
|
||||
func (sm *SystemSettingsManager) LoadFromDatabase() error {
|
||||
if db.DB == nil {
|
||||
return fmt.Errorf("database not initialized")
|
||||
}
|
||||
|
||||
var settings []models.SystemSetting
|
||||
if err := db.DB.Find(&settings).Error; err != nil {
|
||||
return fmt.Errorf("failed to load system settings: %w", err)
|
||||
}
|
||||
|
||||
settingsMap := make(map[string]string)
|
||||
for _, setting := range settings {
|
||||
settingsMap[setting.SettingKey] = setting.SettingValue
|
||||
}
|
||||
|
||||
sm.mu.Lock()
|
||||
defer sm.mu.Unlock()
|
||||
|
||||
// 使用默认值,然后用数据库中的值覆盖
|
||||
sm.settings = DefaultSystemSettings()
|
||||
sm.mapToStruct(settingsMap, &sm.settings)
|
||||
|
||||
logrus.Info("System settings loaded from database")
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetSettings 获取当前系统配置
|
||||
func (sm *SystemSettingsManager) GetSettings() SystemSettings {
|
||||
sm.mu.RLock()
|
||||
defer sm.mu.RUnlock()
|
||||
return sm.settings
|
||||
}
|
||||
|
||||
// UpdateSettings 更新系统配置
|
||||
func (sm *SystemSettingsManager) UpdateSettings(settingsMap map[string]string) error {
|
||||
if db.DB == nil {
|
||||
return fmt.Errorf("database not initialized")
|
||||
}
|
||||
|
||||
// 验证配置项
|
||||
if err := sm.ValidateSettings(settingsMap); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// 更新数据库
|
||||
var settingsToUpdate []models.SystemSetting
|
||||
for key, value := range settingsMap {
|
||||
settingsToUpdate = append(settingsToUpdate, models.SystemSetting{
|
||||
SettingKey: key,
|
||||
SettingValue: value,
|
||||
})
|
||||
}
|
||||
|
||||
if len(settingsToUpdate) > 0 {
|
||||
if err := db.DB.Clauses(clause.OnConflict{
|
||||
Columns: []clause.Column{{Name: "setting_key"}},
|
||||
DoUpdates: clause.AssignmentColumns([]string{"setting_value", "updated_at"}),
|
||||
}).Create(&settingsToUpdate).Error; err != nil {
|
||||
return fmt.Errorf("failed to update system settings: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
// 重新加载配置到内存
|
||||
return sm.LoadFromDatabase()
|
||||
}
|
||||
|
||||
// GetEffectiveConfig 获取有效配置 (系统配置 + 分组覆盖)
|
||||
func (sm *SystemSettingsManager) GetEffectiveConfig(groupConfig map[string]any) SystemSettings {
|
||||
sm.mu.RLock()
|
||||
defer sm.mu.RUnlock()
|
||||
|
||||
// 从系统配置开始
|
||||
effectiveConfig := sm.settings
|
||||
v := reflect.ValueOf(&effectiveConfig).Elem()
|
||||
t := v.Type()
|
||||
|
||||
// 创建一个从 json 标签到字段名的映射
|
||||
jsonToField := make(map[string]string)
|
||||
for i := 0; i < t.NumField(); i++ {
|
||||
field := t.Field(i)
|
||||
jsonTag := strings.Split(field.Tag.Get("json"), ",")[0]
|
||||
if jsonTag != "" {
|
||||
jsonToField[jsonTag] = field.Name
|
||||
}
|
||||
}
|
||||
|
||||
// 应用分组配置覆盖
|
||||
for key, val := range groupConfig {
|
||||
if fieldName, ok := jsonToField[key]; ok {
|
||||
fieldValue := v.FieldByName(fieldName)
|
||||
if fieldValue.IsValid() && fieldValue.CanSet() {
|
||||
if intVal, err := interfaceToInt(val); err == nil {
|
||||
fieldValue.SetInt(int64(intVal))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return effectiveConfig
|
||||
}
|
||||
|
||||
// ValidateSettings 验证系统配置的有效性
|
||||
func (sm *SystemSettingsManager) ValidateSettings(settingsMap map[string]string) error {
|
||||
tempSettings := DefaultSystemSettings()
|
||||
v := reflect.ValueOf(&tempSettings).Elem()
|
||||
t := v.Type()
|
||||
jsonToField := make(map[string]reflect.StructField)
|
||||
for i := 0; i < t.NumField(); i++ {
|
||||
field := t.Field(i)
|
||||
jsonTag := field.Tag.Get("json")
|
||||
if jsonTag != "" {
|
||||
jsonToField[jsonTag] = field
|
||||
}
|
||||
}
|
||||
|
||||
for key, value := range settingsMap {
|
||||
field, ok := jsonToField[key]
|
||||
if !ok {
|
||||
return fmt.Errorf("invalid setting key: %s", key)
|
||||
}
|
||||
|
||||
validateTag := field.Tag.Get("validate")
|
||||
if validateTag == "" {
|
||||
continue
|
||||
}
|
||||
|
||||
switch field.Type.Kind() {
|
||||
case reflect.Int:
|
||||
intVal, err := strconv.Atoi(value)
|
||||
if err != nil {
|
||||
return fmt.Errorf("invalid integer value for %s: %s", key, value)
|
||||
}
|
||||
if strings.HasPrefix(validateTag, "min=") {
|
||||
minValStr := strings.TrimPrefix(validateTag, "min=")
|
||||
minVal, _ := strconv.Atoi(minValStr)
|
||||
if intVal < minVal {
|
||||
return fmt.Errorf("value for %s (%d) is below minimum value (%d)", key, intVal, minVal)
|
||||
}
|
||||
}
|
||||
default:
|
||||
return fmt.Errorf("unsupported type for setting key validation: %s", key)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// DisplayCurrentSettings 显示当前系统配置信息
|
||||
func (sm *SystemSettingsManager) DisplayCurrentSettings() {
|
||||
sm.mu.RLock()
|
||||
defer sm.mu.RUnlock()
|
||||
|
||||
logrus.Info("Current System Settings:")
|
||||
logrus.Infof(" Blacklist threshold: %d", sm.settings.BlacklistThreshold)
|
||||
logrus.Infof(" Max retries: %d", sm.settings.MaxRetries)
|
||||
logrus.Infof(" Server timeouts: read=%ds, write=%ds, idle=%ds, shutdown=%ds",
|
||||
sm.settings.ServerReadTimeout, sm.settings.ServerWriteTimeout,
|
||||
sm.settings.ServerIdleTimeout, sm.settings.ServerGracefulShutdownTimeout)
|
||||
logrus.Infof(" Request timeouts: request=%ds, response=%ds, idle_conn=%ds",
|
||||
sm.settings.RequestTimeout, sm.settings.ResponseTimeout, sm.settings.IdleConnTimeout)
|
||||
// logrus.Infof(" Performance: max_concurrent=%d", sm.settings.MaxConcurrentRequests)
|
||||
logrus.Infof(" Request log retention: %d days", sm.settings.RequestLogRetentionDays)
|
||||
}
|
||||
|
||||
// 辅助方法
|
||||
|
||||
func (sm *SystemSettingsManager) mapToStruct(m map[string]string, s *SystemSettings) {
|
||||
v := reflect.ValueOf(s).Elem()
|
||||
t := v.Type()
|
||||
|
||||
// 创建一个从 json 标签到字段名的映射
|
||||
jsonToField := make(map[string]string)
|
||||
for i := 0; i < t.NumField(); i++ {
|
||||
field := t.Field(i)
|
||||
jsonTag := strings.Split(field.Tag.Get("json"), ",")[0]
|
||||
if jsonTag != "" {
|
||||
jsonToField[jsonTag] = field.Name
|
||||
}
|
||||
}
|
||||
|
||||
for key, valStr := range m {
|
||||
if fieldName, ok := jsonToField[key]; ok {
|
||||
fieldValue := v.FieldByName(fieldName)
|
||||
if fieldValue.IsValid() && fieldValue.CanSet() {
|
||||
// 假设所有字段都是 int 类型
|
||||
if intVal, err := strconv.Atoi(valStr); err == nil {
|
||||
fieldValue.SetInt(int64(intVal))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 工具函数
|
||||
|
||||
func interfaceToInt(val interface{}) (int, error) {
|
||||
switch v := val.(type) {
|
||||
case int:
|
||||
return v, nil
|
||||
case float64:
|
||||
return int(v), nil
|
||||
case string:
|
||||
return strconv.Atoi(v)
|
||||
default:
|
||||
return 0, fmt.Errorf("cannot convert to int: %v", val)
|
||||
}
|
||||
}
|
@@ -49,7 +49,7 @@ func (s *Server) Login(c *gin.Context) {
|
||||
}
|
||||
|
||||
authConfig := s.config.GetAuthConfig()
|
||||
|
||||
|
||||
if !authConfig.Enabled {
|
||||
c.JSON(http.StatusOK, LoginResponse{
|
||||
Success: true,
|
||||
@@ -102,70 +102,3 @@ func (s *Server) Health(c *gin.Context) {
|
||||
"uptime": uptime,
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
// GetConfig returns configuration information (for debugging)
|
||||
func (s *Server) GetConfig(c *gin.Context) {
|
||||
// Only allow in development mode or with special header
|
||||
if c.GetHeader("X-Debug-Config") != "true" {
|
||||
c.JSON(http.StatusForbidden, gin.H{
|
||||
"error": "Access denied",
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
serverConfig := s.config.GetServerConfig()
|
||||
openaiConfig := s.config.GetOpenAIConfig()
|
||||
authConfig := s.config.GetAuthConfig()
|
||||
corsConfig := s.config.GetCORSConfig()
|
||||
perfConfig := s.config.GetPerformanceConfig()
|
||||
logConfig := s.config.GetLogConfig()
|
||||
|
||||
// Sanitize sensitive information
|
||||
sanitizedConfig := gin.H{
|
||||
"server": gin.H{
|
||||
"host": serverConfig.Host,
|
||||
"port": serverConfig.Port,
|
||||
},
|
||||
"openai": gin.H{
|
||||
"base_url": openaiConfig.BaseURL,
|
||||
"request_timeout": openaiConfig.RequestTimeout,
|
||||
"response_timeout": openaiConfig.ResponseTimeout,
|
||||
"idle_conn_timeout": openaiConfig.IdleConnTimeout,
|
||||
},
|
||||
"auth": gin.H{
|
||||
"enabled": authConfig.Enabled,
|
||||
// Don't expose the actual key
|
||||
},
|
||||
"cors": gin.H{
|
||||
"enabled": corsConfig.Enabled,
|
||||
"allowed_origins": corsConfig.AllowedOrigins,
|
||||
"allowed_methods": corsConfig.AllowedMethods,
|
||||
"allowed_headers": corsConfig.AllowedHeaders,
|
||||
"allow_credentials": corsConfig.AllowCredentials,
|
||||
},
|
||||
"performance": gin.H{
|
||||
"max_concurrent_requests": perfConfig.MaxConcurrentRequests,
|
||||
"enable_gzip": perfConfig.EnableGzip,
|
||||
},
|
||||
"timeout_config": gin.H{
|
||||
"request_timeout_s": openaiConfig.RequestTimeout,
|
||||
"response_timeout_s": openaiConfig.ResponseTimeout,
|
||||
"idle_conn_timeout_s": openaiConfig.IdleConnTimeout,
|
||||
"server_read_timeout_s": serverConfig.ReadTimeout,
|
||||
"server_write_timeout_s": serverConfig.WriteTimeout,
|
||||
"server_idle_timeout_s": serverConfig.IdleTimeout,
|
||||
"graceful_shutdown_timeout_s": serverConfig.GracefulShutdownTimeout,
|
||||
},
|
||||
"log": gin.H{
|
||||
"level": logConfig.Level,
|
||||
"format": logConfig.Format,
|
||||
"enable_file": logConfig.EnableFile,
|
||||
"file_path": logConfig.FilePath,
|
||||
"enable_request": logConfig.EnableRequest,
|
||||
},
|
||||
"timestamp": time.Now().UTC().Format(time.RFC3339),
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, sanitizedConfig)
|
||||
}
|
||||
|
34
internal/handler/log_cleanup_handler.go
Normal file
34
internal/handler/log_cleanup_handler.go
Normal file
@@ -0,0 +1,34 @@
|
||||
package handler
|
||||
|
||||
import (
|
||||
"gpt-load/internal/response"
|
||||
"gpt-load/internal/services"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
// LogCleanupHandler handles log cleanup related requests
|
||||
type LogCleanupHandler struct {
|
||||
LogCleanupService *services.LogCleanupService
|
||||
}
|
||||
|
||||
// NewLogCleanupHandler creates a new LogCleanupHandler
|
||||
func NewLogCleanupHandler(s *services.LogCleanupService) *LogCleanupHandler {
|
||||
return &LogCleanupHandler{
|
||||
LogCleanupService: s,
|
||||
}
|
||||
}
|
||||
|
||||
// CleanupLogsNow handles the POST /api/logs/cleanup request.
|
||||
// It triggers an asynchronous cleanup of expired request logs.
|
||||
func (h *LogCleanupHandler) CleanupLogsNow(c *gin.Context) {
|
||||
go func() {
|
||||
logrus.Info("Asynchronous log cleanup started from API request")
|
||||
h.LogCleanupService.CleanupNow()
|
||||
}()
|
||||
|
||||
response.Success(c, gin.H{
|
||||
"message": "Log cleanup process started in the background",
|
||||
})
|
||||
}
|
@@ -1,26 +0,0 @@
|
||||
package handler
|
||||
|
||||
import (
|
||||
"gpt-load/internal/response"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
// ReloadConfig handles the POST /api/reload request.
|
||||
// It triggers a configuration reload.
|
||||
func (s *Server) ReloadConfig(c *gin.Context) {
|
||||
if s.config == nil {
|
||||
response.InternalError(c, "Configuration manager is not initialized")
|
||||
return
|
||||
}
|
||||
|
||||
err := s.config.ReloadConfig()
|
||||
if err != nil {
|
||||
logrus.Errorf("Failed to reload config: %v", err)
|
||||
response.InternalError(c, "Failed to reload config")
|
||||
return
|
||||
}
|
||||
|
||||
response.Success(c, gin.H{"message": "Configuration reloaded successfully"})
|
||||
}
|
@@ -1,33 +1,28 @@
|
||||
package handler
|
||||
|
||||
import (
|
||||
"gpt-load/internal/db"
|
||||
"gpt-load/internal/models"
|
||||
"gpt-load/internal/config"
|
||||
"gpt-load/internal/response"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
"gorm.io/gorm/clause"
|
||||
"github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
// GetSettings handles the GET /api/settings request.
|
||||
// It retrieves all system settings from the database and returns them as a key-value map.
|
||||
// It retrieves all system settings and returns them with detailed information.
|
||||
func GetSettings(c *gin.Context) {
|
||||
var settings []models.SystemSetting
|
||||
if err := db.DB.Find(&settings).Error; err != nil {
|
||||
response.InternalError(c, "Failed to retrieve settings")
|
||||
return
|
||||
}
|
||||
settingsManager := config.GetSystemSettingsManager()
|
||||
currentSettings := settingsManager.GetSettings()
|
||||
|
||||
settingsMap := make(map[string]string)
|
||||
for _, s := range settings {
|
||||
settingsMap[s.SettingKey] = s.SettingValue
|
||||
}
|
||||
// 使用新的动态元数据生成器
|
||||
settingsInfo := config.GenerateSettingsMetadata(¤tSettings)
|
||||
|
||||
response.Success(c, settingsMap)
|
||||
response.Success(c, settingsInfo)
|
||||
}
|
||||
|
||||
// UpdateSettings handles the PUT /api/settings request.
|
||||
// It receives a key-value JSON object and updates or creates settings in the database.
|
||||
// It receives a key-value JSON object and updates system settings.
|
||||
// After updating, it triggers a configuration reload.
|
||||
func UpdateSettings(c *gin.Context) {
|
||||
var settingsMap map[string]string
|
||||
if err := c.ShouldBindJSON(&settingsMap); err != nil {
|
||||
@@ -35,28 +30,37 @@ func UpdateSettings(c *gin.Context) {
|
||||
return
|
||||
}
|
||||
|
||||
var settingsToUpdate []models.SystemSetting
|
||||
for key, value := range settingsMap {
|
||||
settingsToUpdate = append(settingsToUpdate, models.SystemSetting{
|
||||
SettingKey: key,
|
||||
SettingValue: value,
|
||||
})
|
||||
}
|
||||
|
||||
if len(settingsToUpdate) == 0 {
|
||||
if len(settingsMap) == 0 {
|
||||
response.Success(c, nil)
|
||||
return
|
||||
}
|
||||
|
||||
// Using OnConflict to perform an "upsert" operation.
|
||||
// If a setting with the same key exists, it will be updated. Otherwise, a new one will be created.
|
||||
if err := db.DB.Clauses(clause.OnConflict{
|
||||
Columns: []clause.Column{{Name: "setting_key"}},
|
||||
DoUpdates: clause.AssignmentColumns([]string{"setting_value"}),
|
||||
}).Create(&settingsToUpdate).Error; err != nil {
|
||||
response.InternalError(c, "Failed to update settings")
|
||||
settingsManager := config.GetSystemSettingsManager()
|
||||
|
||||
// 更新配置
|
||||
if err := settingsManager.UpdateSettings(settingsMap); err != nil {
|
||||
response.InternalError(c, "Failed to update settings: "+err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
response.Success(c, nil)
|
||||
}
|
||||
// 重载系统配置
|
||||
if err := settingsManager.LoadFromDatabase(); err != nil {
|
||||
logrus.Errorf("Failed to reload system settings: %v", err)
|
||||
response.InternalError(c, "Failed to reload system settings")
|
||||
return
|
||||
}
|
||||
|
||||
settingsManager.DisplayCurrentSettings()
|
||||
|
||||
logrus.Info("Configuration reloaded successfully via API")
|
||||
response.Success(c, gin.H{
|
||||
"message": "Configuration reloaded successfully",
|
||||
"timestamp": gin.H{
|
||||
"reloaded_at": "now",
|
||||
},
|
||||
})
|
||||
|
||||
response.Success(c, gin.H{
|
||||
"message": "Settings updated successfully. Configuration reloaded.",
|
||||
})
|
||||
}
|
||||
|
15
internal/models/setting_info.go
Normal file
15
internal/models/setting_info.go
Normal file
@@ -0,0 +1,15 @@
|
||||
package models
|
||||
|
||||
// SystemSettingInfo 表示系统配置的详细信息(用于API返回)
|
||||
type SystemSettingInfo struct {
|
||||
Key string `json:"key"`
|
||||
Value interface{} `json:"value"`
|
||||
Type string `json:"type"` // "int", "bool", "string"
|
||||
DefaultValue interface{} `json:"default_value"`
|
||||
Description string `json:"description"`
|
||||
Category string `json:"category"` // "timeout", "performance", "logging", etc.
|
||||
Required bool `json:"required"`
|
||||
MinValue *int `json:"min_value,omitempty"`
|
||||
MaxValue *int `json:"max_value,omitempty"`
|
||||
ValidOptions []string `json:"valid_options,omitempty"`
|
||||
}
|
@@ -63,4 +63,4 @@ type DashboardStats struct {
|
||||
SuccessRequests int64 `json:"success_requests"`
|
||||
SuccessRate float64 `json:"success_rate"`
|
||||
GroupStats []GroupRequestStat `json:"group_stats"`
|
||||
}
|
||||
}
|
||||
|
@@ -39,6 +39,7 @@ func EmbedFolder(fsEmbed embed.FS, targetPath string) static.ServeFileSystem {
|
||||
func New(
|
||||
serverHandler *handler.Server,
|
||||
proxyServer *proxy.ProxyServer,
|
||||
logCleanupHandler *handler.LogCleanupHandler,
|
||||
configManager types.ConfigManager,
|
||||
buildFS embed.FS,
|
||||
indexPage []byte,
|
||||
@@ -60,7 +61,7 @@ func New(
|
||||
|
||||
// 注册路由
|
||||
registerSystemRoutes(router, serverHandler)
|
||||
registerAPIRoutes(router, serverHandler, configManager)
|
||||
registerAPIRoutes(router, serverHandler, logCleanupHandler, configManager)
|
||||
registerProxyRoutes(router, proxyServer, configManager)
|
||||
registerFrontendRoutes(router, buildFS, indexPage)
|
||||
|
||||
@@ -71,11 +72,10 @@ func New(
|
||||
func registerSystemRoutes(router *gin.Engine, serverHandler *handler.Server) {
|
||||
router.GET("/health", serverHandler.Health)
|
||||
router.GET("/stats", serverHandler.Stats)
|
||||
// router.GET("/config", serverHandler.GetConfig)
|
||||
}
|
||||
|
||||
// registerAPIRoutes 注册API路由
|
||||
func registerAPIRoutes(router *gin.Engine, serverHandler *handler.Server, configManager types.ConfigManager) {
|
||||
func registerAPIRoutes(router *gin.Engine, serverHandler *handler.Server, logCleanupHandler *handler.LogCleanupHandler, configManager types.ConfigManager) {
|
||||
api := router.Group("/api")
|
||||
authConfig := configManager.GetAuthConfig()
|
||||
|
||||
@@ -86,9 +86,9 @@ func registerAPIRoutes(router *gin.Engine, serverHandler *handler.Server, config
|
||||
if authConfig.Enabled {
|
||||
protectedAPI := api.Group("")
|
||||
protectedAPI.Use(middleware.Auth(authConfig))
|
||||
registerProtectedAPIRoutes(protectedAPI, serverHandler)
|
||||
registerProtectedAPIRoutes(protectedAPI, serverHandler, logCleanupHandler)
|
||||
} else {
|
||||
registerProtectedAPIRoutes(api, serverHandler)
|
||||
registerProtectedAPIRoutes(api, serverHandler, logCleanupHandler)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -98,7 +98,7 @@ func registerPublicAPIRoutes(api *gin.RouterGroup, serverHandler *handler.Server
|
||||
}
|
||||
|
||||
// registerProtectedAPIRoutes 认证API路由
|
||||
func registerProtectedAPIRoutes(api *gin.RouterGroup, serverHandler *handler.Server) {
|
||||
func registerProtectedAPIRoutes(api *gin.RouterGroup, serverHandler *handler.Server, logCleanupHandler *handler.LogCleanupHandler) {
|
||||
groups := api.Group("/groups")
|
||||
{
|
||||
groups.POST("", serverHandler.CreateGroup)
|
||||
@@ -123,7 +123,11 @@ func registerProtectedAPIRoutes(api *gin.RouterGroup, serverHandler *handler.Ser
|
||||
}
|
||||
|
||||
// 日志
|
||||
api.GET("/logs", handler.GetLogs)
|
||||
logs := api.Group("/logs")
|
||||
{
|
||||
logs.GET("", handler.GetLogs)
|
||||
logs.POST("/cleanup", logCleanupHandler.CleanupLogsNow)
|
||||
}
|
||||
|
||||
// 设置
|
||||
settings := api.Group("/settings")
|
||||
@@ -131,9 +135,6 @@ func registerProtectedAPIRoutes(api *gin.RouterGroup, serverHandler *handler.Ser
|
||||
settings.GET("", handler.GetSettings)
|
||||
settings.PUT("", handler.UpdateSettings)
|
||||
}
|
||||
|
||||
// 重载配置
|
||||
api.POST("/reload", serverHandler.ReloadConfig)
|
||||
}
|
||||
|
||||
// registerProxyRoutes 注册代理路由
|
||||
|
102
internal/services/log_cleanup.go
Normal file
102
internal/services/log_cleanup.go
Normal file
@@ -0,0 +1,102 @@
|
||||
package services
|
||||
|
||||
import (
|
||||
"gpt-load/internal/config"
|
||||
"gpt-load/internal/db"
|
||||
"gpt-load/internal/models"
|
||||
"time"
|
||||
|
||||
"github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
// LogCleanupService 负责清理过期的请求日志
|
||||
type LogCleanupService struct {
|
||||
stopCh chan struct{}
|
||||
}
|
||||
|
||||
// NewLogCleanupService 创建新的日志清理服务
|
||||
func NewLogCleanupService() *LogCleanupService {
|
||||
return &LogCleanupService{
|
||||
stopCh: make(chan struct{}),
|
||||
}
|
||||
}
|
||||
|
||||
// Start 启动日志清理服务
|
||||
func (s *LogCleanupService) Start() {
|
||||
go s.run()
|
||||
logrus.Info("Log cleanup service started")
|
||||
}
|
||||
|
||||
// Stop 停止日志清理服务
|
||||
func (s *LogCleanupService) Stop() {
|
||||
close(s.stopCh)
|
||||
logrus.Info("Log cleanup service stopped")
|
||||
}
|
||||
|
||||
// run 运行日志清理的主循环
|
||||
func (s *LogCleanupService) run() {
|
||||
// 每天凌晨2点执行清理任务
|
||||
ticker := time.NewTicker(24 * time.Hour)
|
||||
defer ticker.Stop()
|
||||
|
||||
// 启动时先执行一次清理
|
||||
s.cleanupExpiredLogs()
|
||||
|
||||
for {
|
||||
select {
|
||||
case <-ticker.C:
|
||||
s.cleanupExpiredLogs()
|
||||
case <-s.stopCh:
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 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()
|
||||
retentionDays := settings.RequestLogRetentionDays
|
||||
|
||||
if retentionDays <= 0 {
|
||||
logrus.Debug("Log retention is disabled (retention_days <= 0)")
|
||||
return
|
||||
}
|
||||
|
||||
// 计算过期时间点
|
||||
cutoffTime := time.Now().AddDate(0, 0, -retentionDays).UTC()
|
||||
|
||||
// 执行删除操作
|
||||
result := db.DB.Where("timestamp < ?", cutoffTime).Delete(&models.RequestLog{})
|
||||
if result.Error != nil {
|
||||
logrus.WithError(result.Error).Error("Failed to cleanup expired request logs")
|
||||
return
|
||||
}
|
||||
|
||||
if result.RowsAffected > 0 {
|
||||
logrus.WithFields(logrus.Fields{
|
||||
"deleted_count": result.RowsAffected,
|
||||
"cutoff_time": cutoffTime.Format(time.RFC3339),
|
||||
"retention_days": retentionDays,
|
||||
}).Info("Successfully cleaned up expired request logs")
|
||||
} else {
|
||||
logrus.Debug("No expired request logs found to cleanup")
|
||||
}
|
||||
}
|
||||
|
||||
// CleanupNow 立即执行一次日志清理
|
||||
func (s *LogCleanupService) CleanupNow() {
|
||||
logrus.Info("Manual log cleanup triggered")
|
||||
s.cleanupExpiredLogs()
|
||||
}
|
@@ -7,12 +7,19 @@ import (
|
||||
|
||||
// ConfigManager defines the interface for configuration management
|
||||
type ConfigManager interface {
|
||||
GetServerConfig() ServerConfig
|
||||
GetOpenAIConfig() OpenAIConfig
|
||||
// GetServerConfig() ServerConfig
|
||||
// GetOpenAIConfig() OpenAIConfig
|
||||
GetAuthConfig() AuthConfig
|
||||
GetCORSConfig() CORSConfig
|
||||
GetPerformanceConfig() PerformanceConfig
|
||||
GetLogConfig() LogConfig
|
||||
|
||||
// Effective configuration methods that merge system settings
|
||||
GetEffectiveServerConfig() ServerConfig
|
||||
GetEffectiveOpenAIConfig(groupConfig map[string]any) OpenAIConfig
|
||||
// GetEffectivePerformanceConfig() PerformanceConfig
|
||||
// GetEffectiveLogConfig() LogConfig
|
||||
|
||||
Validate() error
|
||||
DisplayConfig()
|
||||
ReloadConfig() error
|
||||
|
Reference in New Issue
Block a user