package com.example.demo.config;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.Signature;
import org.aspectj.lang.annotation.Around;
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.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.slf4j.MDC;
import org.springframework.core.DefaultParameterNameDiscoverer;
import org.springframework.core.ParameterNameDiscoverer;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import org.springframework.web.multipart.MultipartFile;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.*;
@Aspect
@Component
public class LogAspect {
private final static Logger LOG = LoggerFactory.getLogger(LogAspect.class);
/**
* 定义一个切点
*/
@Pointcut("execution(public * com..*.controller..*Controller.*(..))")
public void controllerPointcut() {
}
@Before("controllerPointcut()")
public void doBefore(JoinPoint joinPoint) throws Throwable {
// 日志编号
MDC.put("UUID",
// fixme 抽取方法
UUID.randomUUID().toString().replace("-", ""));
// 开始打印请求日志
ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
HttpServletRequest request = attributes.getRequest();
Signature signature = joinPoint.getSignature();
// 调用的 controller 方法名
String name = signature.getName();
// 打印业务操作
String nameCn = "";
// 根据方法名识别业务操作
if (name.contains("list") || name.contains("query")) {
nameCn = "查询";
} else if (name.contains("save")) {
nameCn = "保存";
} else if (name.contains("delete")) {
nameCn = "删除";
} else {
nameCn = "操作";
}
// 使用反射,获取业务名称
Class clazz = signature.getDeclaringType();
Field field;
String businessName = "";
try {
// 需要在 controller 中定义该字段
field = clazz.getField("BUSINESS_NAME");
if (!StringUtils.isEmpty(field)) {
businessName = (String) field.get(clazz);
}
} catch (NoSuchFieldException e) {
LOG.error("未获取到业务名称");
} catch (SecurityException e) {
LOG.error("获取业务名称失败", e);
}
// 打印请求信息
LOG.info("------------- 【{}】{}开始 -------------", businessName, nameCn);
LOG.info("请求地址: {} {}", request.getRequestURL().toString(), request.getMethod());
LOG.info("类名方法: {}.{}", signature.getDeclaringTypeName(), name);
LOG.info("远程地址: {}", request.getRemoteAddr());
// 打印请求参数
Object[] args = joinPoint.getArgs();
// 这里获取到所有的参数值的数组
MethodSignature methodSignature = (MethodSignature) signature;
// 最关键的一步:通过这获取到方法的所有参数名称的字符串数组
String[] parameterNames = methodSignature.getParameterNames();
Map<String, Object> argMap = new HashMap<>(args.length);
for (int i=0; i<args.length; i++) {
// 去除非必要参数
if (args[i] instanceof ServletRequest
|| args[i] instanceof ServletResponse
|| args[i] instanceof MultipartFile) {
continue;
}
argMap.put(parameterNames[i], args[i]);
}
// todo 去除请求中的敏感字段
String[] excludeProperties = {"password"};
for (String key: excludeProperties) {
argMap.remove(key);
}
LOG.info("请求参数: {}, 其中去除了敏感字段: {}",
argMap, Arrays.asList(excludeProperties));
}
@Around("controllerPointcut()")
public Object doAround(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
long startTime = System.currentTimeMillis();
Object result = proceedingJoinPoint.proceed();
// 排除字段,敏感字段或太长的字段不显示
String[] excludeProperties = {"password", "shard"};
// todo 去除 结果集的敏感内容?
LOG.info("返回结果: {}", result);
LOG.info("------------- 结束 耗时:{} ms -------------", System.currentTimeMillis() - startTime);
return result;
}
}