• 首页 首页 icon
  • 工具库 工具库 icon
    • IP查询 IP查询 icon
  • 内容库 内容库 icon
    • 快讯库 快讯库 icon
    • 精品库 精品库 icon
    • 问答库 问答库 icon
  • 更多 更多 icon
    • 服务条款 服务条款 icon

Spring:Aop切面---日志收集环绕处理、前置处理方式--使用/教程/

武飞扬头像
多喝清晨的粥
帮助1

简介

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

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

系统登录日志类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<T, E, PK>)
   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();       //连接点所在位置的全部相关信息  
}

这篇好文章是转载于:学新通技术网

  • 版权申明: 本站部分内容来自互联网,仅供学习及演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系,请提供相关证据及您的身份证明,我们将在收到邮件后48小时内删除。
  • 本站站名: 学新通技术网
  • 本文地址: /boutique/detail/tanhgffhfi
系列文章
更多 icon
同类精品
更多 icon
继续加载