
com.github.mengweijin.quickboot.log.LogAspect Maven / Gradle / Ivy
package com.github.mengweijin.quickboot.log;
import cn.hutool.core.io.IoUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.extra.servlet.ServletUtil;
import cn.hutool.json.JSONUtil;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.github.mengweijin.quickboot.filter.repeatable.RepeatedlyRequestWrapper;
import com.github.mengweijin.quickboot.util.Const;
import com.github.mengweijin.quickboot.util.ServletUtils;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.Signature;
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.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.beans.factory.annotation.Autowired;
import javax.servlet.http.HttpServletRequest;
import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
import java.nio.charset.StandardCharsets;
import java.time.LocalDateTime;
import java.time.ZoneOffset;
import java.time.ZonedDateTime;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Map;
import java.util.function.Consumer;
/**
* @author Meng Wei Jin
*
* - @Before 是在所拦截方法执行之前执行一段逻辑。
* - @After 是在所拦截方法执行之后执行一段逻辑。
* - @Around 是可以同时在所拦截方法的前后执行一段逻辑(写在point.proceed方法前就是之前执行, 写在point.proceed方法后就是之后执行)。
* - @AfterReturning finally块中执行
* - @AfterThrowing 捕获到异常会执行
* 执行顺序:Before, After, AfterReturning, AfterThrowing
**/
@Slf4j
@Aspect
public class LogAspect {
@Autowired
private ObjectMapper objectMapper;
/**
* 钩子函数。比如:可以把 AppLog 保存到数据库作为系统操作日志记录
*/
private final Consumer consumer;
public LogAspect(Consumer consumer){
this.consumer = consumer;
}
private final ThreadLocal threadLocal = new ThreadLocal<>();
@Pointcut("@within(org.springframework.stereotype.Controller) || @within(org.springframework.web.bind.annotation.RestController)")
public void pointCut() {
// just a point cut, no need do anything.
}
@Before("pointCut()")
public void before(JoinPoint joinPoint){
try {
AppLog appLog = new AppLog();
HttpServletRequest request = ServletUtils.getRequest();
// 这里的 request.getParameterMap() 方法的调用必须早于下面 ServletUtils.getBody(request)
// 否则也会发生下面注释中说到的流不能重复读取的问题,造成获取不到数据。
Map argsMap = request.getParameterMap();
appLog.setArgs(argsMap);
// 这里会从 request 中通过流的方式读取 requestBody,而默认,流只能读取一次,第二次就读不到数据了。
// 在 SpringMVC 中,会先解析 @RequestBody 注释的参数,而触发 requestBody 数据的流读取。
// 此时就造成日志这里因为读取不到流数据而报错。
// 解决方法:添加可重复读取流的过滤器,详情参见 RepeatableFilter
if (request instanceof RepeatedlyRequestWrapper) {
RepeatedlyRequestWrapper repeatedlyRequest = (RepeatedlyRequestWrapper) request;
String body = IoUtil.read(repeatedlyRequest.getInputStream(), StandardCharsets.UTF_8);
if(StrUtil.isNotBlank(body)) {
if(JSONUtil.isTypeJSON(body)) {
appLog.setRequestBody(objectMapper.readValue(body, HashMap.class));
} else if(JSONUtil.isTypeJSONArray(body)) {
appLog.setRequestBody(objectMapper.readValue(body, ArrayList.class));
} else {
appLog.setRequestBody(body);
}
}
}
String methodName = joinPoint.getTarget().getClass().getName() + Const.DOT + joinPoint.getSignature().getName();
appLog.setMethodName(methodName);
appLog.setUrl(request.getRequestURI());
appLog.setHttpMethod(request.getMethod());
appLog.setOperateUtcTime(ZonedDateTime.now(ZoneOffset.UTC));
appLog.setOperateLocalTime(LocalDateTime.now());
appLog.setIp(ServletUtil.getClientIP(request));
threadLocal.set(appLog);
} catch (Exception e){
log.error("An exception has occurred to record the Controller logs in the LogAspect!", e);
}
}
/**
* @param joinPoint 切点
* @param object 返回的对象,参数名必须和注解中配置的名称保持一致。
*/
@AfterReturning(pointcut = "pointCut()", returning = "object")
public void afterReturning(JoinPoint joinPoint, Object object) {
recordLog(joinPoint, object, null);
}
/**
* 拦截异常操作
*
* @param joinPoint
* @param e
*/
@AfterThrowing(value = "pointCut()", throwing = "e")
public void afterThrowing(JoinPoint joinPoint, Exception e) {
recordLog(joinPoint, null, e);
}
/**
* 记录日志
*
* @param joinPoint
* @param object
* @param e
*/
private void recordLog(final JoinPoint joinPoint, final Object object, final Exception e) {
AppLog appLog = null;
try {
appLog = threadLocal.get();
appLog.setResponseBody(object);
if (e != null) {
appLog.setStatus(Const.FAILURE);
appLog.setErrorInfo(e.getMessage());
} else {
appLog.setStatus(Const.SUCCESS);
}
// record controller logs
log.debug(objectMapper.writeValueAsString(appLog));
} catch (Exception ex) {
log.error("An exception has occurred to record the Controller logs in the LogAspect!", ex);
} finally {
// 钩子函数。比如:可以把 AppLog 保存到数据库作为系统操作日志记录
consumer.accept(appLog);
threadLocal.remove();
}
}
/**
* 获取方法上指定的注解
*
* @param joinPoint JoinPoint
* @param annotationClass annotationClass
* @param T extends Annotation
* @return T extends Annotation
*/
public static T getMethodAnnotation(JoinPoint joinPoint, Class annotationClass) {
Signature signature = joinPoint.getSignature();
MethodSignature methodSignature = (MethodSignature) signature;
Method method = methodSignature.getMethod();
return method == null ? null : method.getAnnotation(annotationClass);
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy