Spring之Aop切面---日志收集(环绕处理、前置处理方式)--使用/教程/实例

Spring之Aop切面---日志收集(环绕处理、前置处理方式)--使用/教程/实例

    • 简介
      • 系统登录日志类LoginLogEntity .java
    • 一、环绕处理方式
      • 1、自定义注解类LoginLogAop.class
      • 2、切面处理类LogoutLogAspect.java
    • 二、前置处理方式:
      • 1、自定义注解类LogoutLogAop.class
      • 2、切面处理类LogoutLogAspect.java
    • 三、Proceedingjoinpoint简述

简介

本文章介绍采用两种不同方式处理----系统登录、系统退出登录两种场景日志。

  • 环绕处理系统登录日志
  • 前置处理系统退出登录日志

系统登录日志类LoginLogEntity .java

package com.fy.test.entity;

import com.baomidou.mybatisplus.annotation.TableName;
import com.baomidou.mybatisplus.annotation.IdType;

import java.io.Serializable;
import java.time.LocalDateTime;

import com.baomidou.mybatisplus.annotation.TableId;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import lombok.experimental.Accessors;

/**
 * @ClassName: LoginLogEntity 
 * @Description: 
 * @Author fy
 * @Date 2023/07/10 9:00
 */
@Data
@Accessors(chain = true)
@TableName("t_login_log")
public class LoginLogEntity implements Serializable {

    private static final long serialVersionUID = 1L;

    /**
     * 主键
     */
    @TableId(value = "id", type = IdType.ASSIGN_ID)
    private String id;

    /**
     * 操作系统
     */
    private String opOs;

    /**
     * 浏览器类型
     */
    private String opBrowser;

    /**
     * 登录IP地址
     */
    private String opIp;

    /**
     * 登录时间
     */
    private LocalDateTime opDate;

    /**
     * 登录用户ID
     */
    private String userId;

    /**
     * 登录用户名称
     */
    private String userName;

    /**
     * 错误类型
     */
    private String exCode;

    /**
     * 错误信息
     */
    private String exMsg;

    /**
     * 登录状态
     */
    private boolean status;

    /**
     * 描述
     */
    private String desc;
}

一、环绕处理方式

1、自定义注解类LoginLogAop.class

package com.fy.test.log.annotation;

import java.lang.annotation.*;

/**
 * @ClassName: LoginLogAop
 * @Description: 
 * @Author fy
 * @Date 2023/07/10 9:05
 */
