一、Springboot使用Aop保存接口请求日志到mysql
1、添加aop依赖
<!-- aop日志 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
2、新建接口保存数据库的实体类RequestLog.java
package com.example.springboot.entity;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import java.io.Serializable;
import java.time.LocalDateTime;
import lombok.Getter;
import lombok.Setter;
/**
* <p>
* 请求日志
* </p>
*
* @author Sca_jie
* @since 2023-09-28
*/
@Getter
@Setter
@TableName("request_log")
public class RequestLog implements Serializable {
private static final long serialVersionUID = 1L;
// 主键-自增
@TableId(value = "number", type = IdType.AUTO)
private Integer number;
// 用户账号
private String id;
// 携带token
private String token;
// 接口路径
private String url;
// 请求类型
private String method;
// 携带参数
private String params;
// ip地址
private String ip;
// 结果
private String result;
// 接口发起时间
private LocalDateTime startDate;
// 接口结束时间
private LocalDateTime endDate;
// 响应耗时
private String responseTime;
}
3、新建一个注解RequestLogAnnotation.java
package com.example.springboot.annotation;
import java.lang.annotation.*;
/**
* 请求记录日志注解
*/
@Target({ElementType.TYPE, ElementType.METHOD}) //注解放置的目标位置,METHOD是可注解在方法级别上
@Retention(RetentionPolicy.RUNTIME) //注解在哪个阶段执行
@Documented
public @interface RequestLogAnnotation {
String value() default "";
}
4、(核心)新建aop面切类RequestLogAspect.java拦截请求并保存日志
package com.example.springboot.common;
import cn.hutool.core.net.NetUtil;
import cn.hutool.json.JSONUtil;
import com.alibaba.fastjson.JSONObject;
import com.example.springboot.annotation.RequestLogAnnotation;
import com.example.springboot.entity.RequestLog;
import com.example.springboot.mapper.RequestLogMapper;
import com.example.springboot.utils.CookieUtil;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.AfterReturning;
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 org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.lang.reflect.Method;
import java.time.LocalDateTime;
/**
* 日志记录
*
*/
@Aspect
@Component
public class RequestLogAspect {
@Autowired(required = false)
RequestLogMapper requestLogMapper;
@Pointcut("@annotation(com.example.springboot.annotation.RequestLogAnnotation)")
public void logPointCut() {
}
// 请求的开始处理时间(不同类型)
Long startTime = null;
LocalDateTime startDate;
@Before("logPointCut()")
public void beforeRequest() {
startTime = System.currentTimeMillis();
startDate = LocalDateTime.now();
}
@AfterReturning(value = "logPointCut()", returning = "result")
public void saveLog(JoinPoint joinPoint, Object result) {
// 获取请求头
ServletRequestAttributes requestAttributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
HttpServletRequest request = requestAttributes.getRequest();
HttpServletResponse response = requestAttributes.getResponse();
//从切面织入点处通过反射机制获取织入点处的方法
MethodSignature signature = (MethodSignature) joinPoint.getSignature();
//获取切入点所在的方法
Method method = signature.getMethod();
// 初始化日志表的实体类
RequestLog requestLog = new RequestLog();
//获取操作
RequestLogAnnotation requestLogAnnotation = method.getAnnotation(RequestLogAnnotation.class);
// // 获取@SystemLogAnnotation(value = "用户登录")中的注解value
// if (systemLogAnnotation != null) {
// String value = systemLogAnnotation.value();
// requestLog.setSName(value);
// }
// 获取cookies
Cookie[] cookies = request.getCookies();
if (cookies != null) {
// 获取token
for(Cookie cookie : cookies){
if(cookie.getName().equals("token")){
requestLog.setToken(cookie.getValue());
}
}
// 获取id
String id = CookieUtil.getid(cookies);
if (id != "" | id != null) {
requestLog.setId(id);
}
}
// 区分get和post获取参数
String params = "{}";
if (request.getMethod().equals("GET")) {
params = JSONObject.toJSONString(request.getParameterMap());
} else if (request.getMethod().equals("POST")) {
params = JSONUtil.toJsonStr(joinPoint.getArgs());
}
// 获取用户真实ip地址
String ip;
if (request.getHeader("x-forwarded-for") == null) {
ip = request.getRemoteAddr();
} else {
ip = request.getHeader("x-forwarded-for");
}
if (ip.equals("0:0:0:0:0:0:0:1")) {
ip = "127.0.0.1";
}
// 用户Ip
requestLog.setIp(ip);
// 接口请求类型
requestLog.setMethod(request.getMethod());
// 请求参数(区分get和post)
requestLog.setParams(params);
// 请求接口路径
requestLog.setUrl(request.getRequestURI().toString());
// 返回结果
requestLog.setResult(JSONObject.toJSONString(result));
// 请求开始时间
requestLog.setStartDate(startDate);
// 请求结束时间
requestLog.setEndDate(LocalDateTime.now());
// 请求共计时间(ms)
requestLog.setResponseTime(String.valueOf(System.currentTimeMillis() - startTime));
// 保存日志到mysql
requestLogMapper.insert(requestLog);
}
}
5、在对应接口添加注解@RequestLogAnnotation
@RequestLogAnnotation(value = "获取上传记录")
@GetMapping("/getlist")
public Result getlist (@RequestParam(required = false) String id) {
if (id == null) {
return Result.success(404, "参数缺失");
} else {
List<UploadLog> page = uploadLogService.getlist(id);
return Result.success(200, page.toString());
}
}
效果如下
二、解决Interceptor拦截器中引用mapper和service为null
背景
当我们项目中同时使用Interceptor拦截器和aop日志拦截时,被Interceptor拦截器所拦截的请求不会通过aop日志保存到数据库(防止恶意爬虫)。
但是项目如果需要记录这些被拦截的非法请求的话,目前暂时的解决方法是在Interceptor拦截器所拦截非法的请求之前再使用前面的RequestLogMapper再重新进行保存一次(只针对非法请求,因为合法请求会通过Aop日志拦截)。
但这时候又出现了新的问题,在Interceptor创建时mapper和service还没来得及注入,会导致mapper和service引用为null,就需要在创建前先行赋值,如下:
@Configuration
public class InterceptorConfig implements WebMvcConfigurer {
/**
* 白名单
*/
private static String[] WhiteList = {"/user/login", "/user/register"};
/**
* 解决在Token拦截器中无法使用mapper和service的情况(无Bean)
* @return
*/
@Bean
public TokenInterceptor myTokenInterceptor () {
return new TokenInterceptor();
}
/**
* http请求拦截器
* @param registry
*/
@Override
public void addInterceptors(InterceptorRegistry registry) {
// 除excludePathPatterns内包含的接口,其他接口都要经过拦截,执行LogInterceptor()
registry.addInterceptor(myTokenInterceptor())
.excludePathPatterns(WhiteList);
}
}