redline-api/controller/client_config.go
wangdong 75079c05d3 Initial commit: One API with custom Server Configuration UI
- Added Server Configuration settings page (backend + frontend)
- Added .env config download/import API
- Runtime config updates via UI
- Support for all 3 themes (default, air, berry)
- i18n support (zh/en)
2026-07-28 01:25:29 +08:00

207 lines
6.0 KiB
Go
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

package controller
import (
"fmt"
"io"
"net/http"
"os"
"strconv"
"strings"
"github.com/gin-gonic/gin"
"github.com/songquanpeng/one-api/common"
"github.com/songquanpeng/one-api/common/config"
)
func DownloadServerConfig(c *gin.Context) {
configs := collectServerConfigs()
var lines []string
lines = append(lines, "# One API Server Configuration")
lines = append(lines, "# Generated at runtime - download from settings page")
lines = append(lines, "")
// Group by category for readability
categories := []string{"database", "redis", "performance", "network", "ratelimit", "metrics", "other"}
categoryNames := map[string]string{
"database": "# ===== Database =====\n",
"redis": "\n# ===== Redis =====\n",
"performance": "\n# ===== Performance =====\n",
"network": "\n# ===== Network =====\n",
"ratelimit": "\n# ===== Rate Limit =====\n",
"metrics": "\n# ===== Metrics =====\n",
"other": "\n# ===== Other =====\n",
}
for _, cat := range categories {
lines = append(lines, categoryNames[cat])
for _, item := range configs {
if item.Category != cat {
continue
}
// Skip the virtual db_type key
if item.Key == "db_type" {
continue
}
// Get the actual value from env/runtime for the download
actualValue := getActualConfigValue(item.Key)
if item.Key == "SESSION_SECRET" || item.Key == "REDIS_PASSWORD" ||
strings.HasSuffix(item.Key, "TOKEN") || strings.HasSuffix(item.Key, "TOKEN") ||
strings.HasSuffix(item.Key, "SECRET") || strings.HasSuffix(item.Key, "Secret") ||
item.Key == "SQL_DSN" || item.Key == "LOG_SQL_DSN" ||
item.Key == "REDIS_CONN_STRING" || item.Key == "HTTPS_PROXY" {
// Include masked values for secrets
lines = append(lines, fmt.Sprintf("# %s=%s", item.Key, item.Value))
lines = append(lines, fmt.Sprintf("# %s (masked above, set manually if needed)", item.Key))
} else {
lines = append(lines, fmt.Sprintf("%s=%s", item.Key, actualValue))
}
}
}
content := strings.Join(lines, "\n")
c.Header("Content-Disposition", "attachment; filename=server-config.env")
c.Header("Content-Type", "text/plain; charset=utf-8")
c.String(http.StatusOK, content)
}
func ImportServerConfig(c *gin.Context) {
file, _, err := c.Request.FormFile("file")
if err != nil {
c.JSON(http.StatusBadRequest, gin.H{
"success": false,
"message": "请上传 .env 配置文件",
})
return
}
defer file.Close()
content, err := io.ReadAll(file)
if err != nil {
c.JSON(http.StatusBadRequest, gin.H{
"success": false,
"message": "读取文件失败:" + err.Error(),
})
return
}
// Validate basic .env format
lines := strings.Split(string(content), "\n")
validLines := 0
for _, line := range lines {
line = strings.TrimSpace(line)
if line == "" || strings.HasPrefix(line, "#") {
continue
}
if strings.Contains(line, "=") {
validLines++
}
}
if validLines == 0 {
c.JSON(http.StatusBadRequest, gin.H{
"success": false,
"message": "无效的 .env 文件格式,未找到有效的配置项",
})
return
}
// Determine the .env file path
envPath := ".env"
if p := os.Getenv("SERVER_CONFIG_PATH"); p != "" {
envPath = p
}
err = os.WriteFile(envPath, content, 0644)
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{
"success": false,
"message": "写入配置文件失败:" + err.Error(),
})
return
}
c.JSON(http.StatusOK, gin.H{
"success": true,
"message": fmt.Sprintf("配置文件已保存到 %s请重启服务以生效。", envPath),
})
}
func getActualConfigValue(key string) string {
switch key {
case "PORT":
return strconv.Itoa(*common.Port)
case "SESSION_SECRET":
return config.SessionSecret
case "SQL_DSN", "LOG_SQL_DSN", "REDIS_CONN_STRING", "REDIS_PASSWORD",
"REDIS_MASTER_NAME", "NODE_TYPE", "POLLING_INTERVAL",
"GIN_MODE", "FRONTEND_BASE_URL", "CHANNEL_TEST_FREQUENCY",
"INITIAL_ROOT_TOKEN", "INITIAL_ROOT_ACCESS_TOKEN", "HTTPS_PROXY":
return os.Getenv(key)
case "SQLITE_PATH":
if os.Getenv("SQLITE_PATH") != "" {
return os.Getenv("SQLITE_PATH")
}
return common.SQLitePath
case "SQLITE_BUSY_TIMEOUT":
return strconv.Itoa(common.SQLiteBusyTimeout)
case "SQL_MAX_IDLE_CONNS":
if v := os.Getenv("SQL_MAX_IDLE_CONNS"); v != "" {
return v
}
return "100"
case "SQL_MAX_OPEN_CONNS":
if v := os.Getenv("SQL_MAX_OPEN_CONNS"); v != "" {
return v
}
return "1000"
case "SQL_MAX_LIFETIME":
if v := os.Getenv("SQL_MAX_LIFETIME"); v != "" {
return v
}
return "60"
case "SYNC_FREQUENCY":
return strconv.Itoa(config.SyncFrequency)
case "DEBUG":
return strconv.FormatBool(config.DebugEnabled)
case "DEBUG_SQL":
return strconv.FormatBool(config.DebugSQLEnabled)
case "MEMORY_CACHE_ENABLED":
return strconv.FormatBool(config.MemoryCacheEnabled)
case "BATCH_UPDATE_ENABLED":
return strconv.FormatBool(config.BatchUpdateEnabled)
case "BATCH_UPDATE_INTERVAL":
return strconv.Itoa(config.BatchUpdateInterval)
case "RELAY_TIMEOUT":
return strconv.Itoa(config.RelayTimeout)
case "RELAY_PROXY":
return config.RelayProxy
case "USER_CONTENT_REQUEST_PROXY":
return config.UserContentRequestProxy
case "USER_CONTENT_REQUEST_TIMEOUT":
return strconv.Itoa(config.UserContentRequestTimeout)
case "GEMINI_SAFETY_SETTING":
return config.GeminiSafetySetting
case "GEMINI_VERSION":
return config.GeminiVersion
case "THEME":
return config.Theme
case "GLOBAL_API_RATE_LIMIT":
return strconv.Itoa(config.GlobalApiRateLimitNum)
case "GLOBAL_WEB_RATE_LIMIT":
return strconv.Itoa(config.GlobalWebRateLimitNum)
case "ENABLE_METRIC":
return strconv.FormatBool(config.EnableMetric)
case "METRIC_QUEUE_SIZE":
return strconv.Itoa(config.MetricQueueSize)
case "METRIC_SUCCESS_RATE_THRESHOLD":
return strconv.FormatFloat(config.MetricSuccessRateThreshold, 'f', 2, 64)
case "ONLY_ONE_LOG_FILE":
return strconv.FormatBool(config.OnlyOneLogFile)
case "ENFORCE_INCLUDE_USAGE":
return strconv.FormatBool(config.EnforceIncludeUsage)
case "TEST_PROMPT":
return config.TestPrompt
}
return ""
}