RuoYi-Vue 前后端分离版代码浅析-操作日志记录切面

1,748 阅读2分钟

这是我参与11月更文挑战的第11天,活动详情查看:2021最后一次更文挑战

前言

本节介绍RuoYi-Vue的模块中是如何进行操作日志切面的,这是一个很重要的地方,毕竟作为操作日志,可以帮助我们记录用户的各种操作,当出现问题时也可以很快的进行排查,但是怎样才是最优雅的记录日志的方式呢,最简单的方式是用注解来直接一步解决!

image.png

@Log(title = "参数管理", businessType = BusinessType.INSERT)

image.png

注解部分

@Target({ElementType.PARAMETER, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Log {
    /**
     * 模块
     */
    public String title() default "";

    /**
     * 功能
     */
    public BusinessType businessType() default BusinessType.OTHER;

    /**
     * 操作人类别
     */
    public OperatorType operatorType() default OperatorType.MANAGE;

    /**
     * 是否保存请求的参数
     */
    public boolean isSaveRequestData() default true;

    /**
     * 是否保存响应的参数
     */
    public boolean isSaveResponseData() default true;
}

可以看到,它是针对参数和方法起效的,默认是会保存请求和响应的参数,也可以通过设置为false来节省数据库的空间。

对应的处理逻辑

sys_oper_log表

当对应的方法有了注解之后,我们将如何进行日志的存储和处理呢?在Ruoyi中有一个sys_oper_log表来进行操作日志的记录。

image.png

对应的切面就是要将对应的数据放入其中,放的时机分别是 @AfterReturning@AfterThrowing,从而能够在处理完请求后和出现异常之后进行日志的记录。

两者都是调用handleLog方法来进行日志的记录。handleLog方法就是将各种数据整理完毕之后放入表中。

获取请求参数

private void setRequestValue(JoinPoint joinPoint, SysOperLog operLog) throws Exception {
    String requestMethod = operLog.getRequestMethod();
    if (HttpMethod.PUT.name().equals(requestMethod) || HttpMethod.POST.name().equals(requestMethod)) {
        String params = argsArrayToString(joinPoint.getArgs());
        operLog.setOperParam(StringUtils.substring(params, 0, 2000));
    } else {
        Map<?, ?> paramsMap = (Map<?, ?>) ServletUtils.getRequest().getAttribute(HandlerMapping.URI_TEMPLATE_VARIABLES_ATTRIBUTE);
        operLog.setOperParam(StringUtils.substring(paramsMap.toString(), 0, 2000));
    }
}

如果是PUT和POST方法,直接用切面的joinPoint.getArgs()获取数据的字符串即可,否则要用ServletUtils.getRequest().getAttribute直接去请求里面拿对应的参数了。

获取参数的时候一定要注意参数中有没有文件对象,有的话不要字符串化,否则会报错的。要过滤掉,这里一定要注意多文件上传这个问题。

@SuppressWarnings("rawtypes")
public boolean isFilterObject(final Object o) {
    Class<?> clazz = o.getClass();
    if (clazz.isArray()) {
        return clazz.getComponentType().isAssignableFrom(MultipartFile.class);
    } else if (Collection.class.isAssignableFrom(clazz)) {
        Collection collection = (Collection) o;
        for (Object value : collection) {
            return value instanceof MultipartFile;
        }
    } else if (Map.class.isAssignableFrom(clazz)) {
        Map map = (Map) o;
        for (Object value : map.entrySet()) {
            Map.Entry entry = (Map.Entry) value;
            return entry.getValue() instanceof MultipartFile;
        }
    }
    return o instanceof MultipartFile || o instanceof HttpServletRequest || o instanceof HttpServletResponse
            || o instanceof BindingResult;
}

首先对象的类型可能是数组,但是里面实际上是MultipartFile文件类型或者可能是Collection或者Map,其中包含着MultipartFile文件类型。