package middleware import ( "bytes" "io" "net/http" "strings" "github.com/gin-gonic/gin" "github.com/yuninks/loggerx" ) // 可以被引入的中间件 // 自动设置请求ID // 中间件 gin框架保存请求响应相关信息 func SetGinParams(log *loggerx.Logger) gin.HandlerFunc { return func(ctx *gin.Context) { reqBody := "非json不保存Body" // 请求参数 reqContentType := ctx.Request.Header.Get("Content-Type") if strings.Contains(reqContentType, "application/json") { // 使用bytes.Buffer来读取并记录请求体,同时避免改变ctx.Request.Body buf := &bytes.Buffer{} tea := io.TeeReader(ctx.Request.Body, buf) // 读取body body, err := io.ReadAll(tea) if err != nil { log.Infof(ctx, "Error reading request body: %v", err) ctx.AbortWithStatus(http.StatusInternalServerError) return } // 截取body的前1000个字符 reqBody = getLimitBody(body) // 使用NopCloser包裹buffer,仅为了确保在读取body之后body可以被关闭,但并不改变原始的Request.Body ctx.Request.Body = io.NopCloser(buf) } // 下面是响应参数缓存 blw := &bodyParamsWriter{ body: bytes.NewBufferString(""), ResponseWriter: ctx.Writer, } ctx.Writer = blw req := struct { Method string `json:"method"` Path string `json:"path"` ClientIP string `json:"client_ip"` Body string `json:"body"` Header map[string][]string `json:"header"` }{ Method: ctx.Request.Method, Path: ctx.FullPath(), ClientIP: ctx.ClientIP(), Body: reqBody, Header: ctx.Request.Header, } log.Info(ctx, "request", req) ctx.Next() // 判断返回的值是否json contentType := ctx.Writer.Header().Get("Content-Type") if !strings.Contains(contentType, "application/json") { return } respData := getLimitBody(blw.body.Bytes()) resp := struct { HttpCode int `json:"http_code"` Response string `json:"response"` }{ HttpCode: ctx.Writer.Status(), Response: respData, // Header: ctx.Writer.Header(), } log.Info(ctx, "response", resp) } } type bodyParamsWriter struct { gin.ResponseWriter body *bytes.Buffer } // 真正输出的时候,它会调用这个输出方法 func (w bodyParamsWriter) Write(b []byte) (int, error) { // b = []byte(`{"code":0}`) w.body.Write(b) return w.ResponseWriter.Write(b) } func getLimitBody(body []byte) string { if len(body) > 1000 { return string(body[:1000]) } return string(body) }