Spring AOP实现全局日志记录

Spring AOP实现全局日志记录

一 前言:
在java开发中日志的管理有很多种, 在这里推荐Spring 的Aop思想将每一次请求记录 ,并且保存到数据库表中。 通常情况下,我们将项目部署到服务器后,如果接口出现了bug,那么我们就可以通过查看数据库表中记录的信息来判断这次请求是哪个用户操作的 在公司内部办公软件可以很好的使用,但是对外的项目建议使用日志几率到文件中更加符合业务场景。

二、代码详情:
ps: 涉及公司隐私信息 添加数据库代码(被修改)就不对外暴露啦 谢谢大家

import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import javax.servlet.http.HttpServletRequest;
import java.util.Arrays;

/**
 * @author zhu
 * @date 2021/11/20:30
 * 统一日志处理
 * TODO 使用Aop记录每一个请求
 */
@Aspect
@Component
public class HttpAspect {
    
    
    @Autowired
    private LogInfoMapper logInfoMapper;
    private static final Logger LOGGER = LoggerFactory.getLogger(HttpAspect.class);

    /**
     * @Pointcut 声明切入点表达式。
     * 注意此处扫描的是你的Controller层接口位置
     */
    @Pointcut("execution(public * com.zhu.demo.controller.*.*(..))")
    public void log() {
    
    
    }

    /**
     * @Before:前置通知,在方法执行前执行
     */
    @Before("log()")
    public void doBefore(JoinPoint joinPoint) {
    
    
        ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
        HttpServletRequest request = attributes.getRequest();
        //此处获取用户为null
        LOGGER.info("user:{}", request.getRemoteUser());
        //url
        LOGGER.info("url:{}", request.getRequestURL());
        //接口路径
        LOGGER.info("requestURI:{}", request.getRequestURI());
        //method
        LOGGER.info("method:{}", request.getMethod());
        //ip
        LOGGER.info("ip:{}", request.getRemoteAddr());
        //类方法
        LOGGER.info("class_method:{}", joinPoint.getSignature().getDeclaringTypeName() + "." + joinPoint.getSignature().getName());
        //参数(joinPoint.getArgs()返回一个参数数组)
        LOGGER.info("args:{}", Arrays.asList(joinPoint.getArgs()));
   
 		 //获取用户真实Ip  
        String ip = CusAccessObjectUtil.getIpAddress(request);


        /**
         * 封装日志信息对象
         */
        Log log = new Log();
        //日志内容
      	//调用方法
        this.addLog(log);


    }

    /**
     * @AfterReturning:返回通知,在方法返回结果之后执行。
     */
    @AfterReturning(returning = "object", pointcut = "log()")
    public void doAfterReturning(Object object) {
    
    
	   	 //	这里做非空判断  因为出现Bug情况下 返回参数为NUll
        if (object != null) {
    
    
            LOGGER.info("response:{}", object.toString());
        }
    }

    /**
     * 记录日志信息
     *
     * @param log 日志model
     */
    @Transactional(rollbackFor = Exception.class)
    public void addLog(Log log) {
    
    
    	//这里我使用的是Mybatis——Plus
        logInfoMapper.insert(log);
    }


}

获取用户真实Ip地址工具类:

import javax.servlet.http.HttpServletRequest;

/**
  @author zhu
 * @date 2021/11/20:30
 */
public class CusAccessObjectUtil {
    
    
    /**
     * 获取用户真实IP地址,不使用request.getRemoteAddr();的原因是有可能用户使用了代理软件方式避免真实IP地址,
     * <p>
     * 可是,如果通过了多级反向代理的话,X-Forwarded-For的值并不止一个,而是一串IP值,究竟哪个才是真正的用户端的真实IP呢?
     * 答案是取X-Forwarded-For中第一个非unknown的有效IP字符串。
     * <p>
     * 如:X-Forwarded-For:192.168.1.110, 192.168.1.120, 192.168.1.130,
     * 192.168.1.100
     * <p>
     * 用户真实IP为: 192.168.1.110
     *
     * @param request 请求
     * @return ip
     */
    public static String getIpAddress(HttpServletRequest request) {
    
    
        String ip = request.getHeader("x-forwarded-for");
        if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
    
    
            ip = request.getHeader("Proxy-Client-IP");
        }
        if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
    
    
            ip = request.getHeader("WL-Proxy-Client-IP");
        }
        if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
    
    
            ip = request.getHeader("HTTP_CLIENT_IP");
        }
        if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
    
    
            ip = request.getHeader("HTTP_X_FORWARDED_FOR");
        }
        if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
    
    
            ip = request.getRemoteAddr();
        }
        return ip;
    }

}

接下来看看接口调用以及效果:

Postman:

在这里插入图片描述

控制台输出:
在这里插入图片描述

Controller层接口:
在这里插入图片描述

Mysql数据库:
在这里插入图片描述
以下是我在网上总结补充过来的资料大家可以了解一下哈:

一、AOP术语
通知(Advice)
  切面的工作被称为通知。通知定义了切面是什么以及何时使用。除了描述切面要完成的工作,通知还解决了何时执行这个工作的问题。
5种通知类型:
前置通知(Before):在目标方法被调用之前调用通知功能
后置通知(After):在目标方法完成之后调用通知,此时不会关心方法的输出是什么
返回通知(After-returning):在目标方法成功执行之后调用通知
异常通知(After-throwing):在目标方法抛出异常后调用通知
环绕通知(Around):通知包裹了被通知的方法,在被通知的方
法调用之前和之后执行自定义的行为

后置通知和返回通知的区别是,后置通知是不管方法是否有异常,都会执行该通知;而返回通知是方法正常结束时才会执行。

在这里插入图片描述

二、关于request获取请求参数:
获取客户机环境信息常见方法:

  1.getRequestURL方法返回客户端发出请求时的完整URL。

  2.getRequestURI方法返回请求行中的资源名部分。

  3.getQueryString方法返回请求行的参数部分。

  4.getRemoteAddr方法返回发出请求的客户机的IP地址。

  5.getRemoteHost方法返回发出请求的客户机的完整主机名。

  6.getRemotePort方法返回客户机所使用的网络端口号。

  7.getLocalAddr方法返回WEB服务器的IP地址。

  8.getLocalName方法返回WEB服务器的主机名。

  9.getMethod得到客户机请求方式。

总结:

其实也是通过SpringAop注解 拦截信息 此工具类大家学习参考即可 我自己在写的时候为了赶工上线 有很地方考虑不够周到不足之处欢迎大家留言补充。

猜你喜欢

转载自blog.csdn.net/weixin_48134878/article/details/113254184