feat: 配置优化梳理
This commit is contained in:
@@ -3,15 +3,12 @@ package config
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"reflect"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"gpt-load/internal/errors"
|
||||
"gpt-load/internal/types"
|
||||
"gpt-load/internal/utils"
|
||||
|
||||
"github.com/joho/godotenv"
|
||||
"github.com/sirupsen/logrus"
|
||||
@@ -67,45 +64,38 @@ func NewManager(settingsManager *SystemSettingsManager) (types.ConfigManager, er
|
||||
|
||||
// ReloadConfig reloads the configuration from environment variables
|
||||
func (m *Manager) ReloadConfig() error {
|
||||
// Try to load .env file
|
||||
if err := godotenv.Load(); err != nil {
|
||||
logrus.Info("Info: Create .env file to support environment variable configuration")
|
||||
}
|
||||
|
||||
// Get business logic defaults from the single source of truth
|
||||
defaultSettings := DefaultSystemSettings()
|
||||
|
||||
config := &Config{
|
||||
Server: types.ServerConfig{
|
||||
Port: parseInteger(os.Getenv("PORT"), 3000),
|
||||
Host: getEnvOrDefault("HOST", "0.0.0.0"),
|
||||
ReadTimeout: defaultSettings.ServerReadTimeout,
|
||||
WriteTimeout: defaultSettings.ServerWriteTimeout,
|
||||
IdleTimeout: defaultSettings.ServerIdleTimeout,
|
||||
GracefulShutdownTimeout: defaultSettings.ServerGracefulShutdownTimeout,
|
||||
Port: utils.ParseInteger(os.Getenv("PORT"), 3000),
|
||||
Host: utils.GetEnvOrDefault("HOST", "0.0.0.0"),
|
||||
ReadTimeout: utils.ParseInteger(os.Getenv("SERVER_READ_TIMEOUT"), 120),
|
||||
WriteTimeout: utils.ParseInteger(os.Getenv("SERVER_WRITE_TIMEOUT"), 1800),
|
||||
IdleTimeout: utils.ParseInteger(os.Getenv("SERVER_IDLE_TIMEOUT"), 120),
|
||||
GracefulShutdownTimeout: utils.ParseInteger(os.Getenv("SERVER_GRACEFUL_SHUTDOWN_TIMEOUT"), 60),
|
||||
},
|
||||
Auth: types.AuthConfig{
|
||||
Key: os.Getenv("AUTH_KEY"),
|
||||
Enabled: os.Getenv("AUTH_KEY") != "",
|
||||
Key: os.Getenv("AUTH_KEY"),
|
||||
},
|
||||
CORS: types.CORSConfig{
|
||||
Enabled: parseBoolean(os.Getenv("ENABLE_CORS"), true),
|
||||
AllowedOrigins: parseArray(os.Getenv("ALLOWED_ORIGINS"), []string{"*"}),
|
||||
AllowedMethods: parseArray(os.Getenv("ALLOWED_METHODS"), []string{"GET", "POST", "PUT", "DELETE", "OPTIONS"}),
|
||||
AllowedHeaders: parseArray(os.Getenv("ALLOWED_HEADERS"), []string{"*"}),
|
||||
AllowCredentials: parseBoolean(os.Getenv("ALLOW_CREDENTIALS"), false),
|
||||
Enabled: utils.ParseBoolean(os.Getenv("ENABLE_CORS"), true),
|
||||
AllowedOrigins: utils.ParseArray(os.Getenv("ALLOWED_ORIGINS"), []string{"*"}),
|
||||
AllowedMethods: utils.ParseArray(os.Getenv("ALLOWED_METHODS"), []string{"GET", "POST", "PUT", "DELETE", "OPTIONS"}),
|
||||
AllowedHeaders: utils.ParseArray(os.Getenv("ALLOWED_HEADERS"), []string{"*"}),
|
||||
AllowCredentials: utils.ParseBoolean(os.Getenv("ALLOW_CREDENTIALS"), false),
|
||||
},
|
||||
Performance: types.PerformanceConfig{
|
||||
MaxConcurrentRequests: parseInteger(os.Getenv("MAX_CONCURRENT_REQUESTS"), 100),
|
||||
EnableGzip: parseBoolean(os.Getenv("ENABLE_GZIP"), true),
|
||||
KeyValidationPoolSize: parseInteger(os.Getenv("KEY_VALIDATION_POOL_SIZE"), 10),
|
||||
MaxConcurrentRequests: utils.ParseInteger(os.Getenv("MAX_CONCURRENT_REQUESTS"), 100),
|
||||
KeyValidationPoolSize: utils.ParseInteger(os.Getenv("KEY_VALIDATION_POOL_SIZE"), 10),
|
||||
},
|
||||
Log: types.LogConfig{
|
||||
Level: getEnvOrDefault("LOG_LEVEL", "info"),
|
||||
Format: getEnvOrDefault("LOG_FORMAT", "text"),
|
||||
EnableFile: parseBoolean(os.Getenv("LOG_ENABLE_FILE"), false),
|
||||
FilePath: getEnvOrDefault("LOG_FILE_PATH", "logs/app.log"),
|
||||
EnableRequest: parseBoolean(os.Getenv("LOG_ENABLE_REQUEST"), true),
|
||||
Level: utils.GetEnvOrDefault("LOG_LEVEL", "info"),
|
||||
Format: utils.GetEnvOrDefault("LOG_FORMAT", "text"),
|
||||
EnableFile: utils.ParseBoolean(os.Getenv("LOG_ENABLE_FILE"), false),
|
||||
FilePath: utils.GetEnvOrDefault("LOG_FILE_PATH", "logs/app.log"),
|
||||
},
|
||||
Database: types.DatabaseConfig{
|
||||
DSN: os.Getenv("DATABASE_DSN"),
|
||||
@@ -154,17 +144,7 @@ func (m *Manager) GetDatabaseConfig() types.DatabaseConfig {
|
||||
|
||||
// GetEffectiveServerConfig returns server configuration merged with system settings
|
||||
func (m *Manager) GetEffectiveServerConfig() types.ServerConfig {
|
||||
config := m.config.Server
|
||||
|
||||
// Merge with system settings from database
|
||||
systemSettings := m.settingsManager.GetSettings()
|
||||
|
||||
config.ReadTimeout = systemSettings.ServerReadTimeout
|
||||
config.WriteTimeout = systemSettings.ServerWriteTimeout
|
||||
config.IdleTimeout = systemSettings.ServerIdleTimeout
|
||||
config.GracefulShutdownTimeout = systemSettings.ServerGracefulShutdownTimeout
|
||||
|
||||
return config
|
||||
return m.config.Server
|
||||
}
|
||||
|
||||
// Validate validates the configuration
|
||||
@@ -180,6 +160,11 @@ func (m *Manager) Validate() error {
|
||||
validationErrors = append(validationErrors, "max concurrent requests cannot be less than 1")
|
||||
}
|
||||
|
||||
// Validate auth key
|
||||
if m.config.Auth.Key == "" {
|
||||
validationErrors = append(validationErrors, "AUTH_KEY is required and cannot be empty")
|
||||
}
|
||||
|
||||
if len(validationErrors) > 0 {
|
||||
logrus.Error("Configuration validation failed:")
|
||||
for _, err := range validationErrors {
|
||||
@@ -191,160 +176,51 @@ func (m *Manager) Validate() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// DisplayConfig displays current configuration information
|
||||
func (m *Manager) DisplayConfig() {
|
||||
// DisplayServerConfig displays current server-related configuration information
|
||||
func (m *Manager) DisplayServerConfig() {
|
||||
serverConfig := m.GetEffectiveServerConfig()
|
||||
authConfig := m.GetAuthConfig()
|
||||
corsConfig := m.GetCORSConfig()
|
||||
perfConfig := m.GetPerformanceConfig()
|
||||
logConfig := m.GetLogConfig()
|
||||
dbConfig := m.GetDatabaseConfig()
|
||||
|
||||
logrus.Info("Current Server Configuration:")
|
||||
logrus.Infof(" Server: %s:%d", serverConfig.Host, serverConfig.Port)
|
||||
logrus.Info("--- Server Configuration ---")
|
||||
logrus.Infof(" Listen Address: %s:%d", serverConfig.Host, serverConfig.Port)
|
||||
logrus.Infof(" Graceful Shutdown Timeout: %d seconds", serverConfig.GracefulShutdownTimeout)
|
||||
logrus.Infof(" Read Timeout: %d seconds", serverConfig.ReadTimeout)
|
||||
logrus.Infof(" Write Timeout: %d seconds", serverConfig.WriteTimeout)
|
||||
logrus.Infof(" Idle Timeout: %d seconds", serverConfig.IdleTimeout)
|
||||
|
||||
authStatus := "disabled"
|
||||
if authConfig.Enabled {
|
||||
authStatus = "enabled"
|
||||
}
|
||||
logrus.Infof(" Authentication: %s", authStatus)
|
||||
logrus.Info("--- Performance ---")
|
||||
logrus.Infof(" Max Concurrent Requests: %d", perfConfig.MaxConcurrentRequests)
|
||||
logrus.Infof(" Key Validation Pool Size: %d", perfConfig.KeyValidationPoolSize)
|
||||
|
||||
logrus.Info("--- Security ---")
|
||||
logrus.Infof(" Authentication: enabled (key loaded)")
|
||||
corsStatus := "disabled"
|
||||
if corsConfig.Enabled {
|
||||
corsStatus = "enabled"
|
||||
corsStatus = fmt.Sprintf("enabled (Origins: %s)", strings.Join(corsConfig.AllowedOrigins, ", "))
|
||||
}
|
||||
logrus.Infof(" CORS: %s", corsStatus)
|
||||
logrus.Infof(" Max concurrent requests: %d", perfConfig.MaxConcurrentRequests)
|
||||
logrus.Infof(" Concurrency pool size: %d", perfConfig.KeyValidationPoolSize)
|
||||
logrus.Infof(" CORS: %s", corsStatus)
|
||||
|
||||
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)
|
||||
}
|
||||
|
||||
// Helper functions
|
||||
|
||||
// parseInteger parses integer environment variable
|
||||
func parseInteger(value string, defaultValue int) int {
|
||||
if value == "" {
|
||||
return defaultValue
|
||||
}
|
||||
if parsed, err := strconv.Atoi(value); err == nil {
|
||||
return parsed
|
||||
}
|
||||
return defaultValue
|
||||
}
|
||||
|
||||
// parseBoolean parses boolean environment variable
|
||||
func parseBoolean(value string, defaultValue bool) bool {
|
||||
if value == "" {
|
||||
return defaultValue
|
||||
}
|
||||
|
||||
lowerValue := strings.ToLower(value)
|
||||
switch lowerValue {
|
||||
case "true", "1", "yes", "on":
|
||||
return true
|
||||
case "false", "0", "no", "off":
|
||||
return false
|
||||
default:
|
||||
return defaultValue
|
||||
}
|
||||
}
|
||||
|
||||
// parseArray parses array environment variable (comma-separated)
|
||||
func parseArray(value string, defaultValue []string) []string {
|
||||
if value == "" {
|
||||
return defaultValue
|
||||
}
|
||||
|
||||
parts := strings.Split(value, ",")
|
||||
result := make([]string, 0, len(parts))
|
||||
for _, part := range parts {
|
||||
if trimmed := strings.TrimSpace(part); trimmed != "" {
|
||||
result = append(result, trimmed)
|
||||
}
|
||||
}
|
||||
|
||||
if len(result) == 0 {
|
||||
return defaultValue
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
// getEnvOrDefault gets environment variable or default value
|
||||
func getEnvOrDefault(key, defaultValue string) string {
|
||||
if value := os.Getenv(key); value != "" {
|
||||
return value
|
||||
}
|
||||
return defaultValue
|
||||
}
|
||||
|
||||
// GetInt is a helper function for SystemSettingsManager to get an integer value with a default.
|
||||
func (s *SystemSettingsManager) GetInt(key string, defaultValue int) int {
|
||||
settings := s.GetSettings()
|
||||
v := reflect.ValueOf(settings)
|
||||
t := v.Type()
|
||||
|
||||
for i := 0; i < t.NumField(); i++ {
|
||||
structField := t.Field(i)
|
||||
jsonTag := strings.Split(structField.Tag.Get("json"), ",")[0]
|
||||
if jsonTag == key {
|
||||
valueField := v.Field(i)
|
||||
if valueField.Kind() == reflect.Int {
|
||||
return int(valueField.Int())
|
||||
}
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
logrus.Info("--- Logging ---")
|
||||
logrus.Infof(" Log Level: %s", logConfig.Level)
|
||||
logrus.Infof(" Log Format: %s", logConfig.Format)
|
||||
logrus.Infof(" File Logging: %t", logConfig.EnableFile)
|
||||
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))
|
||||
}
|
||||
}
|
||||
logrus.Infof(" Log File Path: %s", logConfig.FilePath)
|
||||
}
|
||||
|
||||
logrus.Info("--- Dependencies ---")
|
||||
if dbConfig.DSN != "" {
|
||||
logrus.Info(" Database: configured")
|
||||
} else {
|
||||
logrus.Info(" Database: not configured")
|
||||
}
|
||||
if m.config.RedisDSN != "" {
|
||||
logrus.Info(" Redis: configured")
|
||||
} else {
|
||||
logrus.Info(" Redis: not configured")
|
||||
}
|
||||
logrus.Info("--------------------------")
|
||||
}
|
||||
|
@@ -8,6 +8,7 @@ import (
|
||||
"gpt-load/internal/store"
|
||||
"gpt-load/internal/syncer"
|
||||
"gpt-load/internal/types"
|
||||
"gpt-load/internal/utils"
|
||||
"os"
|
||||
"reflect"
|
||||
"strconv"
|
||||
@@ -20,73 +21,6 @@ import (
|
||||
|
||||
const SettingsUpdateChannel = "system_settings:updated"
|
||||
|
||||
// GenerateSettingsMetadata 使用反射从 SystemSettings 结构体动态生成元数据
|
||||
func GenerateSettingsMetadata(s *types.SystemSettings) []models.SystemSettingInfo {
|
||||
var settingsInfo []models.SystemSettingInfo
|
||||
v := reflect.ValueOf(s).Elem()
|
||||
t := v.Type()
|
||||
|
||||
for i := range t.NumField() {
|
||||
field := t.Field(i)
|
||||
fieldValue := v.Field(i)
|
||||
|
||||
jsonTag := field.Tag.Get("json")
|
||||
if jsonTag == "" {
|
||||
continue
|
||||
}
|
||||
|
||||
nameTag := field.Tag.Get("name")
|
||||
descTag := field.Tag.Get("desc")
|
||||
defaultTag := field.Tag.Get("default")
|
||||
validateTag := field.Tag.Get("validate")
|
||||
categoryTag := field.Tag.Get("category")
|
||||
|
||||
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,
|
||||
Name: nameTag,
|
||||
Value: fieldValue.Interface(),
|
||||
Type: field.Type.String(),
|
||||
DefaultValue: defaultTag,
|
||||
Description: descTag,
|
||||
Category: categoryTag,
|
||||
MinValue: minValue,
|
||||
}
|
||||
settingsInfo = append(settingsInfo, info)
|
||||
}
|
||||
return settingsInfo
|
||||
}
|
||||
|
||||
// DefaultSystemSettings 返回默认的系统配置
|
||||
func DefaultSystemSettings() types.SystemSettings {
|
||||
s := types.SystemSettings{}
|
||||
v := reflect.ValueOf(&s).Elem()
|
||||
t := v.Type()
|
||||
|
||||
for i := range t.NumField() {
|
||||
field := t.Field(i)
|
||||
defaultTag := field.Tag.Get("default")
|
||||
if defaultTag == "" {
|
||||
continue
|
||||
}
|
||||
|
||||
fieldValue := v.Field(i)
|
||||
if fieldValue.CanSet() {
|
||||
if err := setFieldFromString(fieldValue, defaultTag); err != nil {
|
||||
logrus.Warnf("Failed to set default value for field %s: %v", field.Name, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
return s
|
||||
}
|
||||
|
||||
// SystemSettingsManager 管理系统配置
|
||||
type SystemSettingsManager struct {
|
||||
syncer *syncer.CacheSyncer[types.SystemSettings]
|
||||
@@ -97,16 +31,16 @@ func NewSystemSettingsManager() *SystemSettingsManager {
|
||||
return &SystemSettingsManager{}
|
||||
}
|
||||
|
||||
type gm interface {
|
||||
type groupManager interface {
|
||||
Invalidate() error
|
||||
}
|
||||
|
||||
type leader interface {
|
||||
type leaderService interface {
|
||||
IsLeader() bool
|
||||
}
|
||||
|
||||
// Initialize initializes the SystemSettingsManager with database and store dependencies.
|
||||
func (sm *SystemSettingsManager) Initialize(store store.Store, gm gm, leader leader) error {
|
||||
func (sm *SystemSettingsManager) Initialize(store store.Store, gm groupManager, leader leaderService) error {
|
||||
settingsLoader := func() (types.SystemSettings, error) {
|
||||
var dbSettings []models.SystemSetting
|
||||
if err := db.DB.Find(&dbSettings).Error; err != nil {
|
||||
@@ -119,7 +53,7 @@ func (sm *SystemSettingsManager) Initialize(store store.Store, gm gm, leader lea
|
||||
}
|
||||
|
||||
// Start with default settings, then override with values from the database.
|
||||
settings := DefaultSystemSettings()
|
||||
settings := utils.DefaultSystemSettings()
|
||||
v := reflect.ValueOf(&settings).Elem()
|
||||
t := v.Type()
|
||||
jsonToField := make(map[string]string)
|
||||
@@ -135,14 +69,14 @@ func (sm *SystemSettingsManager) Initialize(store store.Store, gm gm, leader lea
|
||||
if fieldName, ok := jsonToField[key]; ok {
|
||||
fieldValue := v.FieldByName(fieldName)
|
||||
if fieldValue.IsValid() && fieldValue.CanSet() {
|
||||
if err := setFieldFromString(fieldValue, valStr); err != nil {
|
||||
if err := utils.SetFieldFromString(fieldValue, valStr); err != nil {
|
||||
logrus.Warnf("Failed to set value from map for field %s: %v", fieldName, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
sm.DisplayCurrentSettings(settings)
|
||||
sm.DisplaySystemConfig(settings)
|
||||
|
||||
return settings, nil
|
||||
}
|
||||
@@ -180,8 +114,8 @@ func (sm *SystemSettingsManager) Stop() {
|
||||
|
||||
// EnsureSettingsInitialized 确保数据库中存在所有系统设置的记录。
|
||||
func (sm *SystemSettingsManager) EnsureSettingsInitialized() error {
|
||||
defaultSettings := DefaultSystemSettings()
|
||||
metadata := GenerateSettingsMetadata(&defaultSettings)
|
||||
defaultSettings := utils.DefaultSystemSettings()
|
||||
metadata := utils.GenerateSettingsMetadata(&defaultSettings)
|
||||
|
||||
for _, meta := range metadata {
|
||||
var existing models.SystemSetting
|
||||
@@ -189,20 +123,15 @@ func (sm *SystemSettingsManager) EnsureSettingsInitialized() error {
|
||||
if err != nil {
|
||||
value := fmt.Sprintf("%v", meta.DefaultValue)
|
||||
if meta.Key == "app_url" {
|
||||
// Special handling for app_url initialization
|
||||
if appURL := os.Getenv("APP_URL"); appURL != "" {
|
||||
value = appURL
|
||||
} else {
|
||||
host := os.Getenv("HOST")
|
||||
if host == "" || host == "0.0.0.0" {
|
||||
host = "localhost"
|
||||
}
|
||||
port := os.Getenv("PORT")
|
||||
if port == "" {
|
||||
port = "3000"
|
||||
}
|
||||
value = fmt.Sprintf("http://%s:%s", host, port)
|
||||
host := os.Getenv("HOST")
|
||||
if host == "" || host == "0.0.0.0" {
|
||||
host = "localhost"
|
||||
}
|
||||
port := os.Getenv("PORT")
|
||||
if port == "" {
|
||||
port = "3000"
|
||||
}
|
||||
value = fmt.Sprintf("http://%s:%s", host, port)
|
||||
}
|
||||
setting := models.SystemSetting{
|
||||
SettingKey: meta.Key,
|
||||
@@ -221,26 +150,30 @@ func (sm *SystemSettingsManager) EnsureSettingsInitialized() error {
|
||||
}
|
||||
|
||||
// GetSettings 获取当前系统配置
|
||||
// If the syncer is not initialized, it returns default settings.
|
||||
func (sm *SystemSettingsManager) GetSettings() types.SystemSettings {
|
||||
if sm.syncer == nil {
|
||||
logrus.Warn("SystemSettingsManager is not initialized, returning default settings.")
|
||||
return DefaultSystemSettings()
|
||||
return utils.DefaultSystemSettings()
|
||||
}
|
||||
return sm.syncer.Get()
|
||||
}
|
||||
|
||||
// GetAppUrl returns the effective App URL.
|
||||
// It prioritizes the value from system settings (database) over the APP_URL environment variable.
|
||||
func (sm *SystemSettingsManager) GetAppUrl() string {
|
||||
// 1. 优先级: 数据库中的系统配置
|
||||
settings := sm.GetSettings()
|
||||
if settings.AppUrl != "" {
|
||||
return settings.AppUrl
|
||||
}
|
||||
|
||||
// 2. 回退: 环境变量
|
||||
return os.Getenv("APP_URL")
|
||||
host := os.Getenv("HOST")
|
||||
if host == "" || host == "0.0.0.0" {
|
||||
host = "localhost"
|
||||
}
|
||||
port := os.Getenv("PORT")
|
||||
if port == "" {
|
||||
port = "3000"
|
||||
}
|
||||
return fmt.Sprintf("http://%s:%s", host, port)
|
||||
}
|
||||
|
||||
// UpdateSettings 更新系统配置
|
||||
@@ -273,40 +206,35 @@ func (sm *SystemSettingsManager) UpdateSettings(settingsMap map[string]any) erro
|
||||
}
|
||||
|
||||
// GetEffectiveConfig 获取有效配置 (系统配置 + 分组覆盖)
|
||||
func (sm *SystemSettingsManager) GetEffectiveConfig(groupConfig datatypes.JSONMap) types.SystemSettings {
|
||||
// 从系统配置开始
|
||||
func (sm *SystemSettingsManager) GetEffectiveConfig(groupConfigJSON datatypes.JSONMap) types.SystemSettings {
|
||||
effectiveConfig := sm.GetSettings()
|
||||
v := reflect.ValueOf(&effectiveConfig).Elem()
|
||||
t := v.Type()
|
||||
|
||||
// 创建一个从 json 标签到字段名的映射
|
||||
jsonToField := make(map[string]string)
|
||||
for i := range t.NumField() {
|
||||
field := t.Field(i)
|
||||
jsonTag := strings.Split(field.Tag.Get("json"), ",")[0]
|
||||
if jsonTag != "" {
|
||||
jsonToField[jsonTag] = field.Name
|
||||
}
|
||||
if groupConfigJSON == nil {
|
||||
return effectiveConfig
|
||||
}
|
||||
|
||||
// 应用分组配置覆盖
|
||||
for key, val := range groupConfig {
|
||||
if fieldName, ok := jsonToField[key]; ok {
|
||||
fieldValue := v.FieldByName(fieldName)
|
||||
if fieldValue.IsValid() && fieldValue.CanSet() {
|
||||
switch fieldValue.Kind() {
|
||||
case reflect.Int:
|
||||
if intVal, err := interfaceToInt(val); err == nil {
|
||||
fieldValue.SetInt(int64(intVal))
|
||||
}
|
||||
case reflect.String:
|
||||
if strVal, ok := interfaceToString(val); ok {
|
||||
fieldValue.SetString(strVal)
|
||||
}
|
||||
case reflect.Bool:
|
||||
if boolVal, ok := interfaceToBool(val); ok {
|
||||
fieldValue.SetBool(boolVal)
|
||||
}
|
||||
var groupConfig models.GroupConfig
|
||||
groupConfigBytes, err := groupConfigJSON.MarshalJSON()
|
||||
if err != nil {
|
||||
logrus.Warnf("Failed to marshal group config JSON, using system settings only. Error: %v", err)
|
||||
return effectiveConfig
|
||||
}
|
||||
if err := json.Unmarshal(groupConfigBytes, &groupConfig); err != nil {
|
||||
logrus.Warnf("Failed to unmarshal group config, using system settings only. Error: %v", err)
|
||||
return effectiveConfig
|
||||
}
|
||||
|
||||
gcv := reflect.ValueOf(groupConfig)
|
||||
ecv := reflect.ValueOf(&effectiveConfig).Elem()
|
||||
|
||||
for i := 0; i < gcv.NumField(); i++ {
|
||||
groupField := gcv.Field(i)
|
||||
if groupField.Kind() == reflect.Ptr && !groupField.IsNil() {
|
||||
groupFieldValue := groupField.Elem()
|
||||
effectiveField := ecv.FieldByName(gcv.Type().Field(i).Name)
|
||||
if effectiveField.IsValid() && effectiveField.CanSet() {
|
||||
if effectiveField.Type() == groupFieldValue.Type() {
|
||||
effectiveField.Set(groupFieldValue)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -317,11 +245,11 @@ func (sm *SystemSettingsManager) GetEffectiveConfig(groupConfig datatypes.JSONMa
|
||||
|
||||
// ValidateSettings 验证系统配置的有效性
|
||||
func (sm *SystemSettingsManager) ValidateSettings(settingsMap map[string]any) error {
|
||||
tempSettings := DefaultSystemSettings()
|
||||
tempSettings := utils.DefaultSystemSettings()
|
||||
v := reflect.ValueOf(&tempSettings).Elem()
|
||||
t := v.Type()
|
||||
jsonToField := make(map[string]reflect.StructField)
|
||||
for i := 0; i < t.NumField(); i++ {
|
||||
for i := range t.NumField() {
|
||||
field := t.Field(i)
|
||||
jsonTag := field.Tag.Get("json")
|
||||
if jsonTag != "" {
|
||||
@@ -339,7 +267,6 @@ func (sm *SystemSettingsManager) ValidateSettings(settingsMap map[string]any) er
|
||||
|
||||
switch field.Type.Kind() {
|
||||
case reflect.Int:
|
||||
// JSON numbers are decoded as float64
|
||||
floatVal, ok := value.(float64)
|
||||
if !ok {
|
||||
return fmt.Errorf("invalid type for %s: expected a number, got %T", key, value)
|
||||
@@ -372,97 +299,70 @@ func (sm *SystemSettingsManager) ValidateSettings(settingsMap map[string]any) er
|
||||
return nil
|
||||
}
|
||||
|
||||
// DisplayCurrentSettings 显示当前系统配置信息
|
||||
func (sm *SystemSettingsManager) DisplayCurrentSettings(settings types.SystemSettings) {
|
||||
logrus.Info("Current System Settings:")
|
||||
logrus.Infof(" App URL: %s", settings.AppUrl)
|
||||
logrus.Infof(" Blacklist threshold: %d", settings.BlacklistThreshold)
|
||||
logrus.Infof(" Max retries: %d", settings.MaxRetries)
|
||||
logrus.Infof(" Server timeouts: read=%ds, write=%ds, idle=%ds, shutdown=%ds",
|
||||
settings.ServerReadTimeout, settings.ServerWriteTimeout,
|
||||
settings.ServerIdleTimeout, settings.ServerGracefulShutdownTimeout)
|
||||
logrus.Infof(" Request timeouts: request=%ds, connect=%ds, idle_conn=%ds",
|
||||
settings.RequestTimeout, settings.ConnectTimeout, settings.IdleConnTimeout)
|
||||
logrus.Infof(" HTTP Client Pool: max_idle_conns=%d, max_idle_conns_per_host=%d",
|
||||
settings.MaxIdleConns, settings.MaxIdleConnsPerHost)
|
||||
logrus.Infof(" Request log retention: %d days", settings.RequestLogRetentionDays)
|
||||
logrus.Infof(" Key validation: interval=%dmin, task_timeout=%dmin",
|
||||
settings.KeyValidationIntervalMinutes, settings.KeyValidationTaskTimeoutMinutes)
|
||||
}
|
||||
|
||||
// setFieldFromString sets a struct field's value from a string, based on the field's kind.
|
||||
func setFieldFromString(fieldValue reflect.Value, value string) error {
|
||||
if !fieldValue.CanSet() {
|
||||
return fmt.Errorf("field cannot be set")
|
||||
// ValidateGroupConfigOverrides validates a map of group-level configuration overrides.
|
||||
func (sm *SystemSettingsManager) ValidateGroupConfigOverrides(configMap map[string]any) error {
|
||||
tempSettings := types.SystemSettings{}
|
||||
v := reflect.ValueOf(&tempSettings).Elem()
|
||||
t := v.Type()
|
||||
jsonToField := make(map[string]reflect.StructField)
|
||||
for i := range t.NumField() {
|
||||
field := t.Field(i)
|
||||
jsonTag := field.Tag.Get("json")
|
||||
if jsonTag != "" {
|
||||
jsonToField[jsonTag] = field
|
||||
}
|
||||
}
|
||||
|
||||
switch fieldValue.Kind() {
|
||||
case reflect.Int:
|
||||
intVal, err := strconv.ParseInt(value, 10, 64)
|
||||
if err != nil {
|
||||
return fmt.Errorf("invalid integer value '%s': %w", value, err)
|
||||
for key, value := range configMap {
|
||||
if value == nil {
|
||||
continue
|
||||
}
|
||||
fieldValue.SetInt(int64(intVal))
|
||||
case reflect.Bool:
|
||||
boolVal, err := strconv.ParseBool(value)
|
||||
if err != nil {
|
||||
return fmt.Errorf("invalid boolean value '%s': %w", value, err)
|
||||
|
||||
field, ok := jsonToField[key]
|
||||
if !ok {
|
||||
return fmt.Errorf("invalid setting key: %s", key)
|
||||
}
|
||||
|
||||
validateTag := field.Tag.Get("validate")
|
||||
|
||||
floatVal, isFloat := value.(float64)
|
||||
if !isFloat {
|
||||
continue
|
||||
}
|
||||
intVal := int(floatVal)
|
||||
if floatVal != float64(intVal) {
|
||||
return fmt.Errorf("invalid value for %s: must be an integer", key)
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
}
|
||||
fieldValue.SetBool(boolVal)
|
||||
case reflect.String:
|
||||
fieldValue.SetString(value)
|
||||
default:
|
||||
return fmt.Errorf("unsupported field kind: %s", fieldValue.Kind())
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// 工具函数
|
||||
// DisplaySystemConfig displays the current system settings.
|
||||
func (sm *SystemSettingsManager) DisplaySystemConfig(settings types.SystemSettings) {
|
||||
logrus.Info("--- System Settings ---")
|
||||
logrus.Infof(" App URL: %s", settings.AppUrl)
|
||||
logrus.Infof(" Request Log Retention: %d days", settings.RequestLogRetentionDays)
|
||||
|
||||
func interfaceToInt(val any) (int, error) {
|
||||
switch v := val.(type) {
|
||||
case json.Number:
|
||||
i64, err := v.Int64()
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
return int(i64), nil
|
||||
case int:
|
||||
return v, nil
|
||||
case float64:
|
||||
if v != float64(int(v)) {
|
||||
return 0, fmt.Errorf("value is a float, not an integer: %v", v)
|
||||
}
|
||||
return int(v), nil
|
||||
case string:
|
||||
return strconv.Atoi(v)
|
||||
default:
|
||||
return 0, fmt.Errorf("cannot convert %T to int", v)
|
||||
}
|
||||
}
|
||||
logrus.Info("--- Request Behavior ---")
|
||||
logrus.Infof(" Request Timeout: %d seconds", settings.RequestTimeout)
|
||||
logrus.Infof(" Connect Timeout: %d seconds", settings.ConnectTimeout)
|
||||
logrus.Infof(" Response Header Timeout: %d seconds", settings.ResponseHeaderTimeout)
|
||||
logrus.Infof(" Idle Connection Timeout: %d seconds", settings.IdleConnTimeout)
|
||||
logrus.Infof(" Max Idle Connections: %d", settings.MaxIdleConns)
|
||||
logrus.Infof(" Max Idle Connections Per Host: %d", settings.MaxIdleConnsPerHost)
|
||||
|
||||
// interfaceToString is kept for GetEffectiveConfig
|
||||
func interfaceToString(val any) (string, bool) {
|
||||
s, ok := val.(string)
|
||||
return s, ok
|
||||
}
|
||||
|
||||
// interfaceToBool is kept for GetEffectiveConfig
|
||||
func interfaceToBool(val any) (bool, bool) {
|
||||
switch v := val.(type) {
|
||||
case json.Number:
|
||||
if s := v.String(); s == "1" {
|
||||
return true, true
|
||||
} else if s == "0" {
|
||||
return false, true
|
||||
}
|
||||
case bool:
|
||||
return v, true
|
||||
case string:
|
||||
b, err := strconv.ParseBool(v)
|
||||
if err == nil {
|
||||
return b, true
|
||||
}
|
||||
}
|
||||
return false, false
|
||||
logrus.Info("--- Key & Group Behavior ---")
|
||||
logrus.Infof(" Max Retries: %d", settings.MaxRetries)
|
||||
logrus.Infof(" Blacklist Threshold: %d", settings.BlacklistThreshold)
|
||||
logrus.Infof(" Key Validation Interval: %d minutes", settings.KeyValidationIntervalMinutes)
|
||||
logrus.Info("-----------------------")
|
||||
}
|
||||
|
Reference in New Issue
Block a user