Files
be.ems/src/framework/middleware/operate_log.go
TsMask 80d612c56c Refactor error handling in system and trace controllers
- Updated error response codes for various validation errors from 400 to 422 to better reflect the nature of the errors.
- Changed error messages for empty parameters (e.g., userId, menuId, roleId) to use a consistent error code format.
- Improved error handling in the IPerf, Ping, and WS controllers to provide more informative error messages.
- Ensured that all controllers return appropriate error messages when binding JSON or query parameters fails.
2025-04-27 16:38:19 +08:00

196 lines
5.0 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
package middleware
import (
"encoding/json"
"fmt"
"reflect"
"strings"
"time"
"github.com/gin-gonic/gin"
"be.ems/src/framework/constants"
"be.ems/src/framework/reqctx"
"be.ems/src/framework/resp"
"be.ems/src/framework/utils/parse"
"be.ems/src/modules/system/model"
"be.ems/src/modules/system/service"
)
const (
// BUSINESS_TYPE_OTHER 业务操作类型-其它
BUSINESS_TYPE_OTHER = "0"
// BUSINESS_TYPE_INSERT 业务操作类型-新增
BUSINESS_TYPE_INSERT = "1"
// BUSINESS_TYPE_UPDATE 业务操作类型-修改
BUSINESS_TYPE_UPDATE = "2"
// BUSINESS_TYPE_DELETE 业务操作类型-删除
BUSINESS_TYPE_DELETE = "3"
// BUSINESS_TYPE_GRANT 业务操作类型-授权
BUSINESS_TYPE_GRANT = "4"
// BUSINESS_TYPE_EXPORT 业务操作类型-导出
BUSINESS_TYPE_EXPORT = "5"
// BUSINESS_TYPE_IMPORT 业务操作类型-导入
BUSINESS_TYPE_IMPORT = "6"
// BUSINESS_TYPE_FORCE 业务操作类型-强退
BUSINESS_TYPE_FORCE = "7"
// BUSINESS_TYPE_CLEAN 业务操作类型-清空数据
BUSINESS_TYPE_CLEAN = "8"
)
// Options Option 操作日志参数
type Options struct {
Title string `json:"title"` // 标题
BusinessType string `json:"businessType"` // 类型,默认常量 BUSINESS_TYPE_OTHER
IsSaveRequestData bool `json:"isSaveRequestData"` // 是否保存请求的参数
IsSaveResponseData bool `json:"isSaveResponseData"` // 是否保存响应的参数
}
// OptionNew 操作日志参数默认值
//
// 标题 "title":"--"
//
// 类型 "businessType": BUSINESS_TYPE_OTHER
//
// 注意之后JSON反序列使用c.ShouldBindBodyWithJSON(&params)
func OptionNew(title, businessType string) Options {
return Options{
Title: title,
BusinessType: businessType,
IsSaveRequestData: true,
IsSaveResponseData: true,
}
}
// OperateLog 访问操作日志记录
//
// 请在用户身份授权认证校验后使用以便获取登录用户信息
func OperateLog(options Options) gin.HandlerFunc {
return func(c *gin.Context) {
c.Set("startTime", time.Now())
// 函数名
funcName := c.HandlerName()
lastDotIndex := strings.LastIndex(funcName, "/")
funcName = funcName[lastDotIndex+1:]
// 解析ip地址
ipaddr, location := reqctx.IPAddrLocation(c)
// 获取登录用户信息
loginUser, err := reqctx.LoginUser(c)
if err != nil {
c.JSON(401, resp.CodeMsg(401002, "invalid login user information"))
c.Abort() // 停止执行后续的处理函数
return
}
// 操作日志记录
operaLog := model.SysLogOperate{
Title: options.Title,
BusinessType: options.BusinessType,
OperaMethod: funcName,
OperaUrl: c.Request.RequestURI,
OperaUrlMethod: c.Request.Method,
OperaIp: ipaddr,
OperaLocation: location,
OperaBy: loginUser.User.UserName,
}
// 是否需要保存request参数和值
if options.IsSaveRequestData {
params := reqctx.RequestParamsMap(c)
// 敏感属性字段进行掩码
processSensitiveFields(params)
jsonStr, _ := json.Marshal(params)
paramsStr := string(jsonStr)
if len(paramsStr) > 2000 {
paramsStr = paramsStr[:2000]
}
operaLog.OperaParam = paramsStr
}
// 调用下一个处理程序
c.Next()
// 响应状态
status := c.Writer.Status()
if status == 200 {
operaLog.StatusFlag = constants.STATUS_YES
} else {
operaLog.StatusFlag = constants.STATUS_NO
}
// 是否需要保存response参数和值
if options.IsSaveResponseData {
contentDisposition := c.Writer.Header().Get("Content-Disposition")
contentType := c.Writer.Header().Get("Content-Type")
content := contentType + contentDisposition
msg := fmt.Sprintf(`{"status":"%d","size":%d,"content-type":"%s"}`, status, c.Writer.Size(), content)
operaLog.OperaMsg = msg
}
// 日志记录时间
duration := time.Since(c.GetTime("startTime"))
operaLog.CostTime = duration.Milliseconds()
operaLog.OperaTime = time.Now().UnixMilli()
// 保存操作记录到数据库
service.NewSysLogOperate.Insert(operaLog)
}
}
// 敏感属性字段进行掩码
var maskProperties = []string{
"password",
"oldPassword",
"newPassword",
"confirmPassword",
}
// processSensitiveFields 处理敏感属性字段
func processSensitiveFields(obj interface{}) {
val := reflect.ValueOf(obj)
switch val.Kind() {
case reflect.Map:
for _, key := range val.MapKeys() {
value := val.MapIndex(key)
keyStr := key.Interface().(string)
// 遍历是否敏感属性
hasMaskKey := false
for _, v := range maskProperties {
if v == keyStr {
hasMaskKey = true
break
}
}
if hasMaskKey {
valueStr := value.Interface().(string)
if len(valueStr) > 100 {
valueStr = valueStr[0:100]
}
val.SetMapIndex(key, reflect.ValueOf(parse.SafeContent(valueStr)))
} else {
processSensitiveFields(value.Interface())
}
}
case reflect.Slice, reflect.Array:
for i := 0; i < val.Len(); i++ {
processSensitiveFields(val.Index(i).Interface())
}
// default:
// logger.Infof("processSensitiveFields unhandled case %v", val.Kind())
}
}