@Target({ElementType.PARAMETER, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface LoginLogAop {

    /**
     * 描述
     */
    String desc() default "";

}

2、切面处理类LogoutLogAspect.java

package com.fy.test.log.aspect;

import cn.hutool.extra.servlet.ServletUtil;
import cn.hutool.http.useragent.UserAgent;
import cn.hutool.http.useragent.UserAgentUtil;
import com.fy.test.log.annotation.LoginLogAop;
import com.fy.test.utils.RequestHolder;
import com.fy.test.utils.SecurityUtil;
import com.fy.test.service.dto.LoginLogDto;
import com.fy.test.service.feign.LogServiceFeign;
import com.fy.test.service.vo.UserVo;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.Signature;
import org.aspectj.lang.annotation.AfterThrowing;
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.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.multipart.MultipartFile;

import javax.servlet.http.HttpServletRequest;
import java.lang.reflect.Method;
import java.time.LocalDateTime;
import java.util.HashMap;
import java.util.Map;

/**
 * @ClassName: LoginLogAspect
 * @Description:
 * @Author fy
 * @Date 2023/07/10 9:05
 */
@Slf4j
@Aspect
public class LoginLogAspect {

    @Autowired
    private LogServiceFeign logServiceFeign;

    /**
     * 配置织入点
     */
    @Pointcut("@annotation(com.fy.test.common.log.annotation.LoginLogAop)")
    public void logPointCut() {
    }

    /**
     * 通知方法会将目标方法封装起来
     * 注意:环绕方式选择ProceedingJoinPoint
     * Proceedingjoinpoint 继承了JoinPoint,在JoinPoint的基础上暴露出 proceed(), 这个方法是AOP代理链执行的方法。
     * JoinPoint仅能获取相关参数,无法执行连接点。
     * 暴露出proceed()这个方法,就能支持 aop:around 这种切面(而其他的几种切面只需要用到JoinPoint,这跟切面类型有关),
     * 就能控制走代理链还是走自己拦截的其他逻辑。  
     * 
     * @param joinPoint 切点
     */
    @Around(value = "logPointCut()")
    public Object doAround(ProceedingJoinPoint joinPoint) throws Throwable {
        Object result = joinPoint.proceed();
        LoginLogDto logDto = getLog();
        logDto.setStatus(true);
        handleLog(joinPoint, logDto);
        return result;
    }

    /**
     * 通知方法会在目标方法抛出异常后执行
     *
     * @param joinPoint
     * @param e
     */
    @AfterThrowing(value = "logPointCut()", throwing = "e")
    public void doAfterThrowing(JoinPoint joinPoint, Exception e) {
        LoginLogDto logDto = getLog();
        logDto.setExCode(e.getClass().getSimpleName()).setExMsg(e.getMessage());
        logDto.setStatus(false);
        handleLog(joinPoint, logDto);
    }

    private LoginLogDto getLog() {

        HttpServletRequest request = RequestHolder.getHttpServletRequest();
        UserAgent userAgent = UserAgentUtil.parse(request.getHeader("User-Agent"));

        LoginLogDto loginLog = new LoginLogDto();
        loginLog.setOpIp(ServletUtil.getClientIP(request))
                .setOpOs(userAgent.getOs().getName())
                .setOpBrowser(userAgent.getBrowser().getName())
                .setUserId(SecurityUtil.getUserId())
                .setUserName(SecurityUtil.getUserName())
                .setOpDate(LocalDateTime.now());

        return loginLog;
    }

    protected void handleLog(final JoinPoint joinPoint, LoginLogDto loginLogDto) {
        // 获得注解
        LoginLogAop logAop = getAnnotationLog(joinPoint);
        if (null == logAop) {
            return;
        }

        loginLogDto.setDescription(logAop.description());

        Map<String, Object> requestParams = getRequestParams(joinPoint);
        if (requestParams.containsKey("userVo")) {
            UserVo userVo = JSONObject.parseObject(JSON.toJSONString(requestParams.get("userVo")), UserVo.class);
            if (null != userVo && StringUtils.isBlank(loginLogDto.getUserName())) {
                loginLogDto.setUserName(userVo.getUsername());
            }
        }

        // 保存数据库
        logServiceFeign.saveLoginLog(loginLogDto);
    }

    /**
     * 是否存在注解,如果存在就获取
     */
    private LoginLogAop getAnnotationLog(JoinPoint joinPoint) {
        Signature signature = joinPoint.getSignature();
        MethodSignature methodSignature = (MethodSignature) signature;
        Method method = methodSignature.getMethod();
        if (method != null) {
            return method.getAnnotation(LoginLogAop.class);
        }
        return null;
    }

    /**
     * 获取入参
     */
    private Map<String, Object> getRequestParams(JoinPoint joinPoint) {
        Map<String, Object> requestParams = new HashMap<>();
        // 参数名
        String[] paramNames = ((MethodSignature) joinPoint.getSignature()).getParameterNames();
        // 参数值
        Object[] paramValues = joinPoint.getArgs();
        for (int i = 0; i < paramNames.length; i++) {
            Object value = paramValues[i];
            // 如果是文件对象
            if (value instanceof MultipartFile) {
                MultipartFile file = (MultipartFile) value;
                // 获取文件名
                value = file.getOriginalFilename();
            }
            requestParams.put(paramNames[i], value);
        }
        return requestParams;
    }
}

二、前置处理方式:

1、自定义注解类LogoutLogAop.class

package com.fy.test.log.annotation;

import java.lang.annotation.*;

/**
 * @ClassName: LogoutLogAop
 * @Description: 
 * @Author fy
 * @Date 2023/07/10 9:10
 */
@Target({ElementType.PARAMETER, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface LogoutLogAop {

    /**
     * 描述
     */
    String desc() default "";

}

2、切面处理类LogoutLogAspect.java

package com.fy.test.log.aspect;

import cn.hutool.extra.servlet.ServletUtil;
import cn.hutool.http.useragent.UserAgent;
import cn.hutool.http.useragent.UserAgentUtil;
import com.fy.test.log.annotation.LoginLogAop;
import com.fy.test.log.annotation.LogoutLogAop;
import com.fy.test.utils.RequestHolder;
import com.fy.test.utils.SecurityUtil;
import com.fy.test.service.dto.LoginLogDto;
import com.fy.test.service.feign.LogServiceFeign;
import com.fy.test.service.vo.UserVo;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.fasterxml.jackson.databind.ObjectMapper;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.Signature;
import org.aspectj.lang.annotation.*;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import org.springframework.web.multipart.MultipartFile;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpSession;
import java.lang.reflect.Method;
import java.time.LocalDateTime;
import java.util.HashMap;
import java.util.Map;

/**
 * @ClassName: LogoutLogAspect
 * @Description:
 * @Author fy
 * @Date 2023/07/10 9:10
 */
@Slf4j
@Aspect
public class LogoutLogAspect {

    @Autowired
    private LogServiceFeign logServiceFeign;

    /**
     * 配置织入点
     */
    @Pointcut("@annotation(com.fy.test.log.annotation.LogoutLogAop)")
    public void logPointCut() {
    }

    /**
     * 通知方法会将目标方法封装起来
     *
     * @param joinPoint 切点
     */
    @Before("logPointCut()")
    public void doBefore(JoinPoint joinPoint) throws Throwable {
        try {
            System.out.println("==============前置处理开始==============");
            LoginLogDto logDto = getLog();
            logDto.setStatus(true);
            handleLog(joinPoint, logDto);
        } catch (Exception e) {
            //记录本地异常日志
            log.error("==前置通知异常==");
            log.error("异常信息:{}", e.getMessage());
        }

    }

    /**
     * 通知方法会在目标方法抛出异常后执行
     *
     * @param joinPoint
     * @param e
     */
    @AfterThrowing(value = "logPointCut()", throwing = "e")
    public void doAfterThrowing(JoinPoint joinPoint, Exception e) {
        LoginLogDto logDto = getLog();
        logDto.setExCode(e.getClass().getSimpleName()).setExMsg(e.getMessage());
        logDto.setStatus(false);
        handleLog(joinPoint, logDto);
    }

    private LoginLogDto getLog() {
        HttpServletRequest request = RequestHolder.getHttpServletRequest();
        UserAgent userAgent = UserAgentUtil.parse(request.getHeader("User-Agent"));

        LoginLogDto loginLogDto = new LoginLogDto();
        loginLogDto.setOpIp(ServletUtil.getClientIP(request))
                .setOpOs(userAgent.getOs().getName())
                .setOpBrowser(userAgent.getBrowser().getName())
                .setUserId(SecurityUtil.getUserId())
                .setUserName(SecurityUtil.getUserName())
                .setOpDate(LocalDateTime.now());

        return loginLogDto;
    }

    protected void handleLog(final JoinPoint joinPoint, LoginLogDto loginLogDto) {
        // 获得注解
        LogoutLogAop logAop = getAnnotationLog(joinPoint);
        if (null == logAop) {
            return;
        }

        loginLogDto.setDesc(logAop.desc());
        // 保存数据库
        logServiceFeign.saveLoginLog(loginLogDto);
    }

    /**
     * 是否存在注解,如果存在就获取
     */
    private LogoutLogAop getAnnotationLog(JoinPoint joinPoint) {
        Signature signature = joinPoint.getSignature();
        MethodSignature methodSignature = (MethodSignature) signature;
        Method method = methodSignature.getMethod();
        if (method != null) {
            return method.getAnnotation(LogoutLogAop.class);
        }
        return null;
    }

    /**
     * 获取入参
     */
    private Map<String, Object> getRequestParams(JoinPoint joinPoint) {
        Map<String, Object> requestParams = new HashMap<>();
        // 参数名
        String[] paramNames = ((MethodSignature) joinPoint.getSignature()).getParameterNames();
        // 参数值
        Object[] paramValues = joinPoint.getArgs();
        for (int i = 0; i < paramNames.length; i++) {
            Object value = paramValues[i];
            // 如果是文件对象
            if (value instanceof MultipartFile) {
                MultipartFile file = (MultipartFile) value;
                // 获取文件名
                value = file.getOriginalFilename();
            }
            requestParams.put(paramNames[i], value);
        }
        return requestParams;
    }
}

三、Proceedingjoinpoint简述

Proceedingjoinpoint 继承了JoinPoint,在JoinPoint的基础上暴露出 proceed(), 这个方法是AOP代理链执行的方法。

JoinPoint仅能获取相关参数,无法执行连接点。暴露出proceed()这个方法,就能支持 aop:around 这种切面(而其他的几种切面只需要用到JoinPoint,这跟切面类型有关),就能控制走代理链还是走自己拦截的其他逻辑。

import org.aspectj.lang.reflect.SourceLocation;  
public interface JoinPoint {  
   String toString();         //连接点所在位置的相关信息  
   String toShortString();    //连接点所在位置的简短相关信息  
   String toLongString();     //连接点所在位置的全部相关信息  
   Object getThis();          //返回AOP代理对象,也就是com.sun.proxy.$Proxy18
   Object getTarget();        //返回目标对象,一般我们都需要它或者(也就是定义方法的接口或类,为什么会是接口呢?
                              //这主要是在目标对象本身是动态代理的情况下,例如Mapper。所以返回的是定义方法的对象如
                              //aoptest.daoimpl.GoodDaoImpl或com.b.base.BaseMapper
   Object[] getArgs();        //返回被通知方法参数列表  
   Signature getSignature();  //返回当前连接点签名。其getName()方法返回方法的FQN,如void aoptest.dao.GoodDao.delete()
                              //或com.b.base.BaseMapper.insert(T)(需要注意的是,很多时候我们定义了子类继承父类的时候,
                              //我们希望拿到基于子类的FQN,无法直接拿到,要依赖于
                              //AopUtils.getTargetClass(point.getTarget())获取原始代理对象,下面会详细讲解)
   SourceLocation getSourceLocation();//返回连接点方法所在类文件中的位置  
   String getKind();           //连接点类型  
   StaticPart getStaticPart(); //返回连接点静态部分  
  }  
 
 public interface ProceedingJoinPoint extends JoinPoint {  
       public Object proceed() throws Throwable;  
       public Object proceed(Object[] args) throws Throwable;  
 }

JoinPoint.StaticPart:提供访问连接点的静态部分,如被通知方法签名、连接点类型等等。

public interface StaticPart {  
   Signature getSignature();    //返回当前连接点签名  
   String getKind();            //连接点类型  
   int getId();                 //唯一标识  
   String toString();           //连接点所在位置的相关信息  
   String toShortString();      //连接点所在位置的简短相关信息  
   String toLongString();       //连接点所在位置的全部相关信息  
}

你可能感兴趣的:(Java,spring,mybatis,java)