package middleware import ( "bytes" "io" "strings" "time" "kra/internal/conf" "kra/internal/service" "kra/internal/service/dto" "github.com/gin-gonic/gin" ) func OperationAudit(runtime *conf.Runtime, service *service.AuditService) gin.HandlerFunc { return func(c *gin.Context) { path := c.Request.URL.Path if !recordsOperation(c.Request.Method, path) { c.Next() return } var requestBody []byte maxBytes := 32768 if config := runtime.Admin(); config != nil && config.Zap != nil && config.Zap.AccessLogMaxBytes > 0 { maxBytes = int(config.Zap.AccessLogMaxBytes) } if c.Request.Body != nil { if strings.Contains(c.GetHeader("Content-Type"), "multipart/form-data") { requestBody = []byte("[文件]") } else { requestBody, _ = io.ReadAll(c.Request.Body) c.Request.Body = io.NopCloser(bytes.NewReader(requestBody)) } } writer := &captureWriter{ResponseWriter: c.Writer, maxBytes: maxBytes} c.Writer = writer started := time.Now() c.Next() userID := uint(0) if claims := Claims(c); claims != nil { userID = claims.ID } requestID, _ := c.Get("request_id") status, errorMessage := c.Writer.Status(), "" if status >= 400 { errorMessage = writer.body.String() } _ = service.RecordOperationRequest(c.Request.Context(), &dto.OperationRecordRequest{IP: c.ClientIP(), Method: c.Request.Method, Path: path, Status: status, LatencyMS: time.Since(started).Milliseconds(), Agent: c.Request.UserAgent(), ErrorMessage: errorMessage, Body: redactJSONLimit(requestBody, maxBytes), Response: writer.body.String(), UserID: userID, RequestID: stringValue(requestID), TraceID: stringValueFromContext(c, "trace_id"), DeviceID: c.GetHeader("X-Device-Id")}) } } // recordsOperation mirrors the routes on which operation records are enabled. // Matching by suffix keeps the behavior stable when router-prefix is configured. func recordsOperation(method, path string) bool { _, ok := operationRoutes[method+" "+routeSuffix(path)] return ok } func routeSuffix(path string) string { for _, marker := range []string{"/user/", "/api/", "/casbin/", "/authority/", "/menu/", "/department/", "/position/", "/sysDictionary/", "/sysDictionaryDetail/", "/sysParams/", "/securityConfig/", "/system/", "/sysApiToken/", "/sysVersion/", "/sysExportTemplate/", "/sysError/", "/sysLoginLog/", "/sysOperationRecord/", "/dataAccessLog/", "/timedTask/", "/info/", "/email/"} { if index := strings.Index(path, marker); index >= 0 { return path[index:] } } return path } var operationRoutes = func() map[string]struct{} { values := []string{ "POST /user/admin_register", "POST /user/changePassword", "POST /user/setUserAuthority", "DELETE /user/deleteUser", "PUT /user/setUserInfo", "PUT /user/setSelfInfo", "POST /user/setUserAuthorities", "POST /user/setUserDepartments", "POST /user/setUserPositions", "POST /user/resetPassword", "PUT /user/setSelfSetting", "GET /api/getApiGroups", "GET /api/syncApi", "POST /api/ignoreApi", "POST /api/enterSyncApi", "POST /api/createApi", "POST /api/deleteApi", "POST /api/getApiById", "POST /api/updateApi", "DELETE /api/deleteApisByIds", "POST /api/setApiRoles", "POST /casbin/updateCasbin", "POST /authority/createAuthority", "POST /authority/deleteAuthority", "PUT /authority/updateAuthority", "POST /authority/copyAuthority", "POST /authority/setDataScope", "POST /authority/setRoleUsers", "POST /menu/addBaseMenu", "POST /menu/addMenuAuthority", "POST /menu/deleteBaseMenu", "POST /menu/updateBaseMenu", "POST /menu/setMenuRoles", "POST /department/createDepartment", "PUT /department/updateDepartment", "DELETE /department/deleteDepartment", "POST /department/setDepartmentUsers", "POST /position/createPosition", "PUT /position/updatePosition", "DELETE /position/deletePosition", "POST /position/setPositionUsers", "POST /sysDictionary/createSysDictionary", "DELETE /sysDictionary/deleteSysDictionary", "PUT /sysDictionary/updateSysDictionary", "POST /sysDictionary/importSysDictionary", "GET /sysDictionary/exportSysDictionary", "POST /sysDictionaryDetail/createSysDictionaryDetail", "DELETE /sysDictionaryDetail/deleteSysDictionaryDetail", "PUT /sysDictionaryDetail/updateSysDictionaryDetail", "POST /sysParams/createSysParams", "DELETE /sysParams/deleteSysParams", "DELETE /sysParams/deleteSysParamsByIds", "PUT /sysParams/updateSysParams", "POST /securityConfig/setSecurityConfig", "POST /system/setSystemConfig", "POST /system/reloadSystem", "POST /sysApiToken/createApiToken", "POST /sysApiToken/getApiTokenList", "POST /sysApiToken/deleteApiToken", "DELETE /sysVersion/deleteSysVersion", "DELETE /sysVersion/deleteSysVersionByIds", "POST /sysVersion/exportVersion", "POST /sysVersion/importVersion", "POST /sysExportTemplate/createSysExportTemplate", "DELETE /sysExportTemplate/deleteSysExportTemplate", "DELETE /sysExportTemplate/deleteSysExportTemplateByIds", "PUT /sysExportTemplate/updateSysExportTemplate", "POST /sysExportTemplate/importExcel", "DELETE /sysError/deleteSysError", "DELETE /sysError/deleteSysErrorByIds", "PUT /sysError/updateSysError", "DELETE /sysLoginLog/deleteLoginLog", "DELETE /sysLoginLog/deleteLoginLogByIds", "DELETE /sysOperationRecord/deleteSysOperationRecord", "DELETE /sysOperationRecord/deleteSysOperationRecordByIds", "DELETE /dataAccessLog/deleteDataAccessLogByIds", "POST /timedTask/createTimedTask", "PUT /timedTask/updateTimedTask", "DELETE /timedTask/deleteTimedTask", "POST /timedTask/toggleTimedTask", "POST /timedTask/triggerTimedTask", "POST /info/createInfo", "DELETE /info/deleteInfo", "DELETE /info/deleteInfoByIds", "PUT /info/updateInfo", "POST /email/emailTest", "POST /email/sendEmail", } out := make(map[string]struct{}, len(values)) for _, value := range values { out[value] = struct{}{} } return out }()