package controller import ( "net/http" "os" "strconv" "github.com/gin-gonic/gin" "github.com/songquanpeng/one-api/common" "github.com/songquanpeng/one-api/common/config" ) type ServerConfigItem struct { Key string `json:"key"` Value string `json:"value"` DefaultValue string `json:"default_value"` RestartRequired bool `json:"restart_required"` Description string `json:"description"` Category string `json:"category"` } func GetServerConfigs(c *gin.Context) { configs := collectServerConfigs() c.JSON(http.StatusOK, gin.H{ "success": true, "message": "", "data": configs, }) } func collectServerConfigs() []ServerConfigItem { // Determine database type dbType := "SQLite" if common.UsingMySQL { dbType = "MySQL" } else if common.UsingPostgreSQL { dbType = "PostgreSQL" } items := []ServerConfigItem{ // ===== Database ===== {Key: "db_type", Value: dbType, DefaultValue: "SQLite", RestartRequired: true, Description: "当前数据库类型(自动检测)", Category: "database"}, {Key: "SQL_DSN", Value: maskSecret(os.Getenv("SQL_DSN")), DefaultValue: "", RestartRequired: true, Description: "数据库连接字符串(留空=SQLite)", Category: "database"}, {Key: "LOG_SQL_DSN", Value: maskSecret(os.Getenv("LOG_SQL_DSN")), DefaultValue: "", RestartRequired: true, Description: "日志数据库连接字符串(留空=使用主库)", Category: "database"}, {Key: "SQLITE_PATH", Value: os.Getenv("SQLITE_PATH"), DefaultValue: "one-api.db", RestartRequired: true, Description: "SQLite 数据库文件路径", Category: "database"}, {Key: "SQLITE_BUSY_TIMEOUT", Value: strconv.Itoa(common.SQLiteBusyTimeout), DefaultValue: "3000", RestartRequired: true, Description: "SQLite 忙等待超时(毫秒)", Category: "database"}, {Key: "SQL_MAX_IDLE_CONNS", Value: "", DefaultValue: "100", RestartRequired: true, Description: "数据库最大空闲连接数", Category: "database"}, {Key: "SQL_MAX_OPEN_CONNS", Value: "", DefaultValue: "1000", RestartRequired: true, Description: "数据库最大打开连接数", Category: "database"}, {Key: "SQL_MAX_LIFETIME", Value: "", DefaultValue: "60", RestartRequired: true, Description: "数据库连接最长存活时间(秒)", Category: "database"}, // ===== Redis ===== {Key: "REDIS_CONN_STRING", Value: maskSecret(os.Getenv("REDIS_CONN_STRING")), DefaultValue: "", RestartRequired: true, Description: "Redis 连接字符串(留空=不启用)", Category: "redis"}, {Key: "REDIS_PASSWORD", Value: maskSecret(os.Getenv("REDIS_PASSWORD")), DefaultValue: "", RestartRequired: true, Description: "Redis 密码", Category: "redis"}, {Key: "REDIS_MASTER_NAME", Value: os.Getenv("REDIS_MASTER_NAME"), DefaultValue: "", RestartRequired: true, Description: "Redis 主节点名称(集群模式)", Category: "redis"}, {Key: "SYNC_FREQUENCY", Value: strconv.Itoa(config.SyncFrequency), DefaultValue: "600", RestartRequired: true, Description: "缓存同步频率(秒)", Category: "redis"}, // ===== Performance ===== {Key: "DEBUG", Value: strconv.FormatBool(config.DebugEnabled), DefaultValue: "false", RestartRequired: false, Description: "启用调试模式", Category: "performance"}, {Key: "DEBUG_SQL", Value: strconv.FormatBool(config.DebugSQLEnabled), DefaultValue: "false", RestartRequired: false, Description: "启用 SQL 调试日志", Category: "performance"}, {Key: "MEMORY_CACHE_ENABLED", Value: strconv.FormatBool(config.MemoryCacheEnabled), DefaultValue: "false", RestartRequired: false, Description: "启用内存缓存", Category: "performance"}, {Key: "BATCH_UPDATE_ENABLED", Value: strconv.FormatBool(config.BatchUpdateEnabled), DefaultValue: "false", RestartRequired: false, Description: "启用批量更新", Category: "performance"}, {Key: "BATCH_UPDATE_INTERVAL", Value: strconv.Itoa(config.BatchUpdateInterval), DefaultValue: "5", RestartRequired: false, Description: "批量更新间隔(秒)", Category: "performance"}, {Key: "GIN_MODE", Value: os.Getenv("GIN_MODE"), DefaultValue: "release", RestartRequired: true, Description: "Gin 运行模式(debug/release/test)", Category: "performance"}, // ===== Network ===== {Key: "RELAY_TIMEOUT", Value: strconv.Itoa(config.RelayTimeout), DefaultValue: "0", RestartRequired: false, Description: "中转超时时间(秒,0=不超时)", Category: "network"}, {Key: "RELAY_PROXY", Value: config.RelayProxy, DefaultValue: "", RestartRequired: false, Description: "中转代理地址", Category: "network"}, {Key: "USER_CONTENT_REQUEST_PROXY", Value: config.UserContentRequestProxy, DefaultValue: "", RestartRequired: false, Description: "用户内容请求代理地址", Category: "network"}, {Key: "USER_CONTENT_REQUEST_TIMEOUT", Value: strconv.Itoa(config.UserContentRequestTimeout), DefaultValue: "30", RestartRequired: false, Description: "用户内容请求超时(秒)", Category: "network"}, {Key: "HTTPS_PROXY", Value: maskSecret(os.Getenv("HTTPS_PROXY")), DefaultValue: "", RestartRequired: false, Description: "HTTPS 代理地址", Category: "network"}, // ===== Rate Limit ===== {Key: "GLOBAL_API_RATE_LIMIT", Value: strconv.Itoa(config.GlobalApiRateLimitNum), DefaultValue: "480", RestartRequired: false, Description: "全局 API 速率限制(次/3分钟)", Category: "ratelimit"}, {Key: "GLOBAL_WEB_RATE_LIMIT", Value: strconv.Itoa(config.GlobalWebRateLimitNum), DefaultValue: "240", RestartRequired: false, Description: "全局 Web 速率限制(次/3分钟)", Category: "ratelimit"}, // ===== Metrics ===== {Key: "ENABLE_METRIC", Value: strconv.FormatBool(config.EnableMetric), DefaultValue: "false", RestartRequired: false, Description: "启用指标监控", Category: "metrics"}, {Key: "METRIC_QUEUE_SIZE", Value: strconv.Itoa(config.MetricQueueSize), DefaultValue: "10", RestartRequired: false, Description: "指标队列大小", Category: "metrics"}, {Key: "METRIC_SUCCESS_RATE_THRESHOLD", Value: strconv.FormatFloat(config.MetricSuccessRateThreshold, 'f', 2, 64), DefaultValue: "0.8", RestartRequired: false, Description: "成功率阈值", Category: "metrics"}, {Key: "CHANNEL_TEST_FREQUENCY", Value: os.Getenv("CHANNEL_TEST_FREQUENCY"), DefaultValue: "", RestartRequired: true, Description: "渠道自动测试频率(秒,留空=不自动测试)", Category: "metrics"}, // ===== Other ===== {Key: "PORT", Value: strconv.Itoa(*common.Port), DefaultValue: "3000", RestartRequired: true, Description: "服务监听端口", Category: "other"}, {Key: "SESSION_SECRET", Value: maskSecret(config.SessionSecret), DefaultValue: "自动生成 UUID", RestartRequired: true, Description: "Session 密钥", Category: "other"}, {Key: "NODE_TYPE", Value: os.Getenv("NODE_TYPE"), DefaultValue: "master", RestartRequired: true, Description: "节点类型(master/slave)", Category: "other"}, {Key: "POLLING_INTERVAL", Value: os.Getenv("POLLING_INTERVAL"), DefaultValue: "", RestartRequired: true, Description: "从节点轮询间隔(秒)", Category: "other"}, {Key: "GEMINI_SAFETY_SETTING", Value: config.GeminiSafetySetting, DefaultValue: "BLOCK_NONE", RestartRequired: false, Description: "Gemini 安全设置", Category: "other"}, {Key: "GEMINI_VERSION", Value: config.GeminiVersion, DefaultValue: "v1", RestartRequired: false, Description: "Gemini API 版本", Category: "other"}, {Key: "THEME", Value: config.Theme, DefaultValue: "default", RestartRequired: false, Description: "前端主题名称", Category: "other"}, {Key: "FRONTEND_BASE_URL", Value: os.Getenv("FRONTEND_BASE_URL"), DefaultValue: "", RestartRequired: true, Description: "前端基础 URL(从节点使用)", Category: "other"}, {Key: "ONLY_ONE_LOG_FILE", Value: strconv.FormatBool(config.OnlyOneLogFile), DefaultValue: "false", RestartRequired: false, Description: "仅使用一个日志文件", Category: "other"}, {Key: "ENFORCE_INCLUDE_USAGE", Value: strconv.FormatBool(config.EnforceIncludeUsage), DefaultValue: "false", RestartRequired: false, Description: "强制返回 usage 信息", Category: "other"}, {Key: "TEST_PROMPT", Value: config.TestPrompt, DefaultValue: "Output only your specific model name with no additional text.", RestartRequired: false, Description: "渠道测试提示词", Category: "other"}, } // Fill in SQL_MAX_* values from env if v := os.Getenv("SQL_MAX_IDLE_CONNS"); v != "" { items[7].Value = v } if v := os.Getenv("SQL_MAX_OPEN_CONNS"); v != "" { items[8].Value = v } if v := os.Getenv("SQL_MAX_LIFETIME"); v != "" { items[9].Value = v } return items } func UpdateServerConfig(c *gin.Context) { var req struct { Key string `json:"key"` Value string `json:"value"` } if err := c.ShouldBindJSON(&req); err != nil { c.JSON(http.StatusBadRequest, gin.H{ "success": false, "message": "无效的参数", }) return } // Check if this is a restart-required setting restartRequiredKeys := map[string]bool{ "SQL_DSN": true, "LOG_SQL_DSN": true, "SQLITE_PATH": true, "SQLITE_BUSY_TIMEOUT": true, "SQL_MAX_IDLE_CONNS": true, "SQL_MAX_OPEN_CONNS": true, "SQL_MAX_LIFETIME": true, "REDIS_CONN_STRING": true, "REDIS_PASSWORD": true, "REDIS_MASTER_NAME": true, "SYNC_FREQUENCY": true, "PORT": true, "SESSION_SECRET": true, "NODE_TYPE": true, "POLLING_INTERVAL": true, "GIN_MODE": true, "FRONTEND_BASE_URL": true, "CHANNEL_TEST_FREQUENCY": true, "INITIAL_ROOT_TOKEN": true, "INITIAL_ROOT_ACCESS_TOKEN": true, } if restartRequiredKeys[req.Key] { c.JSON(http.StatusOK, gin.H{ "success": false, "message": "该设置需要重启服务才能生效,请使用下载 .env 文件功能修改后重启,或直接在 .env 文件中修改。", }) return } // Update runtime settings err := updateRuntimeConfig(req.Key, req.Value) if err != nil { c.JSON(http.StatusOK, gin.H{ "success": false, "message": err.Error(), }) return } c.JSON(http.StatusOK, gin.H{ "success": true, "message": "设置已更新", }) } func updateRuntimeConfig(key, value string) error { switch key { case "DEBUG": config.DebugEnabled = value == "true" case "DEBUG_SQL": config.DebugSQLEnabled = value == "true" case "MEMORY_CACHE_ENABLED": config.MemoryCacheEnabled = value == "true" case "BATCH_UPDATE_ENABLED": config.BatchUpdateEnabled = value == "true" case "BATCH_UPDATE_INTERVAL": v, err := strconv.Atoi(value) if err != nil { return err } config.BatchUpdateInterval = v case "RELAY_TIMEOUT": v, err := strconv.Atoi(value) if err != nil { return err } config.RelayTimeout = v case "RELAY_PROXY": config.RelayProxy = value case "USER_CONTENT_REQUEST_PROXY": config.UserContentRequestProxy = value case "USER_CONTENT_REQUEST_TIMEOUT": v, err := strconv.Atoi(value) if err != nil { return err } config.UserContentRequestTimeout = v case "HTTPS_PROXY": os.Setenv("HTTPS_PROXY", value) case "GEMINI_SAFETY_SETTING": config.GeminiSafetySetting = value case "GEMINI_VERSION": config.GeminiVersion = value case "THEME": if !config.ValidThemes[value] { return nil // silently ignore invalid themes } config.Theme = value case "GLOBAL_API_RATE_LIMIT": v, err := strconv.Atoi(value) if err != nil { return err } config.GlobalApiRateLimitNum = v case "GLOBAL_WEB_RATE_LIMIT": v, err := strconv.Atoi(value) if err != nil { return err } config.GlobalWebRateLimitNum = v case "ENABLE_METRIC": config.EnableMetric = value == "true" case "METRIC_QUEUE_SIZE": v, err := strconv.Atoi(value) if err != nil { return err } config.MetricQueueSize = v case "METRIC_SUCCESS_RATE_THRESHOLD": v, err := strconv.ParseFloat(value, 64) if err != nil { return err } config.MetricSuccessRateThreshold = v case "ONLY_ONE_LOG_FILE": config.OnlyOneLogFile = value == "true" case "ENFORCE_INCLUDE_USAGE": config.EnforceIncludeUsage = value == "true" case "TEST_PROMPT": config.TestPrompt = value default: return nil // unknown key, ignore silently } return nil } func maskSecret(s string) string { if s == "" { return "" } if len(s) <= 4 { return "****" } return s[:2] + "****" + s[len(s)-2:] }