From 5a46b334ea47f66cf35522c597d2e0e7b9ccdbf2 Mon Sep 17 00:00:00 2001 From: liyunfei <2716499331@qq.com> Date: Fri, 13 Jun 2025 11:44:51 +0800 Subject: [PATCH 1/2] feat: add request body logging control --- controller/option.go | 24 ++++++ middleware/request-logger.go | 132 +++++++++++++++++++++++++++++++ router/api-router.go | 1 + web/src/pages/Token/EditToken.js | 11 ++- 4 files changed, 166 insertions(+), 2 deletions(-) create mode 100644 middleware/request-logger.go diff --git a/controller/option.go b/controller/option.go index c82fbd7ead44..82ca43d3410a 100644 --- a/controller/option.go +++ b/controller/option.go @@ -4,6 +4,7 @@ import ( "encoding/json" "net/http" "one-api/common" + "one-api/middleware" "one-api/model" "one-api/setting" "strings" @@ -107,3 +108,26 @@ func UpdateOption(c *gin.Context) { }) return } + +// ToggleRequestLog 切换请求体日志的开关状态 +func ToggleRequestLog(c *gin.Context) { + var request struct { + Enable bool `json:"enable"` + } + if err := c.ShouldBindJSON(&request); err != nil { + c.JSON(200, gin.H{ + "success": false, + "message": "无效的请求参数", + }) + return + } + + middleware.EnableRequestBodyLogging = request.Enable + c.JSON(200, gin.H{ + "success": true, + "message": "请求体日志状态已更新", + "data": gin.H{ + "enable": middleware.EnableRequestBodyLogging, + }, + }) +} diff --git a/middleware/request-logger.go b/middleware/request-logger.go new file mode 100644 index 000000000000..ec004b0bb6a7 --- /dev/null +++ b/middleware/request-logger.go @@ -0,0 +1,132 @@ +package middleware + +import ( + "bytes" + "encoding/json" + "fmt" + "io" + "one-api/common" + "strings" + + "github.com/gin-gonic/gin" +) + +// EnableRequestBodyLogging 控制是否打印请求体 +var EnableRequestBodyLogging bool = false + +func RequestLogger() gin.HandlerFunc { + return func(c *gin.Context) { + // 获取请求头 + headers := make(map[string]string) + for k, v := range c.Request.Header { + // 跳过敏感信息 + if strings.EqualFold(k, "Authorization") || strings.EqualFold(k, "Cookie") { + headers[k] = "***" + continue + } + headers[k] = strings.Join(v, ", ") + } + + // 获取请求参数 因为param并且后面request会打印所以不在此处打印 + // var params interface{} + if c.Request.Method == "GET" { + // params = c.Request.URL.Query() + } else { + // 读取请求体 + body, err := io.ReadAll(c.Request.Body) + if err == nil { + // 尝试解析为JSON + var jsonBody interface{} + if err := json.Unmarshal(body, &jsonBody); err == nil { + // params = jsonBody + } else { + // params = string(body) + } + // 恢复请求体 + c.Request.Body = io.NopCloser(bytes.NewBuffer(body)) + } + } + + // 构建日志信息 + logInfo := fmt.Sprintf("Request: %s %s\tClient IP: %s\tHeaders: %s\t", + c.Request.Method, + c.Request.URL.Path, + c.ClientIP(), + formatMap(headers), + ) + + // 如果启用了请求体日志,则记录请求体 + if EnableRequestBodyLogging { + if c.Request.Method != "GET" { + body, err := io.ReadAll(c.Request.Body) + if err == nil { + // 尝试解析为JSON + var jsonBody interface{} + if err := json.Unmarshal(body, &jsonBody); err == nil { + logInfo += fmt.Sprintf("\tBody: %s", formatValue(jsonBody)) + } else { + logInfo += fmt.Sprintf("\tBody: %s", string(body)) + } + // 恢复请求体 + c.Request.Body = io.NopCloser(bytes.NewBuffer(body)) + } + } + } + + common.SysLog(logInfo) + c.Next() + } +} + +func formatMap(m map[string]string) string { + if len(m) == 0 { + return "{}" + } + var pairs []string + for k, v := range m { + pairs = append(pairs, fmt.Sprintf("%s: %s", k, v)) + } + return "{" + strings.Join(pairs, ", ") + "}" +} + +func formatValue(v interface{}) string { + if v == nil { + return "null" + } + switch val := v.(type) { + case string: + return val + case map[string]interface{}: + return formatMapInterface(val) + case []interface{}: + return formatArray(val) + default: + bytes, err := json.Marshal(v) + if err != nil { + return fmt.Sprintf("%v", v) + } + return string(bytes) + } +} + +func formatMapInterface(m map[string]interface{}) string { + if len(m) == 0 { + return "{}" + } + var pairs []string + for k, v := range m { + pairs = append(pairs, fmt.Sprintf("%s: %s", k, formatValue(v))) + } + return "{" + strings.Join(pairs, ", ") + "}" +} + +func formatArray(arr []interface{}) string { + if len(arr) == 0 { + return "[]" + } + var elements []string + for _, v := range arr { + elements = append(elements, formatValue(v)) + } + return "[" + strings.Join(elements, ", ") + "]" +} diff --git a/router/api-router.go b/router/api-router.go index ccef8c2c8a84..1701313bf8a8 100644 --- a/router/api-router.go +++ b/router/api-router.go @@ -77,6 +77,7 @@ func SetApiRouter(router *gin.Engine) { optionRoute.GET("/", controller.GetOptions) optionRoute.PUT("/", controller.UpdateOption) optionRoute.POST("/rest_model_ratio", controller.ResetModelRatio) + optionRoute.POST("/request_log", controller.ToggleRequestLog) } channelRoute := apiRouter.Group("/channel") channelRoute.Use(middleware.AdminAuth()) diff --git a/web/src/pages/Token/EditToken.js b/web/src/pages/Token/EditToken.js index 8fcde7106afd..6c69013150e4 100644 --- a/web/src/pages/Token/EditToken.js +++ b/web/src/pages/Token/EditToken.js @@ -197,6 +197,13 @@ const EditToken = (props) => { showError(t(message)); } } else { + // 验证分组是否已选择 + if (!inputs.group) { + showError(t('请选择令牌分组!')); + setLoading(false); + return; + } + // 处理新增多个令牌的情况 let successCount = 0; // 记录成功创建的令牌数量 for (let i = 0; i < tokenCount; i++) { @@ -438,12 +445,12 @@ const EditToken = (props) => { disabled={!model_limits_enabled} />