Fix Services & Update the middleware && others

This commit is contained in:
XOF
2025-11-24 04:48:07 +08:00
parent 3a95a07e8a
commit f2706d6fc8
37 changed files with 4458 additions and 1166 deletions

View File

@@ -1,84 +1,213 @@
// Filename: internal/middleware/log_redaction.go
// Filename: internal/middleware/logging.go
package middleware
import (
"bytes"
"io"
"regexp"
"strings"
"time"
"github.com/gin-gonic/gin"
"github.com/sirupsen/logrus"
)
const RedactedBodyKey = "redactedBody"
const RedactedAuthHeaderKey = "redactedAuthHeader"
const RedactedValue = `"[REDACTED]"`
const (
RedactedBodyKey = "redactedBody"
RedactedAuthHeaderKey = "redactedAuthHeader"
RedactedValue = `"[REDACTED]"`
)
// 预编译正则表达式(全局变量,提升性能)
var (
// JSON 敏感字段脱敏
jsonSensitiveKeys = regexp.MustCompile(`("(?i:api_key|apikey|token|password|secret|authorization|key|keys|auth)"\s*:\s*)"[^"]*"`)
// Bearer Token 脱敏
bearerTokenPattern = regexp.MustCompile(`^(Bearer\s+)\S+$`)
// URL 中的 key 参数脱敏
queryKeyPattern = regexp.MustCompile(`([?&](?i:key|token|apikey)=)[^&\s]+`)
)
// RedactionMiddleware 请求数据脱敏中间件
func RedactionMiddleware() gin.HandlerFunc {
// Pre-compile regex for efficiency
jsonKeyPattern := regexp.MustCompile(`("api_key"|"keys")\s*:\s*"[^"]*"`)
bearerTokenPattern := regexp.MustCompile(`^(Bearer\s+)\S+$`)
return func(c *gin.Context) {
// --- 1. Redact Request Body ---
if c.Request.Method == "POST" || c.Request.Method == "PUT" || c.Request.Method == "DELETE" {
if bodyBytes, err := io.ReadAll(c.Request.Body); err == nil {
c.Request.Body = io.NopCloser(bytes.NewBuffer(bodyBytes))
bodyString := string(bodyBytes)
redactedBody := jsonKeyPattern.ReplaceAllString(bodyString, `$1:`+RedactedValue)
c.Set(RedactedBodyKey, redactedBody)
}
}
// --- 2. Redact Authorization Header ---
authHeader := c.GetHeader("Authorization")
if authHeader != "" {
if bearerTokenPattern.MatchString(authHeader) {
redactedHeader := bearerTokenPattern.ReplaceAllString(authHeader, `${1}[REDACTED]`)
c.Set(RedactedAuthHeaderKey, redactedHeader)
}
// 1. 脱敏请求体
if shouldRedactBody(c) {
redactRequestBody(c)
}
// 2. 脱敏认证头
redactAuthHeader(c)
// 3. 脱敏 URL 查询参数
redactQueryParams(c)
c.Next()
}
}
// LogrusLogger is a Gin middleware that logs requests using a Logrus logger.
// It consumes redacted data prepared by the RedactionMiddleware.
// shouldRedactBody 判断是否需要脱敏请求体
func shouldRedactBody(c *gin.Context) bool {
method := c.Request.Method
contentType := c.GetHeader("Content-Type")
// 只处理包含 JSON 的 POST/PUT/PATCH/DELETE 请求
return (method == "POST" || method == "PUT" || method == "PATCH" || method == "DELETE") &&
strings.Contains(contentType, "application/json")
}
// redactRequestBody 脱敏请求体
func redactRequestBody(c *gin.Context) {
bodyBytes, err := io.ReadAll(c.Request.Body)
if err != nil {
return
}
// 恢复请求体供后续使用
c.Request.Body = io.NopCloser(bytes.NewBuffer(bodyBytes))
// 脱敏敏感字段
bodyString := string(bodyBytes)
redactedBody := jsonSensitiveKeys.ReplaceAllString(bodyString, `$1`+RedactedValue)
c.Set(RedactedBodyKey, redactedBody)
}
// redactAuthHeader 脱敏认证头
func redactAuthHeader(c *gin.Context) {
authHeader := c.GetHeader("Authorization")
if authHeader == "" {
return
}
if bearerTokenPattern.MatchString(authHeader) {
redacted := bearerTokenPattern.ReplaceAllString(authHeader, `${1}[REDACTED]`)
c.Set(RedactedAuthHeaderKey, redacted)
} else {
// 对于非 Bearer 的 token全部脱敏
c.Set(RedactedAuthHeaderKey, "[REDACTED]")
}
// 同时处理其他敏感 Header
sensitiveHeaders := []string{"X-Api-Key", "X-Goog-Api-Key", "Api-Key"}
for _, header := range sensitiveHeaders {
if value := c.GetHeader(header); value != "" {
c.Set("redacted_"+header, "[REDACTED]")
}
}
}
// redactQueryParams 脱敏 URL 查询参数
func redactQueryParams(c *gin.Context) {
rawQuery := c.Request.URL.RawQuery
if rawQuery == "" {
return
}
redacted := queryKeyPattern.ReplaceAllString(rawQuery, `${1}[REDACTED]`)
if redacted != rawQuery {
c.Set("redactedQuery", redacted)
}
}
// LogrusLogger Gin 请求日志中间件(使用 Logrus
func LogrusLogger(logger *logrus.Logger) gin.HandlerFunc {
return func(c *gin.Context) {
start := time.Now()
path := c.Request.URL.Path
method := c.Request.Method
// Process request
// 处理请求
c.Next()
// After request, gather data and log
// 计算延迟
latency := time.Since(start)
statusCode := c.Writer.Status()
clientIP := c.ClientIP()
entry := logger.WithFields(logrus.Fields{
"status_code": statusCode,
"latency_ms": latency.Milliseconds(),
"client_ip": c.ClientIP(),
"method": c.Request.Method,
"path": path,
})
// 构建日志字段
fields := logrus.Fields{
"status": statusCode,
"latency_ms": latency.Milliseconds(),
"ip": clientIP,
"method": method,
"path": path,
}
// 添加请求 ID如果存在
if requestID := getRequestID(c); requestID != "" {
fields["request_id"] = requestID
}
// 添加脱敏后的数据
if redactedBody, exists := c.Get(RedactedBodyKey); exists {
entry = entry.WithField("body", redactedBody)
fields["body"] = redactedBody
}
if redactedAuth, exists := c.Get(RedactedAuthHeaderKey); exists {
entry = entry.WithField("authorization", redactedAuth)
fields["authorization"] = redactedAuth
}
if redactedQuery, exists := c.Get("redactedQuery"); exists {
fields["query"] = redactedQuery
}
// 添加用户信息(如果已认证)
if user := getAuthenticatedUser(c); user != "" {
fields["user"] = user
}
// 根据状态码选择日志级别
entry := logger.WithFields(fields)
if len(c.Errors) > 0 {
entry.Error(c.Errors.String())
fields["errors"] = c.Errors.String()
entry.Error("Request failed")
} else {
entry.Info("request handled")
switch {
case statusCode >= 500:
entry.Error("Server error")
case statusCode >= 400:
entry.Warn("Client error")
case statusCode >= 300:
entry.Info("Redirect")
default:
// 只在 Debug 模式记录成功请求
if logger.Level >= logrus.DebugLevel {
entry.Debug("Request completed")
}
}
}
}
}
// getRequestID 获取请求 ID
func getRequestID(c *gin.Context) string {
if id, exists := c.Get("request_id"); exists {
if requestID, ok := id.(string); ok {
return requestID
}
}
return ""
}
// getAuthenticatedUser 获取已认证用户标识
func getAuthenticatedUser(c *gin.Context) string {
// 尝试从不同来源获取用户信息
if user, exists := c.Get("adminUser"); exists {
if authToken, ok := user.(interface{ GetID() string }); ok {
return authToken.GetID()
}
}
if user, exists := c.Get("authToken"); exists {
if authToken, ok := user.(interface{ GetID() string }); ok {
return authToken.GetID()
}
}
return ""
}