|
|
@@ -9,7 +9,6 @@ import (
|
|
|
"wails-app/internal/dao"
|
|
|
"strconv"
|
|
|
"strings"
|
|
|
- "sync"
|
|
|
"time"
|
|
|
|
|
|
"wails-app/internal/pkg"
|
|
|
@@ -22,13 +21,60 @@ import (
|
|
|
|
|
|
var operationRecordService = service.ServiceGroupApp.SystemServiceGroup.OperationRecordService
|
|
|
|
|
|
-var respPool sync.Pool
|
|
|
var bufferSize = 1024
|
|
|
|
|
|
-func init() {
|
|
|
- respPool.New = func() interface{} {
|
|
|
- return make([]byte, bufferSize)
|
|
|
+// sensitiveFields 操作日志脱敏字段(不区分大小写):密码、令牌、密钥、支付敏感字段。
|
|
|
+var sensitiveFields = map[string]bool{
|
|
|
+ "password": true, "old_password": true, "new_password": true,
|
|
|
+ "confirm_password": true, "pwd": true, "pass": true,
|
|
|
+ "token": true, "access_token": true, "refresh_token": true, "jwt": true,
|
|
|
+ "secret": true, "private_key": true,
|
|
|
+}
|
|
|
+
|
|
|
+// maskSensitiveJSON 对 JSON 文本中的敏感字段值脱敏(非 JSON 原样返回)。
|
|
|
+func maskSensitiveJSON(data []byte) []byte {
|
|
|
+ var obj interface{}
|
|
|
+ if err := json.Unmarshal(data, &obj); err != nil {
|
|
|
+ return data
|
|
|
}
|
|
|
+ out, err := json.Marshal(maskValue(obj))
|
|
|
+ if err != nil {
|
|
|
+ return data
|
|
|
+ }
|
|
|
+ return out
|
|
|
+}
|
|
|
+
|
|
|
+// maskValue 递归脱敏:命中敏感字段名时值替换为 "******"(空值保留)。
|
|
|
+func maskValue(v interface{}) interface{} {
|
|
|
+ switch value := v.(type) {
|
|
|
+ case map[string]interface{}:
|
|
|
+ for key, child := range value {
|
|
|
+ if sensitiveFields[strings.ToLower(key)] {
|
|
|
+ if child != nil {
|
|
|
+ value[key] = "******"
|
|
|
+ }
|
|
|
+ continue
|
|
|
+ }
|
|
|
+ value[key] = maskValue(child)
|
|
|
+ }
|
|
|
+ return value
|
|
|
+ case []interface{}:
|
|
|
+ for i := range value {
|
|
|
+ value[i] = maskValue(value[i])
|
|
|
+ }
|
|
|
+ return value
|
|
|
+ default:
|
|
|
+ return v
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+// maskResponse 响应体脱敏并截断(修复旧代码只对下载类响应截断、且误写 record.Body 字段的问题)。
|
|
|
+func maskResponse(resp string) string {
|
|
|
+ masked := maskSensitiveJSON([]byte(resp))
|
|
|
+ if len(masked) > bufferSize {
|
|
|
+ return "[超出记录长度]"
|
|
|
+ }
|
|
|
+ return string(masked)
|
|
|
}
|
|
|
|
|
|
func OperationRecord() gin.HandlerFunc {
|
|
|
@@ -79,10 +125,11 @@ func OperationRecord() gin.HandlerFunc {
|
|
|
if strings.Contains(c.GetHeader("Content-Type"), "multipart/form-data") {
|
|
|
record.Body = "[文件]"
|
|
|
} else {
|
|
|
- if len(body) > bufferSize {
|
|
|
+ masked := maskSensitiveJSON(body)
|
|
|
+ if len(masked) > bufferSize {
|
|
|
record.Body = "[超出记录长度]"
|
|
|
} else {
|
|
|
- record.Body = string(body)
|
|
|
+ record.Body = string(masked)
|
|
|
}
|
|
|
}
|
|
|
|
|
|
@@ -99,22 +146,7 @@ func OperationRecord() gin.HandlerFunc {
|
|
|
record.ErrorMessage = c.Errors.ByType(gin.ErrorTypePrivate).String()
|
|
|
record.Status = c.Writer.Status()
|
|
|
record.Latency = latency
|
|
|
- record.Resp = writer.body.String()
|
|
|
-
|
|
|
- if strings.Contains(c.Writer.Header().Get("Pragma"), "public") ||
|
|
|
- strings.Contains(c.Writer.Header().Get("Expires"), "0") ||
|
|
|
- strings.Contains(c.Writer.Header().Get("Cache-Control"), "must-revalidate, post-check=0, pre-check=0") ||
|
|
|
- strings.Contains(c.Writer.Header().Get("Content-Type"), "application/force-download") ||
|
|
|
- strings.Contains(c.Writer.Header().Get("Content-Type"), "application/octet-stream") ||
|
|
|
- strings.Contains(c.Writer.Header().Get("Content-Type"), "application/vnd.ms-excel") ||
|
|
|
- strings.Contains(c.Writer.Header().Get("Content-Type"), "application/download") ||
|
|
|
- strings.Contains(c.Writer.Header().Get("Content-Disposition"), "attachment") ||
|
|
|
- strings.Contains(c.Writer.Header().Get("Content-Transfer-Encoding"), "binary") {
|
|
|
- if len(record.Resp) > bufferSize {
|
|
|
- // 截断
|
|
|
- record.Body = "超出记录长度"
|
|
|
- }
|
|
|
- }
|
|
|
+ record.Resp = maskResponse(writer.body.String())
|
|
|
|
|
|
if err := operationRecordService.CreateSysOperationRecord(record); err != nil {
|
|
|
global.GVA_LOG.Error("create operation record error:", zap.Error(err))
|