需求:实现一个记录接口请求情况的日志。
运行效果
先展示一下最终使用方法和效果:
接口上添加注解:
@MyLog(url = "/api/students/{count}")
url处根据接口不同进行修改
@RestController
@RequestMapping("/api/students")
public class StudentController {
@Autowired
private StudentService studentService;
@GetMapping("/{count}")
@TakeCount(time = 1)
@MyLog(url = "/api/students/{count}")
public ResponseEntity<List<Student>> getStudents(@PathVariable int count) {
// 参数校验,确保count在合理范围内
if (count < 1 || count > 1000) {
return ResponseEntity.badRequest().body(Collections.emptyList());
}
List<Student> students = studentService.getStudents(count);
return ResponseEntity.ok(students);
}
}
数据库内容:
1.新建数据库
sql:
CREATE TABLE log (
id INT AUTO_INCREMENT PRIMARY KEY,
method_name VARCHAR(255) NOT NULL,
class_name VARCHAR(255) NOT NULL,
params TEXT,
return_value TEXT,
url VARCHAR(255) NOT NULL,
duration BIGINT NOT NULL
);
2.引入依赖
这里我直接把我完整的pom.xml放上来了,如果大家想学习更详细的知识可以去看看其他大佬的文章跟着一点点来,我这里仅供参考。(对!没错就是写了屎山懒得提取精华了orz)
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>3.2.5</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.example</groupId>
<artifactId>security</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>security</name>
<description>security</description>
<properties>
<java.version>17</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>3.0.3</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<scope>runtime</scope>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>com.mysql</groupId>
<artifactId>mysql-connector-j</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter-test</artifactId>
<version>3.0.3</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency>
<groupId>io.micrometer</groupId>
<artifactId>micrometer-core</artifactId>
<version>1.12.5</version>
</dependency>
<!--引入AOP依赖-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
<!--引入Redis依赖-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-all</artifactId>
<version>5.7.8</version>
</dependency>
<dependency>
<groupId>org.redisson</groupId>
<artifactId>redisson</artifactId>
<version>3.5.0</version>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.70</version>
</dependency>
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus</artifactId>
<version>3.4.2</version>
</dependency>
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.4.2</version>
</dependency>
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-generator</artifactId>
<version>3.2.0</version>
</dependency>
<dependency>
<groupId>org.apache.tomcat.embed</groupId>
<artifactId>tomcat-embed-core</artifactId>
<version>9.0.75</version>
</dependency>
<dependency>
<groupId>org.apache.tomcat.embed</groupId>
<artifactId>tomcat-embed-core</artifactId>
<version>10.1.20</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<configuration>
<excludes>
<exclude>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</exclude>
</excludes>
</configuration>
</plugin>
</plugins>
</build>
</project>
3.创建Log实体
model/Log.java:
package com.api.security.model;
import jakarta.persistence.Entity;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.GenerationType;
import jakarta.persistence.Id;
@Entity
public class Log {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String methodName;
private String className;
private String params;
private String returnValue;
private String url;
private Long duration;
// Getters and setters
public void setClassName(String className) {
this.className = className;
}
public String getClassName() {
return className;
}
public void setId(Long id) {
this.id = id;
}
public Long getId() {
return id;
}
public void setMethodName(String methodName) {
this.methodName = methodName;
}
public String getMethodName() {
return methodName;
}
public void setParams(String params) {
this.params = params;
}
public String getParams() {
return params;
}
public void setReturnValue(String returnValue) {
this.returnValue = returnValue;
}
public String getReturnValue() {
return returnValue;
}
public void setDuration(Long duration) {
this.duration = duration;
}
public Long getDuration() {
return duration;
}
public void setUrl(String url) {
this.url = url;
}
public String getUrl() {
return url;
}
}
4.自定义注解
项目中新建一个annotation包,包下新建MyLog
annotation/MyLog代码:
package com.api.security.annotation;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface MyLog {
String value() default "";
String url() default "";
long duration() default 0L;
}
5.AOP切面
项目中新建一个aop包
aop/MyLogAspect.java:
package com.api.security.aop;
import com.api.security.annotation.MyLog;
import com.api.security.annotation.TakeCount;
import com.api.security.model.Log;
import com.api.security.repository.LogRepository;
import jakarta.servlet.http.HttpServletRequest;
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.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 java.util.Arrays;
import java.util.concurrent.TimeUnit;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import static java.lang.System.currentTimeMillis;
@Aspect
@Component
public class MyLogAspect {
private final LogRepository logRepository;
private ThreadLocal<Long> startTime = new ThreadLocal<>();
@Autowired
public MyLogAspect(LogRepository logRepository) {
this.logRepository = logRepository;
}
@Pointcut("@annotation(com.api.security.annotation.MyLog)")
public void logPointcut() {
}
@Before("@annotation(myLog)")
public void doBefore(MyLog myLog) {
//记录接口的开始时
startTime.set(System.currentTimeMillis());
}
@AfterReturning(pointcut = "logPointcut()", returning = "result")
public void logAfterReturning(JoinPoint joinPoint, Object result) {
// 获取方法参数、方法名、返回结果等信息
String methodName = joinPoint.getSignature().getName();
String className = joinPoint.getTarget().getClass().getName();
String params = Arrays.toString(joinPoint.getArgs());
String returnValue = (result != null) ? result.toString() : "void";
// 获取请求 URL 和方法执行时间
ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
jakarta.servlet.http.HttpServletRequest request = attributes.getRequest();
String url = request.getRequestURL().toString();
long duration = System.currentTimeMillis()- startTime.get();
// 构造日志对象并保存到数据库
Log log = new Log();
log.setMethodName(methodName);
log.setClassName(className);
log.setParams(params);
log.setReturnValue(returnValue);
log.setUrl(url);
log.setDuration(duration);
logRepository.save(log);
}
}
6.操作数据库
这里继承的是JpaRepository,因为只需简单的插入操作所以这个超级方便,调用他的save()方法就好。
repository/LogRepository.java:
package com.api.security.repository;
import com.api.security.model.Log;
import org.springframework.data.jpa.repository.JpaRepository;
public interface LogRepository extends JpaRepository<Log, Long> {
}
7.使用注解:
controller文件下在你想记录的接口上方引入@MyLog
这个是我个人的例子,还涉及项目其他逻辑,只用来测试的话大家直接写一个没有操作的/test接口就行。
@RestController
@RequestMapping("/api/students")
public class StudentController {
@Autowired
private StudentService studentService;
@GetMapping("/{count}")
@TakeCount(time = 1)
@MyLog(url = "/api/students/{count}")
public ResponseEntity<List<Student>> getStudents(@PathVariable int count) {
// 参数校验,确保count在合理范围内
if (count < 1 || count > 1000) {
return ResponseEntity.badRequest().body(Collections.emptyList());
}
List<Student> students = studentService.getStudents(count);
return ResponseEntity.ok(students);
}
}
8.其他问题
运行效果如1所示
相关的原理大家可以参考其他大佬的文章,我这里只是提供一个快速的解决方案~有什么问题可以评论区留言,大家一起讨论共同进步呀!