Springboot AOP 自定义注解实现记录日志

Springboot 同时被 2 个专栏收录
4 篇文章 0 订阅
1 篇文章 0 订阅

1.拦截器识别请求头token,token在登录时已经存入账号信息
2.利用token访问方法时,可以利用token获取访问者的身份信息等
3.在需要记录日志的方法上标记@Log 使此方法记录并入库

上代码
maven:

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-aop</artifactId>
        </dependency>
        <!-- commons-lang3 -->
        <dependency>
            <groupId>org.apache.commons</groupId>
            <artifactId>commons-lang3</artifactId>
            <version>3.12.0</version>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

要用到的两个实体类:

/**
 * 模拟用户信息
 */
@Data
public class UserInfo {

    String username;
    Integer age;
}

//==========================

/**
 * 模拟存储的日志 实体类
 */
@Data
public class SysLog extends BaseEntity{
	
	//操作人id
	private String operatorId;
	//请求类型,0=ajax,1=普通请求
	private int type; 
	//请求动作
	private String action;
	//请求主机
	private String host;
	//请求路径
	private String uri;
	//请求方式
	private String httpMethod;
	//请求类的方法
	private String classMethod;
	//请求参数
	private String params;
}

1.首先自定义注解

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Target(ElementType.METHOD)  //表示注解只能标注在方法上
@Retention(RetentionPolicy.RUNTIME) //运行时注解
public @interface Log {

    String value() default "";   //默认值为空串,否则必须传入一个值

}

2.自定义切面,凡方法上有@Log的都进入此切面

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.kdmins.annotation.Log;
import com.kdmins.pojo.SysLog;
import com.kdmins.pojo.UserInfo;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.Signature;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.LocalVariableTableParameterNameDiscoverer;
import org.springframework.stereotype.Component;
import org.springframework.web.multipart.MultipartFile;
import javax.servlet.http.HttpServletRequest;
import java.io.Serializable;
import java.lang.reflect.Method;
import java.util.*;


@Aspect
@Component
@Slf4j
public class LogAspect {

    @Autowired
    private HttpServletRequest request;

    @Autowired
    private ObjectMapper objectMapper;


    /**
     * 自定义切点表达式,检测到@Log 注解的进入aop切面
     */
    @Pointcut("@annotation(com.kdmins.annotation.Log)")
    public void pointcut() {
    }


    /**
     * desc: 后置通知,   @Before在Controller层操作前拦截(自己选择)
     *
     * @param joinPoint 切入点
     */
    @After("pointcut()")
    public void before(JoinPoint joinPoint) {
        log.info("=========注解已生效==========");
        try {
            handleLog(joinPoint);
        } catch (Exception e) {
            log.error("[LogAspect] doBefore error = {}", e);
        }
    }

    /**
     * desc: 通过反射获取日志信息,存入数据库
     *
     * @param joinPoint 切点
     * @throws Exception
     */
    private void handleLog(final JoinPoint joinPoint) throws Exception {
        Signature signature = joinPoint.getSignature();
        MethodSignature methodSignature = (MethodSignature) signature;
        Method method = methodSignature.getMethod();
        //获取注解
        Log logInfo = method.getAnnotation(Log.class);
        if (logInfo == null) {
            return;
        }

        String method1 = request.getMethod();
        System.out.println("请求类型为" + method1);
        SysLog sysLog = setSysLogInfo(joinPoint, method, logInfo);
        System.out.println(sysLog);

        //todo 日志入库
    }


    /**
     * desc: 初始化日志信息,存入数据库
     *
     * @return SysLog
     */
    private SysLog setSysLogInfo(JoinPoint joinPoint, Method method, Log logInfo ) {
        //拦截器已经存入,直接request获取
        UserInfo  userInfo = (UserInfo) request.getAttribute("userInfo");
        // 操作数据库日志表
        SysLog sysLog = new SysLog();
        //获取当前登陆人,获取操作人id,此处不写获取当前登录人的逻辑,直接写死operatorId=1
        sysLog.setOperatorId(userInfo.getUsername());
        // 请求信息
        // sysLog.setType(AjaxUtils.isAjax(request) ? 0 : 1);
        sysLog.setAction(logInfo.value());
        sysLog.setHost(request.getRemoteHost());
        sysLog.setUri(request.getRequestURI().toString());
        sysLog.setHttpMethod(request.getMethod());
        String classMethod = joinPoint.getSignature().getDeclaringTypeName() + "." + joinPoint.getSignature().getName();
        sysLog.setClassMethod(classMethod);
        // 请求的方法参数值
        Object[] args = joinPoint.getArgs();
        // 请求的方法参数名称
        LocalVariableTableParameterNameDiscoverer discoverer = new LocalVariableTableParameterNameDiscoverer();
        String[] paramNames = discoverer.getParameterNames(method);
        if (args != null && paramNames != null) {
            StringBuilder params = new StringBuilder();
            try {
                params = handleParams(params, args, Arrays.asList(paramNames));
            } catch (JsonProcessingException e) {
                log.error("[LogAspect] setSysLogInfo handleParams error = {}", e);
            }
            sysLog.setParams(params.toString());
        }
        return sysLog;
    }


