thumbnail
SpringBoot自定义日志注解实现方法日志记录和参数获取

前言

我们在日常业务操作中需要记录很多日志,可以在我们需要的方法中对日志进行保存操作,但是对业务代码入侵性大。使用切面针对控制类进行处理灵活度不高,因此我们可以使用自定义注解来针对方法进行日志记录


一、定义注解

1.注解

定义一个 @LogMethod 注解,作用于方法上

  • title:记录标题

  • operatorType:操作类别,这里使用了枚举类

  • isSaveRequestData:默认为true,是否获取请求参数

/**
 * 自定义方法日志记录注解
 */
@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface LogMethod {

    /**
     * 记录标题
     */
    public String title() default "";

    /**
     * 操作类别
     */
    public UserStatusCode operatorType() default UserStatusCode.OTHER;

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

2.枚举类

接口定义了两个方法,一个获取 code 编码,一个获取 desc 描述

public interface ResultStandard {

    int getCode();

    String getDesc();

}

自定义用户操作枚举类,可向数据库存储 描述信息 或者 编码值

public enum UserStatusCode implements ResultStandard {

    OTHER("其他操作", 10),
    USER_QUERY("用户查询", 11),
    USER_EXPORT("用户导出", 12),
    WEA_QUERY("天气查询", 13)
    ;

    private String desc;

    private int code;

    private UserStatusCode(String desc, int code) {
        this.desc = desc;
        this.code = code;
    }

    public String getDesc() {
        return desc;
    }

    public int getCode() {
        return code;
    }
}

二、AOP处理

1.配置织入点

这里使用了后置通知 AfterReturning,在处理完方法后获取到结果数据然后进行处理

/**
 * 方法操作日志记录处理
 */
@Aspect
@Component
public class LogMethodAspect {

    private static final Logger log = LoggerFactory.getLogger(LogMethodAspect.class);

    /**
     * 配置织入点
     */
    @Pointcut("@annotation(com.angel.annotation.LogMethod)")
    public void logMethodPointCut() { }

    /**
     * 处理完请求后执行
     */
    @AfterReturning(pointcut = "logMethodPointCut()", returning = "jsonResult")
    public void doAfterReturning(JoinPoint joinPoint, Object jsonResult) {
        handleLog(joinPoint, null, jsonResult);
    }

    /**
     * 拦截异常操作
     */
    @AfterThrowing(value = "logMethodPointCut()", throwing = "e")
    public void doAfterThrowing(JoinPoint joinPoint, Exception e) {
        handleLog(joinPoint, e, null);
    }

	// 代码在下面...
}

2.处理注解方法

  • 根据 JoinPoint 目标对象,利用反射获取方法名称,方法路径
  • 使用 getAnnotationLog() 方法获取 LogMethod 注解
  • 使用 IP工具类和 Security 可以获取到用户数据和访问的IP
protected void handleLog(final JoinPoint joinPoint, final Exception e, Object jsonResult) {
    try {
        // 获得注解
        LogMethod controllerLog = getAnnotationLog(joinPoint);
        if (controllerLog == null) {
            return;
        }

        Map<String, Object> logMap = new HashMap<String, Object>();

        // 获取当前的用户
        // ...

        // 获取请求IP,网上IP工具类多的ya批
        // ...

        // 获取返回参数
        JSONObject j = JSON.parseObject(JSON.toJSONString(jsonResult));
        String msg = j.getString("message");
        String code = j.getString("status");
        String methodResult = "{\"msg\":\""+msg+"\",\"code\":"+code+"}";
        logMap.put("requestResult", methodResult);

        // 获取请求URL
        logMap.put("requestUrl", ServletUtils.getRequest().getRequestURI());

        // 获取异常信息
        if (e != null) {
            logMap.put("requestError", StringUtils.substring(e.getMessage(), 0, 2000));
        }

        // 获取方法名称
        String className = joinPoint.getTarget().getClass().getName();
        String methodName = joinPoint.getSignature().getName();
        String methodStr = className + "." + methodName + "()";
        logMap.put("method", methodStr);

        // 获取请求方式
        logMap.put("requestMethod",ServletUtils.getRequest().getMethod());

        // 处理设置注解上的参数
        getControllerMethodDescription(joinPoint, controllerLog, logMap);

        // 保存至数据库操作
        System.out.println(logMap);

    } catch (Exception exp) {
        log.error("异常信息:{}", exp.getMessage());
        exp.printStackTrace();
    }
}

/**
 *获取存在的注解
 */
private LogMethod getAnnotationLog(JoinPoint joinPoint) throws Exception {
    Signature signature = joinPoint.getSignature();
    MethodSignature methodSignature = (MethodSignature) signature;
    Method method = methodSignature.getMethod();
    if (method != null) {
        return method.getAnnotation(LogMethod.class);
    }
    return null;
}

3.获取参数

  • 由上一步获取到的注解类,获取到注解的参数以及是否保存请求参数
  • 可以根据 HandlerMapping.URI_TEMPLATE_VARIABLES_ATTRIBUTE 来得到 REST 风格的参数
/**
 * 获取注解中对方法的描述信息 用于Controller层注解
 */
public void getControllerMethodDescription(JoinPoint joinPoint, LogMethod log, Map<String, Object> logMap) throws Exception {
    // 操作类型
    logMap.put("operatorType", log.operatorType().getDesc());
    // 标题
    logMap.put("title", log.title());
    // 是否需要保存request,参数和值
    if (log.isSaveRequestData()) {
        // 获取参数的信息
        if ("PUT".equals(logMap.get("requestMethod")) || "POST".equals(logMap.get("requestMethod"))) {
            String params = argsArrayToString(joinPoint.getArgs());
            logMap.put("requestParams", StringUtils.substring(params, 0, 2000));
        } else {
            Map<?, ?> paramsMap = (Map<?, ?>) ServletUtils.getRequest().getAttribute(HandlerMapping.URI_TEMPLATE_VARIABLES_ATTRIBUTE);
            logMap.put("requestParams", StringUtils.substring(paramsMap.toString(), 0, 2000));
        }
    }
}

/**
 * 参数拼装
 */
private String argsArrayToString(Object[] paramsArray) {
    String params = "";
    if (paramsArray != null && paramsArray.length > 0) {
        for (int i = 0; i < paramsArray.length; i++) {
            if (!isFilterObject(paramsArray[i])) {
                Object jsonObj = JSON.toJSON(paramsArray[i]);
                params += jsonObj.toString() + " ";
            }
        }
    }
    return params.trim();
}

4.完整切面处理代码

完整代码如下,需要用到 ServletUtils 工具类

package com.angel.aspect;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.angel.annotation.LogMethod;
import com.angel.util.ServletUtils;
import org.apache.commons.lang3.StringUtils;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.Signature;
import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.AfterThrowing;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;
import org.springframework.web.multipart.MultipartFile;
import org.springframework.web.servlet.HandlerMapping;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.lang.reflect.Method;
import java.util.Collection;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;

/**
 * 方法操作日志记录处理
 */
@Aspect
@Component
public class LogMethodAspect {

    private static final Logger log = LoggerFactory.getLogger(LogMethodAspect.class);

    /**
     * 配置织入点
     */
    @Pointcut("@annotation(com.angel.annotation.LogMethod)")
    public void logMethodPointCut() { }

    /**
     * 处理完请求后执行
     */
    @AfterReturning(pointcut = "logMethodPointCut()", returning = "jsonResult")
    public void doAfterReturning(JoinPoint joinPoint, Object jsonResult) {
        handleLog(joinPoint, null, jsonResult);
    }

    /**
     * 拦截异常操作
     */
    @AfterThrowing(value = "logMethodPointCut()", throwing = "e")
    public void doAfterThrowing(JoinPoint joinPoint, Exception e) {
        handleLog(joinPoint, e, null);
    }

    protected void handleLog(final JoinPoint joinPoint, final Exception e, Object jsonResult) {
        try {
            // 获得注解
            LogMethod controllerLog = getAnnotationLog(joinPoint);
            if (controllerLog == null) {
                return;
            }

            Map<String, Object> logMap = new HashMap<String, Object>();

            // 获取当前的用户
            // ...

            // 获取请求IP,网上IP工具类多的ya批
            // ...

            // 获取返回参数
            JSONObject j = JSON.parseObject(JSON.toJSONString(jsonResult));
            String msg = j.getString("message");
            String code = j.getString("status");
            String methodResult = "{\"msg\":\""+msg+"\",\"code\":"+code+"}";
            logMap.put("requestResult", methodResult);

            // 获取请求URL
            logMap.put("requestUrl", ServletUtils.getRequest().getRequestURI());

            // 获取异常信息
            if (e != null) {
                logMap.put("requestError", StringUtils.substring(e.getMessage(), 0, 2000));
            }

            // 获取方法名称
            String className = joinPoint.getTarget().getClass().getName();
            String methodName = joinPoint.getSignature().getName();
            String methodStr = className + "." + methodName + "()";
            logMap.put("method", methodStr);

            // 获取请求方式
            logMap.put("requestMethod",ServletUtils.getRequest().getMethod());

            // 处理设置注解上的参数
            getControllerMethodDescription(joinPoint, controllerLog, logMap);

            // 保存至数据库操作
            System.out.println(logMap);

        } catch (Exception exp) {
            log.error("异常信息:{}", exp.getMessage());
            exp.printStackTrace();
        }
    }

    /**
     * 获取注解中对方法的描述信息 用于Controller层注解
     */
    public void getControllerMethodDescription(JoinPoint joinPoint, LogMethod log, Map<String, Object> logMap) throws Exception {
        // 操作类型
        logMap.put("operatorType", log.operatorType().getDesc());
        // 标题
        logMap.put("title", log.title());
        // 是否需要保存request,参数和值
        if (log.isSaveRequestData()) {
            // 获取参数的信息
            if ("PUT".equals(logMap.get("requestMethod")) || "POST".equals(logMap.get("requestMethod"))) {
                String params = argsArrayToString(joinPoint.getArgs());
                logMap.put("requestParams", StringUtils.substring(params, 0, 2000));
            } else {
                Map<?, ?> paramsMap = (Map<?, ?>) ServletUtils.getRequest().getAttribute(HandlerMapping.URI_TEMPLATE_VARIABLES_ATTRIBUTE);
                logMap.put("requestParams", StringUtils.substring(paramsMap.toString(), 0, 2000));
            }
        }
    }

    /**
     * 获取存在的注解
     */
    private LogMethod getAnnotationLog(JoinPoint joinPoint) throws Exception {
        Signature signature = joinPoint.getSignature();
        MethodSignature methodSignature = (MethodSignature) signature;
        Method method = methodSignature.getMethod();
        if (method != null) {
            return method.getAnnotation(LogMethod.class);
        }
        return null;
    }

    /**
     * 参数拼装
     */
    private String argsArrayToString(Object[] paramsArray) {
        String params = "";
        if (paramsArray != null && paramsArray.length > 0) {
            for (int i = 0; i < paramsArray.length; i++) {
                if (!isFilterObject(paramsArray[i])) {
                    Object jsonObj = JSON.toJSON(paramsArray[i]);
                    params += jsonObj.toString() + " ";
                }
            }
        }
        return params.trim();
    }

    /**
     * 判断是否需要过滤的对象。
     */
    @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 (Iterator iter = collection.iterator(); iter.hasNext(); ) {
                return iter.next() instanceof MultipartFile;
            }
        } else if (Map.class.isAssignableFrom(clazz)) {
            Map map = (Map) o;
            for (Iterator iter = map.entrySet().iterator(); iter.hasNext(); ) {
                Map.Entry entry = (Map.Entry) iter.next();
                return entry.getValue() instanceof MultipartFile;
            }
        }
        return o instanceof MultipartFile || o instanceof HttpServletRequest || o instanceof HttpServletResponse;
    }
}

