operation.go 4.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165
  1. package middleware
  2. import (
  3. "bytes"
  4. "encoding/json"
  5. "io"
  6. "net/http"
  7. "net/url"
  8. "wails-app/internal/dao"
  9. "strconv"
  10. "strings"
  11. "time"
  12. "wails-app/internal/pkg"
  13. "github.com/gin-gonic/gin"
  14. "go.uber.org/zap"
  15. "wails-app/internal/global"
  16. "wails-app/internal/service"
  17. )
  18. var operationRecordService = service.ServiceGroupApp.SystemServiceGroup.OperationRecordService
  19. var bufferSize = 1024
  20. // sensitiveFields 操作日志脱敏字段(不区分大小写):密码、令牌、密钥、支付敏感字段。
  21. var sensitiveFields = map[string]bool{
  22. "password": true, "old_password": true, "new_password": true,
  23. "confirm_password": true, "pwd": true, "pass": true,
  24. "token": true, "access_token": true, "refresh_token": true, "jwt": true,
  25. "secret": true, "private_key": true,
  26. }
  27. // maskSensitiveJSON 对 JSON 文本中的敏感字段值脱敏(非 JSON 原样返回)。
  28. func maskSensitiveJSON(data []byte) []byte {
  29. var obj interface{}
  30. if err := json.Unmarshal(data, &obj); err != nil {
  31. return data
  32. }
  33. out, err := json.Marshal(maskValue(obj))
  34. if err != nil {
  35. return data
  36. }
  37. return out
  38. }
  39. // maskValue 递归脱敏:命中敏感字段名时值替换为 "******"(空值保留)。
  40. func maskValue(v interface{}) interface{} {
  41. switch value := v.(type) {
  42. case map[string]interface{}:
  43. for key, child := range value {
  44. if sensitiveFields[strings.ToLower(key)] {
  45. if child != nil {
  46. value[key] = "******"
  47. }
  48. continue
  49. }
  50. value[key] = maskValue(child)
  51. }
  52. return value
  53. case []interface{}:
  54. for i := range value {
  55. value[i] = maskValue(value[i])
  56. }
  57. return value
  58. default:
  59. return v
  60. }
  61. }
  62. // maskResponse 响应体脱敏并截断(修复旧代码只对下载类响应截断、且误写 record.Body 字段的问题)。
  63. func maskResponse(resp string) string {
  64. masked := maskSensitiveJSON([]byte(resp))
  65. if len(masked) > bufferSize {
  66. return "[超出记录长度]"
  67. }
  68. return string(masked)
  69. }
  70. func OperationRecord() gin.HandlerFunc {
  71. return func(c *gin.Context) {
  72. var body []byte
  73. var userId int
  74. if c.Request.Method != http.MethodGet {
  75. var err error
  76. body, err = io.ReadAll(c.Request.Body)
  77. if err != nil {
  78. global.GVA_LOG.Error("read body from request error:", zap.Error(err))
  79. } else {
  80. c.Request.Body = io.NopCloser(bytes.NewBuffer(body))
  81. }
  82. } else {
  83. query := c.Request.URL.RawQuery
  84. query, _ = url.QueryUnescape(query)
  85. split := strings.Split(query, "&")
  86. m := make(map[string]string)
  87. for _, v := range split {
  88. kv := strings.Split(v, "=")
  89. if len(kv) == 2 {
  90. m[kv[0]] = kv[1]
  91. }
  92. }
  93. body, _ = json.Marshal(&m)
  94. }
  95. claims, _ := utils.GetClaims(c)
  96. if claims != nil && claims.BaseClaims.ID != 0 {
  97. userId = int(claims.BaseClaims.ID)
  98. } else {
  99. id, err := strconv.Atoi(c.Request.Header.Get("x-user-id"))
  100. if err != nil {
  101. userId = 0
  102. }
  103. userId = id
  104. }
  105. record := dao.SysOperationRecord{
  106. Ip: c.ClientIP(),
  107. Method: c.Request.Method,
  108. Path: c.Request.URL.Path,
  109. Agent: c.Request.UserAgent(),
  110. Body: "",
  111. UserID: userId,
  112. }
  113. // 上传文件时候 中间件日志进行裁断操作
  114. if strings.Contains(c.GetHeader("Content-Type"), "multipart/form-data") {
  115. record.Body = "[文件]"
  116. } else {
  117. masked := maskSensitiveJSON(body)
  118. if len(masked) > bufferSize {
  119. record.Body = "[超出记录长度]"
  120. } else {
  121. record.Body = string(masked)
  122. }
  123. }
  124. writer := responseBodyWriter{
  125. ResponseWriter: c.Writer,
  126. body: &bytes.Buffer{},
  127. }
  128. c.Writer = writer
  129. now := time.Now()
  130. c.Next()
  131. latency := time.Since(now)
  132. record.ErrorMessage = c.Errors.ByType(gin.ErrorTypePrivate).String()
  133. record.Status = c.Writer.Status()
  134. record.Latency = latency
  135. record.Resp = maskResponse(writer.body.String())
  136. if err := operationRecordService.CreateSysOperationRecord(record); err != nil {
  137. global.GVA_LOG.Error("create operation record error:", zap.Error(err))
  138. }
  139. }
  140. }
  141. type responseBodyWriter struct {
  142. gin.ResponseWriter
  143. body *bytes.Buffer
  144. }
  145. func (r responseBodyWriter) Write(b []byte) (int, error) {
  146. r.body.Write(b)
  147. return r.ResponseWriter.Write(b)
  148. }