• Springboot使用Aop保存接口请求日志到mysql(及解决Interceptor拦截器中引用mapper和service为null)


    一、Springboot使用Aop保存接口请求日志到mysql

    1、添加aop依赖

    1. <dependency>
    2. <groupId>org.springframework.bootgroupId>
    3. <artifactId>spring-boot-starter-aopartifactId>
    4. dependency>

    2、新建接口保存数据库的实体类RequestLog.java

    1. package com.example.springboot.entity;
    2. import com.baomidou.mybatisplus.annotation.IdType;
    3. import com.baomidou.mybatisplus.annotation.TableId;
    4. import com.baomidou.mybatisplus.annotation.TableName;
    5. import java.io.Serializable;
    6. import java.time.LocalDateTime;
    7. import lombok.Getter;
    8. import lombok.Setter;
    9. /**
    10. *

    11. * 请求日志
    12. *

    13. *
    14. * @author Sca_jie
    15. * @since 2023-09-28
    16. */
    17. @Getter
    18. @Setter
    19. @TableName("request_log")
    20. public class RequestLog implements Serializable {
    21. private static final long serialVersionUID = 1L;
    22. // 主键-自增
    23. @TableId(value = "number", type = IdType.AUTO)
    24. private Integer number;
    25. // 用户账号
    26. private String id;
    27. // 携带token
    28. private String token;
    29. // 接口路径
    30. private String url;
    31. // 请求类型
    32. private String method;
    33. // 携带参数
    34. private String params;
    35. // ip地址
    36. private String ip;
    37. // 结果
    38. private String result;
    39. // 接口发起时间
    40. private LocalDateTime startDate;
    41. // 接口结束时间
    42. private LocalDateTime endDate;
    43. // 响应耗时
    44. private String responseTime;
    45. }

    3、新建一个注解RequestLogAnnotation.java,用于特定类使用aop(这里是给全局异常留的)

    1. package com.example.springboot.annotation;
    2. import java.lang.annotation.*;
    3. /**
    4. * 请求记录日志注解
    5. */
    6. @Target({ElementType.TYPE, ElementType.METHOD}) //注解放置的目标位置,METHOD是可注解在方法级别上
    7. @Retention(RetentionPolicy.RUNTIME) //注解在哪个阶段执行
    8. @Documented
    9. public @interface RequestLogAnnotation {
    10. String value() default "";
    11. }

    4、(核心)新建aop面切类RequestLogAspect.java拦截请求并保存日志

    1. package com.example.springboot.common;
    2. import cn.hutool.core.net.NetUtil;
    3. import cn.hutool.json.JSONUtil;
    4. import com.alibaba.fastjson.JSONObject;
    5. import com.example.springboot.annotation.RequestLogAnnotation;
    6. import com.example.springboot.entity.RequestLog;
    7. import com.example.springboot.mapper.RequestLogMapper;
    8. import com.example.springboot.utils.CookieUtil;
    9. import org.aspectj.lang.JoinPoint;
    10. import org.aspectj.lang.annotation.AfterReturning;
    11. import org.aspectj.lang.annotation.Aspect;
    12. import org.aspectj.lang.annotation.Before;
    13. import org.aspectj.lang.annotation.Pointcut;
    14. import org.aspectj.lang.reflect.MethodSignature;
    15. import org.springframework.beans.factory.annotation.Autowired;
    16. import org.springframework.stereotype.Component;
    17. import org.springframework.web.context.request.RequestContextHolder;
    18. import org.springframework.web.context.request.ServletRequestAttributes;
    19. import javax.servlet.http.Cookie;
    20. import javax.servlet.http.HttpServletRequest;
    21. import javax.servlet.http.HttpServletResponse;
    22. import java.lang.reflect.Method;
    23. import java.time.LocalDateTime;
    24. /**
    25. * 日志记录
    26. *
    27. */
    28. @Aspect
    29. @Component
    30. public class RequestLogAspect {
    31. @Autowired(required = false)
    32. RequestLogMapper requestLogMapper;
    33. /**
    34. * execution是给指定区域,切入点
    35. * annotation是让特定类使用注解,切入点
    36. */
    37. @Pointcut("execution(* com.example.springboot.controller.*.*(..)) || " +
    38. "@annotation(com.example.springboot.annotation.RequestLogAnnotation)")
    39. public void logPointCut() {
    40. }
    41. // 请求的开始处理时间(不同类型)
    42. Long startTime = null;
    43. LocalDateTime startDate;
    44. @Before("logPointCut()")
    45. public void beforeRequest() {
    46. startTime = System.currentTimeMillis();
    47. startDate = LocalDateTime.now();
    48. }
    49. @AfterReturning(value = "logPointCut()", returning = "result")
    50. public void saveLog(JoinPoint joinPoint, Object result) {
    51. // 获取请求头
    52. ServletRequestAttributes requestAttributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
    53. HttpServletRequest request = requestAttributes.getRequest();
    54. HttpServletResponse response = requestAttributes.getResponse();
    55. //从切面织入点处通过反射机制获取织入点处的方法
    56. MethodSignature signature = (MethodSignature) joinPoint.getSignature();
    57. //获取切入点所在的方法
    58. Method method = signature.getMethod();
    59. // 初始化日志表的实体类
    60. RequestLog requestLog = new RequestLog();
    61. //获取操作
    62. RequestLogAnnotation requestLogAnnotation = method.getAnnotation(RequestLogAnnotation.class);
    63. // // 获取@SystemLogAnnotation(value = "用户登录")中的注解value
    64. // if (systemLogAnnotation != null) {
    65. // String value = systemLogAnnotation.value();
    66. // requestLog.setSName(value);
    67. // }
    68. // 获取cookies
    69. Cookie[] cookies = request.getCookies();
    70. if (cookies != null) {
    71. // 获取token
    72. for(Cookie cookie : cookies){
    73. if(cookie.getName().equals("token")){
    74. requestLog.setToken(cookie.getValue());
    75. }
    76. }
    77. // 获取id
    78. String id = CookieUtil.getid(cookies);
    79. if (id != "" | id != null) {
    80. requestLog.setId(id);
    81. }
    82. }
    83. // 区分get和post获取参数
    84. String params = "{}";
    85. if (request.getMethod().equals("GET")) {
    86. params = JSONObject.toJSONString(request.getParameterMap());
    87. } else if (request.getMethod().equals("POST")) {
    88. params = JSONUtil.toJsonStr(joinPoint.getArgs());
    89. }
    90. // 获取用户真实ip地址
    91. String ip;
    92. if (request.getHeader("x-forwarded-for") == null) {
    93. ip = request.getRemoteAddr();
    94. } else {
    95. ip = request.getHeader("x-forwarded-for");
    96. }
    97. if (ip.equals("0:0:0:0:0:0:0:1")) {
    98. ip = "127.0.0.1";
    99. }
    100. // 用户Ip
    101. requestLog.setIp(ip);
    102. // 接口请求类型
    103. requestLog.setMethod(request.getMethod());
    104. // 请求参数(区分get和post)
    105. requestLog.setParams(params);
    106. // 请求接口路径
    107. requestLog.setUrl(request.getRequestURI().toString());
    108. // 返回结果
    109. requestLog.setResult(JSONObject.toJSONString(result));
    110. // 请求开始时间
    111. requestLog.setStartDate(startDate);
    112. // 请求结束时间
    113. requestLog.setEndDate(LocalDateTime.now());
    114. // 请求共计时间(ms)
    115. requestLog.setResponseTime(String.valueOf(System.currentTimeMillis() - startTime));
    116. // 保存日志到mysql
    117. requestLogMapper.insert(requestLog);
    118. }
    119. }

    5、前面在@Pointcut切入点那里定义了controller目录下所有的都自动切入aop,这里可以不使用@RequestLogAnnotation,其他文件夹下的特殊类或方法可以在对应位置添加注解@RequestLogAnnotation

    1. @RequestLogAnnotation(value = "获取上传记录")
    2. @GetMapping("/getlist")
    3. public Result getlist (@RequestParam(required = false) String id) {
    4. if (id == null) {
    5. return Result.success(404, "参数缺失");
    6. } else {
    7. List page = uploadLogService.getlist(id);
    8. return Result.success(200, page.toString());
    9. }
    10. }

    效果如下

     二、解决Interceptor拦截器中引用mapper和service为null

    背景

    当我们项目中同时使用Interceptor拦截器和aop日志拦截时,被Interceptor拦截器所拦截的请求不会通过aop日志保存到数据库(防止恶意爬虫)。

    但是项目如果需要记录这些被拦截的非法请求的话,目前暂时的解决方法是在Interceptor拦截器所拦截非法的请求之前再使用前面的RequestLogMapper再重新进行保存一次(只针对非法请求,因为合法请求会通过Aop日志拦截)。

    但这时候又出现了新的问题,在Interceptor创建时mapper和service还没来得及注入,会导致mapper和service引用为null,就需要在创建前先行赋值,如下:

    1. @Configuration
    2. public class InterceptorConfig implements WebMvcConfigurer {
    3. /**
    4. * 白名单
    5. */
    6. private static String[] WhiteList = {"/user/login", "/user/register"};
    7. /**
    8. * 解决在Token拦截器中无法使用mapper和service的情况(无Bean)
    9. * @return
    10. */
    11. @Bean
    12. public TokenInterceptor myTokenInterceptor () {
    13. return new TokenInterceptor();
    14. }
    15. /**
    16. * http请求拦截器
    17. * @param registry
    18. */
    19. @Override
    20. public void addInterceptors(InterceptorRegistry registry) {
    21. // 除excludePathPatterns内包含的接口,其他接口都要经过拦截,执行LogInterceptor()
    22. registry.addInterceptor(myTokenInterceptor())
    23. .excludePathPatterns(WhiteList);
    24. }
    25. }

  • 相关阅读:
    PCtoLCD2002 图片取模教程
    从零开始,开发一个 Web Office 套件(5):Mouse hover over text
    密集计算场景下的 JNI 实战
    全连接层是什么,有什么作用?
    【毕业设计】python+大数据构建疫情可视化分析系统
    质因数分解式
    从零开始,开发一个 Web Office 套件(11):支持中文输入法(or 其它使用输入法的语言)
    vue uniapp 实现点击获取坐标出现gif
    分布式锁-全面详解(学习总结---从入门到深化)
    【分布式】SpirngBoot 整合RabbitMQ、Exchagne模式、确认消费
  • 原文地址:https://blog.csdn.net/weixin_42966151/article/details/133614293