三、工具类

需要用到的Servlet工具 ServletUtils 类如下,也可以在任意请求处理方法内调用该类,例如

  • getRequest:获取请求对象

  • renderString:渲染给客户端数据

  • getParameter:获取参数

package com.angel.util;

import org.springframework.web.context.request.RequestAttributes;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import java.io.IOException;

/**
 * 客户端工具类
 */
public class ServletUtils {
    /**
     * 获取String参数
     */
    public static String getParameter(String name) {
        return getRequest().getParameter(name);
    }

    /**
     * 获取String参数
     */
    public static String getParameter(String name, String defaultValue) {
        Object value = getRequest().getParameter(name);
        return value == null ? defaultValue : (String) value;
    }

    /**
     * 获取Integer参数
     */
    public static Integer getParameterToInt(String name) {
        return getParameterToInt(name, null);
    }

    /**
     * 获取Integer参数
     */
    public static Integer getParameterToInt(String name, Integer defaultValue) {
        Object value = getRequest().getParameter(name);
        if (value == null) {
            return defaultValue;
        }
        try {
            return Integer.parseInt(value.toString());
        } catch (Exception e) {
            return defaultValue;
        }

    }

    /**
     * 获取request
     */
    public static HttpServletRequest getRequest() {
        return getRequestAttributes().getRequest();
    }

