From 456117e989997b5776a4cf4ca1cc59743eb17adb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BC=A0=E5=B0=8F=E4=B8=9C?= <13396135+anton-aoi@user.noreply.gitee.com> Date: Wed, 22 Nov 2023 11:15:47 +0800 Subject: [PATCH] =?UTF-8?q?=E6=B7=BB=E5=8A=A0AOP=E6=97=A5=E5=BF=97?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- pom.xml | 24 + .../february/common/log/aspect/LogAspect.java | 500 +++++++++--------- .../february/common/log/aspect/WebLog.java | 16 + .../common/log/aspect/WebLogAspect.java | 87 +++ 4 files changed, 377 insertions(+), 250 deletions(-) create mode 100644 src/main/java/com/february/common/log/aspect/WebLog.java create mode 100644 src/main/java/com/february/common/log/aspect/WebLogAspect.java diff --git a/pom.xml b/pom.xml index 8b626b4..2fc37e0 100644 --- a/pom.xml +++ b/pom.xml @@ -28,6 +28,30 @@ 3.6.3 compile + + + + + org.springframework.boot + spring-boot-starter-aop + + + + com.google.code.gson + gson + 2.8.9 + + + + org.springframework.boot + spring-boot-starter + + + + + org.springframework.boot + spring-boot-starter-logging + diff --git a/src/main/java/com/february/common/log/aspect/LogAspect.java b/src/main/java/com/february/common/log/aspect/LogAspect.java index 0a61c17..9a85e69 100644 --- a/src/main/java/com/february/common/log/aspect/LogAspect.java +++ b/src/main/java/com/february/common/log/aspect/LogAspect.java @@ -1,250 +1,250 @@ -package com.february.common.log.aspect; - -import java.util.Collection; -import java.util.Map; -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; - -import com.february.system.domain.SysOperLog; -import org.apache.commons.lang3.ArrayUtils; -import org.aspectj.lang.JoinPoint; -import org.aspectj.lang.annotation.AfterReturning; -import org.aspectj.lang.annotation.AfterThrowing; -import org.aspectj.lang.annotation.Aspect; -import org.aspectj.lang.annotation.Before; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.core.NamedThreadLocal; -import org.springframework.http.HttpMethod; -import org.springframework.stereotype.Component; -import org.springframework.validation.BindingResult; -import org.springframework.web.multipart.MultipartFile; -import com.alibaba.fastjson2.JSON; -import com.february.common.core.utils.ServletUtils; -import com.february.common.core.utils.StringUtils; -import com.february.common.core.utils.ip.IpUtils; -import com.february.common.log.annotation.Log; -import com.february.common.log.enums.BusinessStatus; -import com.february.common.log.filter.PropertyPreExcludeFilter; -import com.february.common.log.service.AsyncLogService; -import com.february.common.security.utils.SecurityUtils; - -/** - * 操作日志记录处理 - * - * @author february - */ -@Aspect -@Component -public class LogAspect -{ - private static final Logger log = LoggerFactory.getLogger(LogAspect.class); - - /** 排除敏感属性字段 */ - public static final String[] EXCLUDE_PROPERTIES = { "password", "oldPassword", "newPassword", "confirmPassword" }; - - /** 计算操作消耗时间 */ - private static final ThreadLocal TIME_THREADLOCAL = new NamedThreadLocal("Cost Time"); - - @Autowired - private AsyncLogService asyncLogService; - - /** - * 处理请求前执行 - */ - @Before(value = "@annotation(controllerLog)") - public void boBefore(JoinPoint joinPoint, Log controllerLog) - { - TIME_THREADLOCAL.set(System.currentTimeMillis()); - } - - /** - * 处理完请求后执行 - * - * @param joinPoint 切点 - */ - @AfterReturning(pointcut = "@annotation(controllerLog)", returning = "jsonResult") - public void doAfterReturning(JoinPoint joinPoint, Log controllerLog, Object jsonResult) - { - handleLog(joinPoint, controllerLog, null, jsonResult); - } - - /** - * 拦截异常操作 - * - * @param joinPoint 切点 - * @param e 异常 - */ - @AfterThrowing(value = "@annotation(controllerLog)", throwing = "e") - public void doAfterThrowing(JoinPoint joinPoint, Log controllerLog, Exception e) - { - handleLog(joinPoint, controllerLog, e, null); - } - - protected void handleLog(final JoinPoint joinPoint, Log controllerLog, final Exception e, Object jsonResult) - { - try - { - // *========数据库日志=========*// - SysOperLog operLog = new SysOperLog(); - operLog.setStatus(BusinessStatus.SUCCESS.ordinal()); - // 请求的地址 - String ip = IpUtils.getIpAddr(); - operLog.setOperIp(ip); - operLog.setOperUrl(StringUtils.substring(ServletUtils.getRequest().getRequestURI(), 0, 255)); - String username = SecurityUtils.getUsername(); - if (StringUtils.isNotBlank(username)) - { - operLog.setOperName(username); - } - - if (e != null) - { - operLog.setStatus(BusinessStatus.FAIL.ordinal()); - operLog.setErrorMsg(StringUtils.substring(e.getMessage(), 0, 2000)); - } - // 设置方法名称 - String className = joinPoint.getTarget().getClass().getName(); - String methodName = joinPoint.getSignature().getName(); - operLog.setMethod(className + "." + methodName + "()"); - // 设置请求方式 - operLog.setRequestMethod(ServletUtils.getRequest().getMethod()); - // 处理设置注解上的参数 - getControllerMethodDescription(joinPoint, controllerLog, operLog, jsonResult); - // 设置消耗时间 - operLog.setCostTime(System.currentTimeMillis() - TIME_THREADLOCAL.get()); - // 保存数据库 - asyncLogService.saveSysLog(operLog); - } - catch (Exception exp) - { - // 记录本地异常日志 - log.error("异常信息:{}", exp.getMessage()); - exp.printStackTrace(); - } - finally - { - TIME_THREADLOCAL.remove(); - } - } - - /** - * 获取注解中对方法的描述信息 用于Controller层注解 - * - * @param log 日志 - * @param operLog 操作日志 - * @throws Exception - */ - public void getControllerMethodDescription(JoinPoint joinPoint, Log log, SysOperLog operLog, Object jsonResult) throws Exception - { - // 设置action动作 - operLog.setBusinessType(log.businessType().ordinal()); - // 设置标题 - operLog.setTitle(log.title()); - // 设置操作人类别 - operLog.setOperatorType(log.operatorType().ordinal()); - // 是否需要保存request,参数和值 - if (log.isSaveRequestData()) - { - // 获取参数的信息,传入到数据库中。 - setRequestValue(joinPoint, operLog, log.excludeParamNames()); - } - // 是否需要保存response,参数和值 - if (log.isSaveResponseData() && StringUtils.isNotNull(jsonResult)) - { - operLog.setJsonResult(StringUtils.substring(JSON.toJSONString(jsonResult), 0, 2000)); - } - } - - /** - * 获取请求的参数,放到log中 - * - * @param operLog 操作日志 - * @throws Exception 异常 - */ - private void setRequestValue(JoinPoint joinPoint, SysOperLog operLog, String[] excludeParamNames) throws Exception - { - String requestMethod = operLog.getRequestMethod(); - Map paramsMap = ServletUtils.getParamMap(ServletUtils.getRequest()); - if (StringUtils.isEmpty(paramsMap) - && (HttpMethod.PUT.name().equals(requestMethod) || HttpMethod.POST.name().equals(requestMethod))) - { - String params = argsArrayToString(joinPoint.getArgs(), excludeParamNames); - operLog.setOperParam(StringUtils.substring(params, 0, 2000)); - } - else - { - operLog.setOperParam(StringUtils.substring(JSON.toJSONString(paramsMap, excludePropertyPreFilter(excludeParamNames)), 0, 2000)); - } - } - - /** - * 参数拼装 - */ - private String argsArrayToString(Object[] paramsArray, String[] excludeParamNames) - { - String params = ""; - if (paramsArray != null && paramsArray.length > 0) - { - for (Object o : paramsArray) - { - if (StringUtils.isNotNull(o) && !isFilterObject(o)) - { - try - { - String jsonObj = JSON.toJSONString(o, excludePropertyPreFilter(excludeParamNames)); - params += jsonObj.toString() + " "; - } - catch (Exception e) - { - } - } - } - } - return params.trim(); - } - - /** - * 忽略敏感属性 - */ - public PropertyPreExcludeFilter excludePropertyPreFilter(String[] excludeParamNames) - { - return new PropertyPreExcludeFilter().addExcludes(ArrayUtils.addAll(EXCLUDE_PROPERTIES, excludeParamNames)); - } - - /** - * 判断是否需要过滤的对象。 - * - * @param o 对象信息。 - * @return 如果是需要过滤的对象,则返回true;否则返回false。 - */ - @SuppressWarnings("rawtypes") - public boolean isFilterObject(final Object o) - { - Class clazz = o.getClass(); - if (clazz.isArray()) - { - return clazz.getComponentType().isAssignableFrom(MultipartFile.class); - } - else if (Collection.class.isAssignableFrom(clazz)) - { - Collection collection = (Collection) o; - for (Object value : collection) - { - return value instanceof MultipartFile; - } - } - else if (Map.class.isAssignableFrom(clazz)) - { - Map map = (Map) o; - for (Object value : map.entrySet()) - { - Map.Entry entry = (Map.Entry) value; - return entry.getValue() instanceof MultipartFile; - } - } - return o instanceof MultipartFile || o instanceof HttpServletRequest || o instanceof HttpServletResponse - || o instanceof BindingResult; - } -} +//package com.february.common.log.aspect; +// +//import java.util.Collection; +//import java.util.Map; +//import javax.servlet.http.HttpServletRequest; +//import javax.servlet.http.HttpServletResponse; +// +//import com.february.system.domain.SysOperLog; +//import org.apache.commons.lang3.ArrayUtils; +//import org.aspectj.lang.JoinPoint; +//import org.aspectj.lang.annotation.AfterReturning; +//import org.aspectj.lang.annotation.AfterThrowing; +//import org.aspectj.lang.annotation.Aspect; +//import org.aspectj.lang.annotation.Before; +//import org.slf4j.Logger; +//import org.slf4j.LoggerFactory; +//import org.springframework.beans.factory.annotation.Autowired; +//import org.springframework.core.NamedThreadLocal; +//import org.springframework.http.HttpMethod; +//import org.springframework.stereotype.Component; +//import org.springframework.validation.BindingResult; +//import org.springframework.web.multipart.MultipartFile; +//import com.alibaba.fastjson2.JSON; +//import com.february.common.core.utils.ServletUtils; +//import com.february.common.core.utils.StringUtils; +//import com.february.common.core.utils.ip.IpUtils; +//import com.february.common.log.annotation.Log; +//import com.february.common.log.enums.BusinessStatus; +//import com.february.common.log.filter.PropertyPreExcludeFilter; +//import com.february.common.log.service.AsyncLogService; +//import com.february.common.security.utils.SecurityUtils; +// +///** +// * 操作日志记录处理 +// * +// * @author february +// */ +//@Aspect +//@Component +//public class LogAspect +//{ +// private static final Logger log = LoggerFactory.getLogger(LogAspect.class); +// +// /** 排除敏感属性字段 */ +// public static final String[] EXCLUDE_PROPERTIES = { "password", "oldPassword", "newPassword", "confirmPassword" }; +// +// /** 计算操作消耗时间 */ +// private static final ThreadLocal TIME_THREADLOCAL = new NamedThreadLocal("Cost Time"); +// +// @Autowired +// private AsyncLogService asyncLogService; +// +// /** +// * 处理请求前执行 +// */ +// @Before(value = "@annotation(controllerLog)") +// public void boBefore(JoinPoint joinPoint, Log controllerLog) +// { +// TIME_THREADLOCAL.set(System.currentTimeMillis()); +// } +// +// /** +// * 处理完请求后执行 +// * +// * @param joinPoint 切点 +// */ +// @AfterReturning(pointcut = "@annotation(controllerLog)", returning = "jsonResult") +// public void doAfterReturning(JoinPoint joinPoint, Log controllerLog, Object jsonResult) +// { +// handleLog(joinPoint, controllerLog, null, jsonResult); +// } +// +// /** +// * 拦截异常操作 +// * +// * @param joinPoint 切点 +// * @param e 异常 +// */ +// @AfterThrowing(value = "@annotation(controllerLog)", throwing = "e") +// public void doAfterThrowing(JoinPoint joinPoint, Log controllerLog, Exception e) +// { +// handleLog(joinPoint, controllerLog, e, null); +// } +// +// protected void handleLog(final JoinPoint joinPoint, Log controllerLog, final Exception e, Object jsonResult) +// { +// try +// { +// // *========数据库日志=========*// +// SysOperLog operLog = new SysOperLog(); +// operLog.setStatus(BusinessStatus.SUCCESS.ordinal()); +// // 请求的地址 +// String ip = IpUtils.getIpAddr(); +// operLog.setOperIp(ip); +// operLog.setOperUrl(StringUtils.substring(ServletUtils.getRequest().getRequestURI(), 0, 255)); +// String username = SecurityUtils.getUsername(); +// if (StringUtils.isNotBlank(username)) +// { +// operLog.setOperName(username); +// } +// +// if (e != null) +// { +// operLog.setStatus(BusinessStatus.FAIL.ordinal()); +// operLog.setErrorMsg(StringUtils.substring(e.getMessage(), 0, 2000)); +// } +// // 设置方法名称 +// String className = joinPoint.getTarget().getClass().getName(); +// String methodName = joinPoint.getSignature().getName(); +// operLog.setMethod(className + "." + methodName + "()"); +// // 设置请求方式 +// operLog.setRequestMethod(ServletUtils.getRequest().getMethod()); +// // 处理设置注解上的参数 +// getControllerMethodDescription(joinPoint, controllerLog, operLog, jsonResult); +// // 设置消耗时间 +// operLog.setCostTime(System.currentTimeMillis() - TIME_THREADLOCAL.get()); +// // 保存数据库 +// asyncLogService.saveSysLog(operLog); +// } +// catch (Exception exp) +// { +// // 记录本地异常日志 +// log.error("异常信息:{}", exp.getMessage()); +// exp.printStackTrace(); +// } +// finally +// { +// TIME_THREADLOCAL.remove(); +// } +// } +// +// /** +// * 获取注解中对方法的描述信息 用于Controller层注解 +// * +// * @param log 日志 +// * @param operLog 操作日志 +// * @throws Exception +// */ +// public void getControllerMethodDescription(JoinPoint joinPoint, Log log, SysOperLog operLog, Object jsonResult) throws Exception +// { +// // 设置action动作 +// operLog.setBusinessType(log.businessType().ordinal()); +// // 设置标题 +// operLog.setTitle(log.title()); +// // 设置操作人类别 +// operLog.setOperatorType(log.operatorType().ordinal()); +// // 是否需要保存request,参数和值 +// if (log.isSaveRequestData()) +// { +// // 获取参数的信息,传入到数据库中。 +// setRequestValue(joinPoint, operLog, log.excludeParamNames()); +// } +// // 是否需要保存response,参数和值 +// if (log.isSaveResponseData() && StringUtils.isNotNull(jsonResult)) +// { +// operLog.setJsonResult(StringUtils.substring(JSON.toJSONString(jsonResult), 0, 2000)); +// } +// } +// +// /** +// * 获取请求的参数,放到log中 +// * +// * @param operLog 操作日志 +// * @throws Exception 异常 +// */ +// private void setRequestValue(JoinPoint joinPoint, SysOperLog operLog, String[] excludeParamNames) throws Exception +// { +// String requestMethod = operLog.getRequestMethod(); +// Map paramsMap = ServletUtils.getParamMap(ServletUtils.getRequest()); +// if (StringUtils.isEmpty(paramsMap) +// && (HttpMethod.PUT.name().equals(requestMethod) || HttpMethod.POST.name().equals(requestMethod))) +// { +// String params = argsArrayToString(joinPoint.getArgs(), excludeParamNames); +// operLog.setOperParam(StringUtils.substring(params, 0, 2000)); +// } +// else +// { +// operLog.setOperParam(StringUtils.substring(JSON.toJSONString(paramsMap, excludePropertyPreFilter(excludeParamNames)), 0, 2000)); +// } +// } +// +// /** +// * 参数拼装 +// */ +// private String argsArrayToString(Object[] paramsArray, String[] excludeParamNames) +// { +// String params = ""; +// if (paramsArray != null && paramsArray.length > 0) +// { +// for (Object o : paramsArray) +// { +// if (StringUtils.isNotNull(o) && !isFilterObject(o)) +// { +// try +// { +// String jsonObj = JSON.toJSONString(o, excludePropertyPreFilter(excludeParamNames)); +// params += jsonObj.toString() + " "; +// } +// catch (Exception e) +// { +// } +// } +// } +// } +// return params.trim(); +// } +// +// /** +// * 忽略敏感属性 +// */ +// public PropertyPreExcludeFilter excludePropertyPreFilter(String[] excludeParamNames) +// { +// return new PropertyPreExcludeFilter().addExcludes(ArrayUtils.addAll(EXCLUDE_PROPERTIES, excludeParamNames)); +// } +// +// /** +// * 判断是否需要过滤的对象。 +// * +// * @param o 对象信息。 +// * @return 如果是需要过滤的对象,则返回true;否则返回false。 +// */ +// @SuppressWarnings("rawtypes") +// public boolean isFilterObject(final Object o) +// { +// Class clazz = o.getClass(); +// if (clazz.isArray()) +// { +// return clazz.getComponentType().isAssignableFrom(MultipartFile.class); +// } +// else if (Collection.class.isAssignableFrom(clazz)) +// { +// Collection collection = (Collection) o; +// for (Object value : collection) +// { +// return value instanceof MultipartFile; +// } +// } +// else if (Map.class.isAssignableFrom(clazz)) +// { +// Map map = (Map) o; +// for (Object value : map.entrySet()) +// { +// Map.Entry entry = (Map.Entry) value; +// return entry.getValue() instanceof MultipartFile; +// } +// } +// return o instanceof MultipartFile || o instanceof HttpServletRequest || o instanceof HttpServletResponse +// || o instanceof BindingResult; +// } +//} diff --git a/src/main/java/com/february/common/log/aspect/WebLog.java b/src/main/java/com/february/common/log/aspect/WebLog.java new file mode 100644 index 0000000..8cb7f62 --- /dev/null +++ b/src/main/java/com/february/common/log/aspect/WebLog.java @@ -0,0 +1,16 @@ +package com.february.common.log.aspect; +import java.lang.annotation.*; + +/** + * 切面类 + */ +@Retention(RetentionPolicy.RUNTIME) +@Target({ElementType.METHOD}) +@Documented +public @interface WebLog { + /** + * 日志描述信息 + * @return + **/ + String description() default ""; +} diff --git a/src/main/java/com/february/common/log/aspect/WebLogAspect.java b/src/main/java/com/february/common/log/aspect/WebLogAspect.java new file mode 100644 index 0000000..29b75d7 --- /dev/null +++ b/src/main/java/com/february/common/log/aspect/WebLogAspect.java @@ -0,0 +1,87 @@ +package com.february.common.log.aspect; + +import com.google.gson.Gson; +import org.aspectj.lang.JoinPoint; +import org.aspectj.lang.ProceedingJoinPoint; +import org.aspectj.lang.annotation.*; +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.lang.reflect.Method; + +/** + * AOP日志 + */ +@Aspect +@Component +public class WebLogAspect { + + private final static Logger logger = LoggerFactory.getLogger(WebLogAspect.class); + private static final Gson gson = new Gson(); // 用于重复使用Gson实例 + private static final String LINE_SEPARATOR = System.lineSeparator(); + + @Pointcut("@annotation(com.february.common.log.aspect.WebLog)") + public void webLog() {} + + @Before("webLog()") + public void doBefore(JoinPoint joinPoint) { + // 开始打印请求日志 + ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes(); + if (attributes != null) { + HttpServletRequest request = attributes.getRequest(); + + // 获取 @WebLog 注解的描述信息 + String methodDescription; + try { + methodDescription = getAspectLogDescription(joinPoint); + } catch (Exception e) { + methodDescription = "无法获取方法描述信息"; + logger.error("获取方法描述信息时发生异常: ", e); + } + + // 打印请求相关参数 + logger.info("========================================== Start =========================================="); + logger.info("URL : {}", request.getRequestURL().toString()); + logger.info("Description : {}", methodDescription); + logger.info("HTTP Method : {}", request.getMethod()); + logger.info("Class Method : {}.{}", joinPoint.getSignature().getDeclaringTypeName(), joinPoint.getSignature().getName()); + logger.info("IP : {}", request.getRemoteAddr()); + logger.info("Request Args : {}", gson.toJson(joinPoint.getArgs())); + } + } + + @After("webLog()") + public void doAfter() { + logger.info("=========================================== End ==========================================={}", LINE_SEPARATOR); + } + + @Around("webLog()") + public Object doAround(ProceedingJoinPoint proceedingJoinPoint) throws Throwable { + long startTime = System.currentTimeMillis(); + Object result = proceedingJoinPoint.proceed(); + logger.info("Response Args : {}", gson.toJson(result)); + logger.info("Time-Consuming : {} ms", System.currentTimeMillis() - startTime); + return result; + } + + private String getAspectLogDescription(JoinPoint joinPoint) throws ClassNotFoundException { + String targetName = joinPoint.getTarget().getClass().getName(); + String methodName = joinPoint.getSignature().getName(); + Object[] arguments = joinPoint.getArgs(); + Class targetClass = Class.forName(targetName); + Method[] methods = targetClass.getMethods(); + for (Method method : methods) { + if (method.getName().equals(methodName) && method.getParameterTypes().length == arguments.length) { + WebLog webLog = method.getAnnotation(WebLog.class); + if (webLog != null) { + return webLog.description(); + } + } + } + return ""; + } +}