    /**
     * desc: 处理请求参数
     *
     * @param params
     * @param args
     * @param paramNames
     * @return
     * @throws JsonProcessingException
     */
    private StringBuilder handleParams(StringBuilder params, Object[] args, List paramNames) throws JsonProcessingException {
        for (int i = 0; i < args.length; i++) {
            if (args[i] instanceof Map) {
                Set set = ((Map) args[i]).keySet();
                List list = new ArrayList();
                List paramList = new ArrayList<>();
                for (Object key : set) {
                    list.add(((Map) args[i]).get(key));
                    paramList.add(key);
                }
                return handleParams(params, list.toArray(), paramList);
            } else {
                if (args[i] instanceof Serializable) {
                    Class<?> aClass = args[i].getClass();
                    try {
                        aClass.getDeclaredMethod("toString", new Class[]{null});
                        // 如果不抛出NoSuchMethodException 异常则存在 toString 方法 ,安全的writeValueAsString ,否则 走 Object的 toString方法
                        params.append("  ").append(paramNames.get(i)).append(": ").append(objectMapper.writeValueAsString(args[i]));
                    } catch (NoSuchMethodException e) {
                        params.append("  ").append(paramNames.get(i)).append(": ").append(objectMapper.writeValueAsString(args[i].toString()));
                    }
                } else if (args[i] instanceof MultipartFile) {
                    MultipartFile file = (MultipartFile) args[i];
                    params.append("  ").append(paramNames.get(i)).append(": ").append(file.getName());
                } else {
                    params.append("  ").append(paramNames.get(i)).append(": ").append(args[i]);
                }


            }
        }
        return params;
    }
}

3.实现拦截器

import com.kdmins.pojo.UserInfo;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.servlet.handler.HandlerInterceptorAdapter;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

/**
 * @version 1.0
 * @author: lsy
 * @create: 2021-05-31 15:04
 **/
@Component
@Slf4j
public class TokenInterceptor extends HandlerInterceptorAdapter {

    /**
     * 预处理回调方法,实现处理器的预处理(如检查登陆),第三个参数为响应的处理器,自定义Controller
     * 返回值:true表示继续流程(如调用下一个拦截器或处理器);false表示流程中断(如登录检查失败),不会继续调用其他的拦截器或处理器,此时我们需要通过response来产生响应;
     */
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        log.info("进入预拦截方法");
        String token = request.getHeader("token");
        log.info("获取到的 token 值 :{}", token);

        //todo  token中存放的账号信息等 (模拟从token中获取)
        UserInfo userInfo = new UserInfo();
        userInfo.setUsername("admin");
        userInfo.setAge(11);
        // 设置 request attribute 在Controller可以直接 用 @RequestAttribute
        request.setAttribute("userInfo", userInfo);
        //request.setAttribute("auditLog", null);

        //token 校验成功后 可继续校验权限相关,数据库或redis都可以,此类交给spring了,可直接注入
        if ("token".equals(token)) {
            return true;
        }

        //todo 此处可以抛出异常,然后用全局异常包裹后返回响应,也可以直接返回 (建议)
        response.setCharacterEncoding("utf-8");
        response.getWriter().write("token校验失败");
        return false;
    }

    /**
     * 后处理回调方法,实现处理器的后处理(但在渲染视图之前),此时我们可以通过modelAndView(模型和视图对象)对模型数据进行处理或对视图进行处理,modelAndView也可能为null。
     *
     * @param request
     * @param response
     * @param handler
     * @param modelAndView
     * @throws Exception
     */
    @Override
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {

    }

    /**
     * 整个请求处理完毕回调方法,即在视图渲染完毕时回调,如性能监控中我们可以在此记录结束时间并输出消耗时间,还可以进行一些资源清理,类似于try-catch-finally中的finally,但仅调用处理器执行链中
     *
     * @param request
     * @param response
     * @param handler
     * @param ex
     * @throws Exception
     */
    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
        //此方法必须是preHandle方法放行后才会执行
        log.info("方法处理完毕,开始清理request");

        // 移除request attribute
        request.removeAttribute("currentAccount");
        request.removeAttribute("auditLog");
        request.removeAttribute("responseBody");
        //清理资源
        super.afterCompletion(request, response, handler, ex);

    }
}

4.过滤器,并开启拦截器

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

/**
 * 过滤器并开启token拦截器
 *
 * @version 1.0
 * @author: lsy
 * @create: 2021-05-31 15:25
 **/
@Configuration
public class InterceptorConfiguration implements WebMvcConfigurer {

    @Autowired
    private TokenInterceptor tokenInterceptor;

    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        InterceptorRegistration registration = registry.addInterceptor(tokenInterceptor);
        registration.addPathPatterns("/**");
        registration.excludePathPatterns(
                "/login",  //登录方法
                "/captcha",//验证码方法
                "/logout"  //登出方法
        );
    }
}

开始测试: 我用的是Postman
参数和请求头token
在这里插入图片描述
在这里插入图片描述
注解生效,日志已入库
在这里插入图片描述

  • 2
    点赞
  • 3
    评论
  • 2
    收藏
  • 一键三连
    一键三连
  • 扫一扫,分享海报

相关推荐
©️2020 CSDN 皮肤主题: 大白 设计师:CSDN官方博客 返回首页
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、C币套餐、付费专栏及课程。

余额充值