1. package com.example.demo.config;
    2. import org.aspectj.lang.JoinPoint;
    3. import org.aspectj.lang.ProceedingJoinPoint;
    4. import org.aspectj.lang.Signature;
    5. import org.aspectj.lang.annotation.Around;
    6. import org.aspectj.lang.annotation.Aspect;
    7. import org.aspectj.lang.annotation.Before;
    8. import org.aspectj.lang.annotation.Pointcut;
    9. import org.aspectj.lang.reflect.MethodSignature;
    10. import org.slf4j.Logger;
    11. import org.slf4j.LoggerFactory;
    12. import org.slf4j.MDC;
    13. import org.springframework.core.DefaultParameterNameDiscoverer;
    14. import org.springframework.core.ParameterNameDiscoverer;
    15. import org.springframework.stereotype.Component;
    16. import org.springframework.util.StringUtils;
    17. import org.springframework.web.context.request.RequestContextHolder;
    18. import org.springframework.web.context.request.ServletRequestAttributes;
    19. import org.springframework.web.multipart.MultipartFile;
    20. import javax.servlet.ServletRequest;
    21. import javax.servlet.ServletResponse;
    22. import javax.servlet.http.HttpServletRequest;
    23. import java.lang.reflect.Field;
    24. import java.lang.reflect.Method;
    25. import java.util.*;
    26. @Aspect
    27. @Component
    28. public class LogAspect {
    29. private final static Logger LOG = LoggerFactory.getLogger(LogAspect.class);
    30. /**
    31. * 定义一个切点
    32. */
    33. @Pointcut("execution(public * com..*.controller..*Controller.*(..))")
    34. public void controllerPointcut() {
    35. }
    36. @Before("controllerPointcut()")
    37. public void doBefore(JoinPoint joinPoint) throws Throwable {
    38. // 日志编号
    39. MDC.put("UUID",
    40. // fixme 抽取方法
    41. UUID.randomUUID().toString().replace("-", ""));
    42. // 开始打印请求日志
    43. ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
    44. HttpServletRequest request = attributes.getRequest();
    45. Signature signature = joinPoint.getSignature();
    46. // 调用的 controller 方法名
    47. String name = signature.getName();
    48. // 打印业务操作
    49. String nameCn = "";
    50. // 根据方法名识别业务操作
    51. if (name.contains("list") || name.contains("query")) {
    52. nameCn = "查询";
    53. } else if (name.contains("save")) {
    54. nameCn = "保存";
    55. } else if (name.contains("delete")) {
    56. nameCn = "删除";
    57. } else {
    58. nameCn = "操作";
    59. }
    60. // 使用反射,获取业务名称
    61. Class clazz = signature.getDeclaringType();
    62. Field field;
    63. String businessName = "";
    64. try {
    65. // 需要在 controller 中定义该字段
    66. field = clazz.getField("BUSINESS_NAME");
    67. if (!StringUtils.isEmpty(field)) {
    68. businessName = (String) field.get(clazz);
    69. }
    70. } catch (NoSuchFieldException e) {
    71. LOG.error("未获取到业务名称");
    72. } catch (SecurityException e) {
    73. LOG.error("获取业务名称失败", e);
    74. }
    75. // 打印请求信息
    76. LOG.info("------------- 【{}】{}开始 -------------", businessName, nameCn);
    77. LOG.info("请求地址: {} {}", request.getRequestURL().toString(), request.getMethod());
    78. LOG.info("类名方法: {}.{}", signature.getDeclaringTypeName(), name);
    79. LOG.info("远程地址: {}", request.getRemoteAddr());
    80. // 打印请求参数
    81. Object[] args = joinPoint.getArgs();
    82. // 这里获取到所有的参数值的数组
    83. MethodSignature methodSignature = (MethodSignature) signature;
    84. // 最关键的一步:通过这获取到方法的所有参数名称的字符串数组
    85. String[] parameterNames = methodSignature.getParameterNames();
    86. Map<String, Object> argMap = new HashMap<>(args.length);
    87. for (int i=0; i<args.length; i++) {
    88. // 去除非必要参数
    89. if (args[i] instanceof ServletRequest
    90. || args[i] instanceof ServletResponse
    91. || args[i] instanceof MultipartFile) {
    92. continue;
    93. }
    94. argMap.put(parameterNames[i], args[i]);
    95. }
    96. // todo 去除请求中的敏感字段
    97. String[] excludeProperties = {"password"};
    98. for (String key: excludeProperties) {
    99. argMap.remove(key);
    100. }
    101. LOG.info("请求参数: {}, 其中去除了敏感字段: {}",
    102. argMap, Arrays.asList(excludeProperties));
    103. }
    104. @Around("controllerPointcut()")
    105. public Object doAround(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
    106. long startTime = System.currentTimeMillis();
    107. Object result = proceedingJoinPoint.proceed();
    108. // 排除字段,敏感字段或太长的字段不显示
    109. String[] excludeProperties = {"password", "shard"};
    110. // todo 去除 结果集的敏感内容?
    111. LOG.info("返回结果: {}", result);
    112. LOG.info("------------- 结束 耗时:{} ms -------------", System.currentTimeMillis() - startTime);
    113. return result;
    114. }
    115. }