    /**
     * 获取response
     */
    public static HttpServletResponse getResponse() {
        return getRequestAttributes().getResponse();
    }

    /**
     * 获取session
     */
    public static HttpSession getSession() {
        return getRequest().getSession();
    }

    public static ServletRequestAttributes getRequestAttributes() {
        RequestAttributes attributes = RequestContextHolder.getRequestAttributes();
        return (ServletRequestAttributes) attributes;
    }

    /**
     * 将字符串渲染到客户端
     */
    public static String renderString(HttpServletResponse response, String string) {
        try {
            response.setStatus(200);
            response.setContentType("application/json");
            response.setCharacterEncoding("utf-8");
            response.getWriter().print(string);
        } catch (IOException e) {
            e.printStackTrace();
        }
        return null;
    }

    /**
     * 是否是Ajax异步请求
     */
    public static boolean isAjaxRequest(HttpServletRequest request) {
        String accept = request.getHeader("accept");
        if (accept != null && accept.indexOf("application/json") != -1) {
            return true;
        }

        String xRequestedWith = request.getHeader("X-Requested-With");
        if (xRequestedWith != null && xRequestedWith.indexOf("XMLHttpRequest") != -1) {
            return true;
        }

        String uri = request.getRequestURI();
        if (inStringIgnoreCase(uri, ".json", ".xml")) {
            return true;
        }

        String ajax = request.getParameter("__ajax");
        if (inStringIgnoreCase(ajax, "json", "xml")) {
            return true;
        }
        return false;
    }

    /**
     * 需要用到的转换方法,是否包含字符串
     */
    public static boolean inStringIgnoreCase(String str, String... strs) {
        if (str != null && strs != null) {
            for (String s : strs) {
                if (str.equalsIgnoreCase(s == null ? "" : s.trim())) {
                    return true;
                }
            }
        }
        return false;
    }
}

总结

来发送个GET请求吧,使用@PathVariable获取参数,参数名为dayCount

@GetMapping("/dayinfo/{dayCount}")
@LogMethod(title = "获取未来天气预报", operatorType = UserStatusCode.WEA_QUERY)
@ApiOperation(value = "获取未来天气预报", notes = "获取未来天气预报", response = Result.class)
// 方法省略...

在这里插入图片描述
来看下 logMap 的内容

在这里插入图片描述
最后把Map对象换成大家自己的操作日志实体类,然后就可以对其进行对心所欲的 插入 操作了,咳咳…
当然要插入到数据库,有瘾的话Redis也可以。实体类在手,日志你有

上一篇
下一篇