174 lines
3.9 KiB
Go
174 lines
3.9 KiB
Go
|
|
/**
|
|||
|
|
* package middleware
|
|||
|
|
* 作用:接口请求日志中间件
|
|||
|
|
* 说明:记录所有API请求信息,异步写入数据库
|
|||
|
|
*/
|
|||
|
|
package middleware
|
|||
|
|
|
|||
|
|
import (
|
|||
|
|
"bytes"
|
|||
|
|
"encoding/json"
|
|||
|
|
"io"
|
|||
|
|
"strings"
|
|||
|
|
"time"
|
|||
|
|
"xk-websocket-v2/internal/model"
|
|||
|
|
"xk-websocket-v2/internal/utils"
|
|||
|
|
|
|||
|
|
"github.com/gin-gonic/gin"
|
|||
|
|
"gorm.io/gorm"
|
|||
|
|
)
|
|||
|
|
|
|||
|
|
// RequestLogMiddleware 请求日志中间件
|
|||
|
|
type RequestLogMiddleware struct {
|
|||
|
|
DB *gorm.DB
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// NewRequestLogMiddleware 创建请求日志中间件
|
|||
|
|
func NewRequestLogMiddleware(db *gorm.DB) *RequestLogMiddleware {
|
|||
|
|
return &RequestLogMiddleware{DB: db}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// Handler 中间件处理函数
|
|||
|
|
func (m *RequestLogMiddleware) Handler() gin.HandlerFunc {
|
|||
|
|
return func(c *gin.Context) {
|
|||
|
|
// 跳过OPTIONS请求
|
|||
|
|
if c.Request.Method == "OPTIONS" {
|
|||
|
|
c.Next()
|
|||
|
|
return
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 获取请求IP
|
|||
|
|
ip := getClientIP(c)
|
|||
|
|
|
|||
|
|
// 本地IP不记录
|
|||
|
|
if utils.GetIPLocation(ip) == "本地" {
|
|||
|
|
c.Next()
|
|||
|
|
return
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 获取请求参数
|
|||
|
|
var requestBody []byte
|
|||
|
|
if c.Request.Body != nil {
|
|||
|
|
requestBody, _ = io.ReadAll(c.Request.Body)
|
|||
|
|
c.Request.Body = io.NopCloser(bytes.NewBuffer(requestBody))
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 获取用户ID(从Context中获取,未登录为"0")
|
|||
|
|
userID := "0"
|
|||
|
|
if uid, exists := c.Get("user_id"); exists {
|
|||
|
|
if uidStr, ok := uid.(string); ok {
|
|||
|
|
userID = uidStr
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 记录开始时间
|
|||
|
|
startTime := time.Now()
|
|||
|
|
|
|||
|
|
// 创建响应写入器
|
|||
|
|
writer := &responseWriter{
|
|||
|
|
ResponseWriter: c.Writer,
|
|||
|
|
body: &bytes.Buffer{},
|
|||
|
|
}
|
|||
|
|
c.Writer = writer
|
|||
|
|
|
|||
|
|
// 处理请求
|
|||
|
|
c.Next()
|
|||
|
|
|
|||
|
|
// 计算请求时间
|
|||
|
|
duration := time.Since(startTime)
|
|||
|
|
|
|||
|
|
// 异步记录日志(避免影响性能)
|
|||
|
|
go m.logRequest(c, ip, userID, requestBody, writer.body.Bytes(), writer.status, duration)
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// logRequest 记录请求日志
|
|||
|
|
func (m *RequestLogMiddleware) logRequest(c *gin.Context, ip, userID string, requestBody, responseBody []byte, httpStatus int, duration time.Duration) {
|
|||
|
|
// 获取IP归属地
|
|||
|
|
location := utils.GetIPLocation(ip)
|
|||
|
|
|
|||
|
|
// 获取响应code(从响应体中解析)
|
|||
|
|
responseCode := 0
|
|||
|
|
if len(responseBody) > 0 {
|
|||
|
|
// 尝试解析响应体获取code
|
|||
|
|
var resp model.ApiResponse
|
|||
|
|
if err := json.Unmarshal(responseBody, &resp); err == nil {
|
|||
|
|
responseCode = resp.Code
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 限制请求参数长度(避免存储过大)
|
|||
|
|
requestParams := string(requestBody)
|
|||
|
|
if len(requestParams) > 5000 {
|
|||
|
|
requestParams = requestParams[:5000] + "...(truncated)"
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 限制返回参数长度
|
|||
|
|
responseParams := string(responseBody)
|
|||
|
|
if len(responseParams) > 5000 {
|
|||
|
|
responseParams = responseParams[:5000] + "...(truncated)"
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 创建日志记录
|
|||
|
|
log := model.ApiRequestLog{
|
|||
|
|
Route: c.FullPath(),
|
|||
|
|
IP: ip,
|
|||
|
|
IPLocation: location,
|
|||
|
|
UserID: userID,
|
|||
|
|
Method: c.Request.Method,
|
|||
|
|
RequestParams: requestParams,
|
|||
|
|
ResponseParams: responseParams,
|
|||
|
|
ResponseCode: responseCode,
|
|||
|
|
HTTPStatus: httpStatus,
|
|||
|
|
RequestTime: time.Now(),
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 异步写入数据库
|
|||
|
|
m.DB.Create(&log)
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// getClientIP 获取客户端IP
|
|||
|
|
func getClientIP(c *gin.Context) string {
|
|||
|
|
// 优先从X-Forwarded-For获取
|
|||
|
|
ip := c.GetHeader("X-Forwarded-For")
|
|||
|
|
if ip != "" {
|
|||
|
|
// X-Forwarded-For可能包含多个IP,取第一个
|
|||
|
|
ips := strings.Split(ip, ",")
|
|||
|
|
if len(ips) > 0 {
|
|||
|
|
return strings.TrimSpace(ips[0])
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 从X-Real-IP获取
|
|||
|
|
ip = c.GetHeader("X-Real-IP")
|
|||
|
|
if ip != "" {
|
|||
|
|
return ip
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 从RemoteAddr获取
|
|||
|
|
return c.ClientIP()
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// responseWriter 响应写入器(用于捕获响应内容)
|
|||
|
|
type responseWriter struct {
|
|||
|
|
gin.ResponseWriter
|
|||
|
|
body *bytes.Buffer
|
|||
|
|
status int
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
func (w *responseWriter) Write(b []byte) (int, error) {
|
|||
|
|
w.body.Write(b)
|
|||
|
|
return w.ResponseWriter.Write(b)
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
func (w *responseWriter) WriteString(s string) (int, error) {
|
|||
|
|
w.body.WriteString(s)
|
|||
|
|
return w.ResponseWriter.WriteString(s)
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
func (w *responseWriter) WriteHeader(statusCode int) {
|
|||
|
|
w.status = statusCode
|
|||
|
|
w.ResponseWriter.WriteHeader(statusCode)
|
|||
|
|
}
|
|||
|
|
|