Spring Boot——使用AOP统一处理Web请求日志

1.关于系统日志表结构的设计:

CREATE TABLE `sys_log`  (
  `id` bigint(20) NOT NULL AUTO_INCREMENT,
  `user_id` bigint(20) NULL DEFAULT NULL COMMENT '用户id',
  `username` varchar(50) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '用户名',
  `operation` varchar(50) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '用户操作',
  `time` int(11) NULL DEFAULT NULL COMMENT '响应时间',
  `method` varchar(200) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '请求方法',
  `params` varchar(5000) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '请求参数',
  `ip` varchar(64) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT 'IP地址',
  `gmt_create` datetime(0) NULL DEFAULT NULL COMMENT '创建时间',
  PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 798 CHARACTER SET = utf8 COLLATE = utf8_general_ci COMMENT = '系统日志' ROW_FORMAT = Compact;

SET FOREIGN_KEY_CHECKS = 1;

2.POM的引入:

        
            org.springframework.boot
            spring-boot-starter-aop
        

        
            com.alibaba
            fastjson
            1.2.31
        

3.基于注解化的AOP自定义Web请求日志处理:

@interface 注解详解
Spring Aop的作用

1.@Log()注解声明Class:
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/*Target注解决定Log注解可以加在哪些成分上,
如加在类身上,或者属性身上,或者方法身上等成分*/
@Target(ElementType.METHOD)
/*Retention注解决定MyAnnotation注解的生命周期*/
@Retention(RetentionPolicy.RUNTIME)

/*
这里是在注解类MyAnnotation上使用另一个注解类,这里的Retention称为元注解。
Retention注解括号中的"RetentionPolicy.RUNTIME"
意思是让MyAnnotation这个注解的生命周期一直程序运行时都存在
*/
public @interface Log {
    String value() default "";
}
2.使用Log注解的请求日志处理Class(将拦截到的数据存入系统日志表中)
import com.alibaba.fastjson.JSONObject;
import com.itcast.commom.annotation.Log;
import com.itcast.commom.utils.HttpContextUtils;
import com.itcast.commom.utils.IPUtils;
import com.itcast.demo.entity.SysLog;
import com.itcast.demo.service.SysLogService;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
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.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import javax.servlet.http.HttpServletRequest;
import java.lang.reflect.Method;
import java.util.Date;

//使用aop来完成基于自定义注解的请求日志处理将其存入数据库
@Aspect
@Component
public class LogAspect {
    private static final Logger logger = LoggerFactory.getLogger(LogAspect.class);

    @Autowired
    SysLogService logService;

    /**
     * 功能描述:定义一个切入点
     *
     * @date: 2019/7/2 9:42
     */
    @Pointcut("@annotation(com.itcast.commom.annotation.Log)")
    public void logPointCut() {
    }

    //环绕通知
    @Around("logPointCut()")
    public Object around(ProceedingJoinPoint point) throws Throwable {
        long beginTime = System.currentTimeMillis();
        // 执行方法
        Object result = point.proceed();
        // 执行时长(毫秒)
        long time = System.currentTimeMillis() - beginTime;
        //TODO 异步保存日志
        saveLog(point, time);
        return result;
    }

    /*
     *保存到数据库
     */      
    void saveLog(ProceedingJoinPoint joinPoint, long time) throws InterruptedException {
        MethodSignature signature = (MethodSignature) joinPoint.getSignature();
        Method method = signature.getMethod();
        SysLog sysLog = new SysLog();
        Log syslog = method.getAnnotation(Log.class);
        if (syslog != null) {
            // 注解上的描述
            sysLog.setOperation(syslog.value());
        }
        // 请求的方法名
        String className = joinPoint.getTarget().getClass().getName();
        String methodName = signature.getName();
        sysLog.setMethod(className + "." + methodName + "()");
        // 请求的参数
        Object[] args = joinPoint.getArgs();
        try {
            //转为JOSN串
            String params = JSONObject.toJSONString(args);
           //Arrays.toString(args)
            sysLog.setParams(params);
        } catch (Exception e) {

        }
        // 获取request
        HttpServletRequest request = HttpContextUtils.getHttpServletRequest();
        // 设置IP地址
        sysLog.setIp(IPUtils.getIpAddr(request));
        //用户id
        sysLog.setUserId(new Long(123));
        //用户名
        sysLog.setUsername("用户名");
        sysLog.setTime((int) time);
        // 系统当前时间
        Date date = new Date();
        sysLog.setGmtCreate(date);
        logService.save(sysLog);
    }
}
3.HttpContextUtils工具类:
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import javax.servlet.http.HttpServletRequest;

public class HttpContextUtils {
    public static HttpServletRequest getHttpServletRequest() {
        return ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
    }
}

4.基于AOP的全局请求日志处理

参考文章

1.AOP请求处理Class:
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.aspectj.lang.annotation.Aspect;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

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

//使用aop来完成全局请求日志处理
@Aspect
@Component
public class WebLogAspect {

    private static final Logger logger = LoggerFactory.getLogger(WebLogAspect.class);

    //切点
    //两个..代表所有子目录,最后括号里的两个..代表所有参数
    @Pointcut("execution( * com.itcast..controller.*.*(..))")
    public void logPointCut() {
    }

    //前置通知
    @Before("logPointCut()")
    public void doBefore(JoinPoint joinPoint) throws Throwable {
        // 接收到请求,记录请求内容
        ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
        HttpServletRequest request = attributes.getRequest();

        // 记录下请求内容
        logger.info("请求地址 : " + request.getRequestURL().toString());
        logger.info("HTTP METHOD : " + request.getMethod());
        // 获取真实的ip地址
        //logger.info("IP : " + IPAddressUtil.getClientIpAddress(request));
        logger.info("CLASS_METHOD : " + joinPoint.getSignature().getDeclaringTypeName() + "."
                + joinPoint.getSignature().getName());
        logger.info("参数 : " + Arrays.toString(joinPoint.getArgs()));

    }

    //后置运行通知
    @AfterReturning(returning = "ret", pointcut = "logPointCut()")// returning的值和doAfterReturning的参数名一致
    public void doAfterReturning(Object ret) throws Throwable {
        // 处理完请求,返回内容(返回值太复杂时,打印的是物理存储空间的地址)
        logger.debug("返回值 : " + ret);
    }

    //环绕最终通知,final增强,不管是抛出异常或者正常退出都会执行
    @Around("logPointCut()")
    public Object doAround(ProceedingJoinPoint pjp) throws Throwable {
        long startTime = System.currentTimeMillis();
        Object ob = pjp.proceed();// ob 为方法的返回值
        logger.info("耗时 : " + (System.currentTimeMillis() - startTime));
        return ob;
    }
}

5.logback-spring.xml



    logback
    
    
        
            %d{HH:mm:ss} [%thread] %-5level %logger{36} - %msg%n
        
    

    
    
        true
        
        
            
                applog/%d{yyyy-MM-dd}/%d{yyyy-MM-dd}.log
            
        
        
            
                %d{yyyy-MM-dd HH:mm:ss} -%msg%n
            
        
    

    
        
        
    

    
        
        
    



6.使用效果:

  • 控制台打印结果
  • 控制层代码
Spring Boot——使用AOP统一处理Web请求日志_第1张图片
  • 数据库存放结果
Spring Boot——使用AOP统一处理Web请求日志_第2张图片

你可能感兴趣的:(Spring Boot——使用AOP统一处理Web请求日志)