石家莊網(wǎng)站開發(fā)設(shè)計(jì)百度網(wǎng)站下載
Spring之Aop切面---日志收集(環(huán)繞處理、前置處理方式)--使用/教程/實(shí)例
- 簡介
- 系統(tǒng)登錄日志類LoginLogEntity .java
- 一、環(huán)繞處理方式
- 1、自定義注解類LoginLogAop.class
- 2、切面處理類LogoutLogAspect.java
- 二、前置處理方式:
- 1、自定義注解類LogoutLogAop.class
- 2、切面處理類LogoutLogAspect.java
- 三、Proceedingjoinpoint簡述
簡介
本文章介紹采用兩種不同方式處理----系統(tǒng)登錄、系統(tǒng)退出登錄兩種場景日志。
- 環(huán)繞處理系統(tǒng)登錄日志
- 前置處理系統(tǒng)退出登錄日志
系統(tǒng)登錄日志類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;/*** 操作系統(tǒng)*/private String opOs;/*** 瀏覽器類型*/private String opBrowser;/*** 登錄IP地址*/private String opIp;/*** 登錄時(shí)間*/private LocalDateTime opDate;/*** 登錄用戶ID*/private String userId;/*** 登錄用戶名稱*/private String userName;/*** 錯(cuò)誤類型*/private String exCode;/*** 錯(cuò)誤信息*/private String exMsg;/*** 登錄狀態(tài)*/private boolean status;/*** 描述*/private String desc;
}
一、環(huán)繞處理方式
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 {@Autowiredprivate LogServiceFeign logServiceFeign;/*** 配置織入點(diǎn)*/@Pointcut("@annotation(com.fy.test.common.log.annotation.LoginLogAop)")public void logPointCut() {}/*** 通知方法會(huì)將目標(biāo)方法封裝起來* 注意:環(huán)繞方式選擇ProceedingJoinPoint* Proceedingjoinpoint 繼承了JoinPoint,在JoinPoint的基礎(chǔ)上暴露出 proceed(), 這個(gè)方法是AOP代理鏈執(zhí)行的方法。* JoinPoint僅能獲取相關(guān)參數(shù),無法執(zhí)行連接點(diǎn)。* 暴露出proceed()這個(gè)方法,就能支持 aop:around 這種切面(而其他的幾種切面只需要用到JoinPoint,這跟切面類型有關(guān)),* 就能控制走代理鏈還是走自己攔截的其他邏輯。 * * @param joinPoint 切點(diǎn)*/@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;}/*** 通知方法會(huì)在目標(biāo)方法拋出異常后執(zhí)行** @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());}}// 保存數(shù)據(jù)庫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;}/*** 獲取入?yún)?/private Map<String, Object> getRequestParams(JoinPoint joinPoint) {Map<String, Object> requestParams = new HashMap<>();// 參數(shù)名String[] paramNames = ((MethodSignature) joinPoint.getSignature()).getParameterNames();// 參數(shù)值Object[] paramValues = joinPoint.getArgs();for (int i = 0; i < paramNames.length; i++) {Object value = paramValues[i];// 如果是文件對(duì)象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 {@Autowiredprivate LogServiceFeign logServiceFeign;/*** 配置織入點(diǎn)*/@Pointcut("@annotation(com.fy.test.log.annotation.LogoutLogAop)")public void logPointCut() {}/*** 通知方法會(huì)將目標(biāo)方法封裝起來** @param joinPoint 切點(diǎn)*/@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());}}/*** 通知方法會(huì)在目標(biāo)方法拋出異常后執(zhí)行** @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());// 保存數(shù)據(jù)庫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;}/*** 獲取入?yún)?/private Map<String, Object> getRequestParams(JoinPoint joinPoint) {Map<String, Object> requestParams = new HashMap<>();// 參數(shù)名String[] paramNames = ((MethodSignature) joinPoint.getSignature()).getParameterNames();// 參數(shù)值Object[] paramValues = joinPoint.getArgs();for (int i = 0; i < paramNames.length; i++) {Object value = paramValues[i];// 如果是文件對(duì)象if (value instanceof MultipartFile) {MultipartFile file = (MultipartFile) value;// 獲取文件名value = file.getOriginalFilename();}requestParams.put(paramNames[i], value);}return requestParams;}
}
三、Proceedingjoinpoint簡述
Proceedingjoinpoint 繼承了JoinPoint,在JoinPoint的基礎(chǔ)上暴露出 proceed(), 這個(gè)方法是AOP代理鏈執(zhí)行的方法。
JoinPoint僅能獲取相關(guān)參數(shù),無法執(zhí)行連接點(diǎn)。暴露出proceed()這個(gè)方法,就能支持 aop:around 這種切面(而其他的幾種切面只需要用到JoinPoint,這跟切面類型有關(guān)),就能控制走代理鏈還是走自己攔截的其他邏輯。
import org.aspectj.lang.reflect.SourceLocation;
public interface JoinPoint { String toString(); //連接點(diǎn)所在位置的相關(guān)信息 String toShortString(); //連接點(diǎn)所在位置的簡短相關(guān)信息 String toLongString(); //連接點(diǎn)所在位置的全部相關(guān)信息 Object getThis(); //返回AOP代理對(duì)象,也就是com.sun.proxy.$Proxy18Object getTarget(); //返回目標(biāo)對(duì)象,一般我們都需要它或者(也就是定義方法的接口或類,為什么會(huì)是接口呢?//這主要是在目標(biāo)對(duì)象本身是動(dòng)態(tài)代理的情況下,例如Mapper。所以返回的是定義方法的對(duì)象如//aoptest.daoimpl.GoodDaoImpl或com.b.base.BaseMapper<T, E, PK>)Object[] getArgs(); //返回被通知方法參數(shù)列表 Signature getSignature(); //返回當(dāng)前連接點(diǎn)簽名。其getName()方法返回方法的FQN,如void aoptest.dao.GoodDao.delete()//或com.b.base.BaseMapper.insert(T)(需要注意的是,很多時(shí)候我們定義了子類繼承父類的時(shí)候,//我們希望拿到基于子類的FQN,無法直接拿到,要依賴于//AopUtils.getTargetClass(point.getTarget())獲取原始代理對(duì)象,下面會(huì)詳細(xì)講解)SourceLocation getSourceLocation();//返回連接點(diǎn)方法所在類文件中的位置 String getKind(); //連接點(diǎn)類型 StaticPart getStaticPart(); //返回連接點(diǎn)靜態(tài)部分 } public interface ProceedingJoinPoint extends JoinPoint { public Object proceed() throws Throwable; public Object proceed(Object[] args) throws Throwable; }
JoinPoint.StaticPart:提供訪問連接點(diǎn)的靜態(tài)部分,如被通知方法簽名、連接點(diǎn)類型等等。
public interface StaticPart { Signature getSignature(); //返回當(dāng)前連接點(diǎn)簽名 String getKind(); //連接點(diǎn)類型 int getId(); //唯一標(biāo)識(shí) String toString(); //連接點(diǎn)所在位置的相關(guān)信息 String toShortString(); //連接點(diǎn)所在位置的簡短相關(guān)信息 String toLongString(); //連接點(diǎn)所在位置的全部相關(guān)信息
}