feat(注解式日志): 使用SpringAOP实现注解式日志

我们在企业级的开发中,必不可少的是对日志的记录,
实现有很多种方式,常见的就是基于AOP+注解进行保存,
但是考虑到程序的流畅和效率,我们可以使用异步进行保存,
小编最近在spring和springboot源码中看到有很多的监听处理贯穿前后:这就是著名的观察者模式!!
This commit is contained in:
Zerroi 2024-04-15 20:20:07 +08:00
commit 5116aac809
21 changed files with 591 additions and 0 deletions

33
.gitignore vendored Normal file
View File

@ -0,0 +1,33 @@
HELP.md
target/
!.mvn/wrapper/maven-wrapper.jar
!**/src/main/**/target/
!**/src/test/**/target/
### STS ###
.apt_generated
.classpath
.factorypath
.project
.settings
.springBeans
.sts4-cache
### IntelliJ IDEA ###
.idea
*.iws
*.iml
*.ipr
### NetBeans ###
/nbproject/private/
/nbbuild/
/dist/
/nbdist/
/.nb-gradle/
build/
!**/src/main/**/build/
!**/src/test/**/build/
### VS Code ###
.vscode/

103
pom.xml Normal file
View File

@ -0,0 +1,103 @@
<?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>
<groupId>com.zerroi</groupId>
<artifactId>AnnotationLogs</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>AnnotationLogs</name>
<description>AnnotationLogs</description>
<properties>
<java.version>17</java.version>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<spring-boot.version>3.0.2</spring-boot.version>
</properties>
<dependencies>
<!--jdbc-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jdbc</artifactId>
</dependency>
<!-- mybatis-plus -->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.5.6</version>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid-spring-boot-starter</artifactId>
<version>1.1.16</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</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>
</dependencies>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-dependencies</artifactId>
<version>${spring-boot.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.8.1</version>
<configuration>
<source>17</source>
<target>17</target>
<encoding>UTF-8</encoding>
</configuration>
</plugin>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<version>${spring-boot.version}</version>
<configuration>
<mainClass>com.zerroi.annotationlogs.AnnotationLogsApplication</mainClass>
<skip>true</skip>
</configuration>
<executions>
<execution>
<id>repackage</id>
<goals>
<goal>repackage</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>

View File

@ -0,0 +1,15 @@
package com.zerroi.annotationlogs;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
@MapperScan("com.zerroi.annotationlogs.mapper")
public class AnnotationLogsApplication {
public static void main(String[] args) {
SpringApplication.run(AnnotationLogsApplication.class, args);
}
}

View File

@ -0,0 +1,25 @@
package com.zerroi.annotationlogs.annotations;
import com.zerroi.annotationlogs.constant.BusinessTypeEnum;
import java.lang.annotation.*;
/**
* 自定义操作日志记录注解
* @author zerroi
*/
@Target(ElementType.METHOD) // 注解只能用于方法
@Retention(RetentionPolicy.RUNTIME) // 修饰注解的生命周期
@Documented
public @interface Log {
String value() default "";
/**
* 模块
*/
String title() default "测试模块";
/**
* 功能
*/
BusinessTypeEnum businessType() default BusinessTypeEnum.OTHER;
}

View File

@ -0,0 +1,64 @@
package com.zerroi.annotationlogs.aspect;
import com.zerroi.annotationlogs.annotations.Log;
import com.zerroi.annotationlogs.config.EventPubListener;
import com.zerroi.annotationlogs.domain.SysLog;
import com.zerroi.annotationlogs.utils.IpUtils;
import jakarta.annotation.Resource;
import jakarta.servlet.http.HttpServletRequest;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import java.time.LocalDateTime;
@Aspect
@Component
public class SysLogAspect {
private final Logger logger = LoggerFactory.getLogger(SysLogAspect.class);
@Resource
private EventPubListener eventPubListener;
/**
* 以注解所标注的方法作为切入点
*/
@Pointcut("@annotation(com.zerroi.annotationlogs.annotations.Log)")
public void sysLog() {}
/**
* 在切点之后织入
*/
@After("sysLog()")
public void doAfter(JoinPoint joinPoint) {
Log log = ((MethodSignature) joinPoint.getSignature()).getMethod()
.getAnnotation(Log.class);
ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder
.getRequestAttributes();
HttpServletRequest request = attributes.getRequest();
String method = request.getMethod();
String url = request.getRequestURL().toString();
String ip = IpUtils.getIpAddr(request);
SysLog sysLog = new SysLog();
sysLog.setBusinessType(log.businessType().getCode());
sysLog.setTitle(log.title());
sysLog.setRequestMethod(method);
sysLog.setOperIp(ip);
sysLog.setOperUrl(url);
// 从登录中token获取登录人员信息即可
sysLog.setOperName("我是测试人员");
sysLog.setOperTime(LocalDateTime.now());
// 发布消息
eventPubListener.pushListener(sysLog);
logger.info("=======日志发送成功,内容:{}",sysLog);
}
}

View File

@ -0,0 +1,18 @@
package com.zerroi.annotationlogs.config;
import com.zerroi.annotationlogs.domain.SysLog;
import jakarta.annotation.Resource;
import org.springframework.context.ApplicationContext;
import org.springframework.stereotype.Component;
@Component
public class EventPubListener {
@Resource
private ApplicationContext applicationContext;
// 事件发布方法
public void pushListener(SysLog sysLogEvent) {
applicationContext.publishEvent(sysLogEvent);
}
}

View File

@ -0,0 +1,27 @@
package com.zerroi.annotationlogs.config;
import com.zerroi.annotationlogs.domain.SysLog;
import com.zerroi.annotationlogs.service.SysService;
import jakarta.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.event.EventListener;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Component;
@Slf4j
@Component
public class MyEventListener {
@Resource
private SysService sysService;
// 开启异步
@Async
// 开启监听
@EventListener(SysLog.class)
public void saveSysLog(SysLog event) {
log.info("=====即将异步保存到数据库======");
sysService.saveLog(event);
}
}

View File

@ -0,0 +1,41 @@
package com.zerroi.annotationlogs.constant;
public enum BusinessTypeEnum {
/**
* 其它
*/
OTHER(0,"其它"),
/**
* 新增
*/
INSERT(1,"新增"),
/**
* 修改
*/
UPDATE(2,"修改"),
/**
* 删除
*/
DELETE(3,"删除");
private Integer code;
private String message;
BusinessTypeEnum(Integer code, String message) {
this.code = code;
this.message = message;
}
public Integer getCode() {
return code;
}
public String getMessage() {
return message;
}
}

View File

@ -0,0 +1,26 @@
package com.zerroi.annotationlogs.controller;
import com.zerroi.annotationlogs.annotations.Log;
import com.zerroi.annotationlogs.constant.BusinessTypeEnum;
import com.zerroi.annotationlogs.pojo.User;
import com.zerroi.annotationlogs.service.UserService;
import jakarta.annotation.Resource;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
@RequestMapping("/user")
public class UserController {
@Resource
private UserService userService;
@GetMapping("/getById/{id}")
@Log(title = "根据id获取用户信息", businessType = BusinessTypeEnum.OTHER)
public User getUser(@PathVariable("id") Integer id) {
return userService.getById(id);
}
}

View File

@ -0,0 +1,66 @@
package com.zerroi.annotationlogs.domain;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import com.fasterxml.jackson.annotation.JsonFormat;
import lombok.Data;
import java.io.Serial;
import java.io.Serializable;
import java.time.LocalDateTime;
/**
* 操作日志记录表 sys_log
*
*/
@Data
@TableName("sys_log")
public class SysLog implements Serializable {
@Serial
private static final long serialVersionUID = 1L;
/**
* 日志主键
*/
@TableId
private Long id;
/**
* 操作模块
*/
private String title;
/**
* 业务类型0其它 1新增 2修改 3删除
*/
private Integer businessType;
/**
* 请求方式
*/
private String requestMethod;
/**
* 操作人员
*/
private String operName;
/**
* 请求url
*/
private String operUrl;
/**
* 操作地址
*/
private String operIp;
/**
* 操作时间
*/
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private LocalDateTime operTime;
}

View File

@ -0,0 +1,10 @@
package com.zerroi.annotationlogs.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.zerroi.annotationlogs.domain.SysLog;
import org.apache.ibatis.annotations.Mapper;
import org.mybatis.spring.annotation.MapperScan;
@Mapper
public interface SysMapper extends BaseMapper<SysLog> {
}

View File

@ -0,0 +1,7 @@
package com.zerroi.annotationlogs.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.zerroi.annotationlogs.pojo.User;
public interface UserMapper extends BaseMapper<User> {
}

View File

@ -0,0 +1,11 @@
package com.zerroi.annotationlogs.pojo;
import lombok.Data;
@Data
public class User {
private Integer id;
private String name;
private Integer age;
private String email;
}

View File

@ -0,0 +1,8 @@
package com.zerroi.annotationlogs.service;
import com.baomidou.mybatisplus.extension.service.IService;
import com.zerroi.annotationlogs.domain.SysLog;
public interface SysService {
int saveLog(SysLog sysLog);
}

View File

@ -0,0 +1,7 @@
package com.zerroi.annotationlogs.service;
import com.baomidou.mybatisplus.extension.service.IService;
import com.zerroi.annotationlogs.pojo.User;
public interface UserService extends IService<User> {
}

View File

@ -0,0 +1,21 @@
package com.zerroi.annotationlogs.service.impl;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.zerroi.annotationlogs.domain.SysLog;
import com.zerroi.annotationlogs.mapper.SysMapper;
import com.zerroi.annotationlogs.service.SysService;
import jakarta.annotation.Resource;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
@Service
public class SysServiceImpl implements SysService {
@Autowired
private SysMapper sysMapper;
@Override
public int saveLog(SysLog sysLog) {
return sysMapper.insert(sysLog);
}
}

View File

@ -0,0 +1,11 @@
package com.zerroi.annotationlogs.service.impl;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.zerroi.annotationlogs.mapper.UserMapper;
import com.zerroi.annotationlogs.pojo.User;
import com.zerroi.annotationlogs.service.UserService;
import org.springframework.stereotype.Service;
@Service
public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements UserService {
}

View File

@ -0,0 +1,68 @@
package com.zerroi.annotationlogs.utils;
import com.baomidou.mybatisplus.core.toolkit.StringUtils;
import jakarta.servlet.http.HttpServletRequest;
public class IpUtils {
/**
* 获取客户端IP
*
* @param request 请求对象
* @return IP地址
*/
public static String getIpAddr(HttpServletRequest request) {
if (request == null) {
return "unknown";
}
String ip = request.getHeader("x-forwarded-for");
if (ip == null || ip.isEmpty() || "unknown".equalsIgnoreCase(ip)) {
ip = request.getHeader("Proxy-Client-IP");
}
if (ip == null || ip.isEmpty() || "unknown".equalsIgnoreCase(ip)) {
ip = request.getHeader("X-Forwarded-For");
}
if (ip == null || ip.isEmpty() || "unknown".equalsIgnoreCase(ip)) {
ip = request.getHeader("WL-Proxy-Client-IP");
}
if (ip == null || ip.isEmpty() || "unknown".equalsIgnoreCase(ip)) {
ip = request.getHeader("X-Real-IP");
}
if (ip == null || ip.isEmpty() || "unknown".equalsIgnoreCase(ip)) {
ip = request.getRemoteAddr();
}
return "0:0:0:0:0:0:0:1".equals(ip) ? "127.0.0.1" : getMultistageReverseProxyIp(ip);
}
/**
* 从多级反向代理中获得第一个非unknown IP地址
*
* @param ip 获得的IP地址
* @return 第一个非unknown IP地址
*/
public static String getMultistageReverseProxyIp(String ip) {
// 多级反向代理检测
if (ip != null && ip.indexOf(",") > 0) {
final String[] ips = ip.trim().split(",");
for (String subIp : ips) {
if (!isUnknown(subIp)) {
ip = subIp;
break;
}
}
}
return ip;
}
/**
* 检测给定字符串是否为未知多用于检测HTTP请求相关
*
* @param checkString 被检测的字符串
* @return 是否未知
*/
public static boolean isUnknown(String checkString) {
return StringUtils.isBlank(checkString) || "unknown".equalsIgnoreCase(checkString);
}
}

View File

@ -0,0 +1,11 @@
server:
port: 8088
spring:
datasource:
#使用阿里的Druid
type: com.alibaba.druid.pool.DruidDataSource
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://192.168.249.131:3306/test?serverTimezone=UTC
username: root
password: root

View File

@ -0,0 +1,6 @@
<html>
<body>
<h1>hello word!!!</h1>
<p>this is a html page</p>
</body>
</html>

View File

@ -0,0 +1,13 @@
package com.zerroi.annotationlogs;
import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest;
@SpringBootTest
class AnnotationLogsApplicationTests {
@Test
void contextLoads() {
}
}