package collectlogs import ( "encoding/json" "fmt" "reflect" "strings" "time" "be.ems/src/framework/constants" "be.ems/src/framework/i18n" "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" "github.com/gin-gonic/gin" ) const ( // 业务操作类型-其它 BUSINESS_TYPE_OTHER = "0" // 业务操作类型-新增 BUSINESS_TYPE_INSERT = "1" // 业务操作类型-修改 BUSINESS_TYPE_UPDATE = "2" // 业务操作类型-删除 BUSINESS_TYPE_DELETE = "3" // 业务操作类型-授权 BUSINESS_TYPE_GRANT = "4" // 业务操作类型-导出 BUSINESS_TYPE_EXPORT = "5" // 业务操作类型-导入 BUSINESS_TYPE_IMPORT = "6" // 业务操作类型-强退 BUSINESS_TYPE_FORCE = "7" // 业务操作类型-清空数据 BUSINESS_TYPE_CLEAN = "8" ) const ( // 操作人类别-其它 OPERATOR_TYPE_OTHER = "0" // 操作人类别-后台用户 OPERATOR_TYPE_MANAGE = "1" // 操作人类别-手机端用户 OPERATOR_TYPE_MOBILE = "2" ) // Option 操作日志参数 type Options struct { Title string `json:"title"` // 标题 BusinessType string `json:"businessType"` // 类型,默认常量 BUSINESS_TYPE_OTHER OperatorType string `json:"operatorType"` // 操作人类别,默认常量 OPERATOR_TYPE_OTHER IsSaveRequestData bool `json:"isSaveRequestData"` // 是否保存请求的参数 IsSaveResponseData bool `json:"isSaveResponseData"` // 是否保存响应的参数 } // OptionNew 操作日志参数默认值 // // 标题 "title":"--" // // 类型 "businessType": BUSINESS_TYPE_OTHER // // 注意之后JSON反序列使用:c.ShouldBindBodyWith(¶ms, binding.JSON) func OptionNew(title, businessType string) Options { return Options{ Title: title, BusinessType: businessType, OperatorType: OPERATOR_TYPE_OTHER, IsSaveRequestData: true, IsSaveResponseData: true, } } // OperateLog 访问操作日志记录 // // 请在用户身份授权认证校验后使用以便获取登录用户信息 func OperateLog(options Options) gin.HandlerFunc { return func(c *gin.Context) { c.Set("startTime", time.Now()) language := reqctx.AcceptLanguage(c) // 函数名 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(resp.CODE_AUTH_INVALID, i18n.TKey(language, err.Error()))) c.Abort() // 停止执行后续的处理函数 return } // 操作日志记录 operLog := model.SysLogOperate{ Title: options.Title, BusinessType: options.BusinessType, OperaMethod: funcName, OperaUrl: c.Request.URL.Path, 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] } operLog.OperaParam = paramsStr } // 调用下一个处理程序 c.Next() // 响应状态 status := c.Writer.Status() if status == 200 { operLog.StatusFlag = constants.STATUS_YES } else { operLog.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) operLog.OperaMsg = msg } // 日志记录时间 duration := time.Since(c.GetTime("startTime")) operLog.CostTime = duration.Milliseconds() operLog.OperaTime = time.Now().UnixMilli() // 保存操作记录到数据库 service.NewSysLogOperate.Insert(operLog) } } // 敏感属性字段进行掩码 var maskProperties []string = []string{ "password", "privateKey", "privatePassword", "passPhrase", "oldPassword", "newPassword", "confirmPassword", constants.ACCESS_TOKEN, constants.ACCESS_TOKEN_QUERY, } // 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()) } } }