1. 准备工作
项目环境:jdk8+springboot2.6.13+mysql8
1.1 MySQL表
/*
Navicat Premium Data Transfer
Source Server : localhost
Source Server Type : MySQL
Source Server Version : 50730
Source Host : 127.0.0.1:3306
Source Schema : test
Target Server Type : MySQL
Target Server Version : 50730
File Encoding : 65001
Date: 04/07/2024 17:50:35
*/
SET NAMES utf8mb4;
SET FOREIGN_KEY_CHECKS = 0;
-- ----------------------------
-- Table structure for operation_log
-- ----------------------------
DROP TABLE IF EXISTS `operation_log`;
CREATE TABLE `operation_log` (
`id` bigint(20) NOT NULL AUTO_INCREMENT,
`module_name` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL,
`operation_name` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL,
`method_type` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL,
`url` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL,
`args` text CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL,
`ip_addr` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL,
`create_time` datetime NOT NULL,
PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 6 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci ROW_FORMAT = Dynamic;
-- ----------------------------
-- Table structure for tb_seller
-- ----------------------------
DROP TABLE IF EXISTS `tb_seller`;
CREATE TABLE `tb_seller` (
`sellerid` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL,
`name` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL,
`nickname` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL,
`password` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL,
`status` int(11) NOT NULL,
`address` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL,
`createtime` datetime NOT NULL,
PRIMARY KEY (`sellerid`) USING BTREE,
UNIQUE INDEX `name`(`name`, `status`, `address`) USING BTREE
) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci ROW_FORMAT = Dynamic;
SET FOREIGN_KEY_CHECKS = 1;
1.2 项目目录结构
新建一个springboot项目,项目目录结构如下:
1.3 maven依赖
pom.xml
<?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.spring</groupId>
<artifactId>demo</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>spring-demo</name>
<description>spring-demo</description>
<properties>
<java.version>1.8</java.version>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<spring-boot.version>2.6.13</spring-boot.version>
</properties>
<dependencies>
<!-- web -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- lombok -->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<!-- test -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<!-- MySQL JDBC -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.13</version>
</dependency>
<!-- mybatis-plus -->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.5.4</version>
</dependency>
<!-- hutool -->
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-all</artifactId>
<version>5.6.6</version>
</dependency>
<!-- spring-aop -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</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>
</project>
1.4 项目配置文件
配置文件application.yml
spring:
datasource:
username: root
password: xxxx
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://localhost:3306/test?useUnicode=true&characterEncoding=utf-8&serverTimezone=GMT%2B8&useSSL=false
mvc:
pathmatch:
matching-strategy: ant_path_matcher # 路径匹配
mybatis-plus:
configuration:
#配置该类sql只会在控制台打印,不会输出到日志文件
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
1.5 domain层
TbSeller
package com.spring.demo.domain.po;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableName;
import com.baomidou.mybatisplus.annotation.IdType;
import java.time.LocalDateTime;
import com.baomidou.mybatisplus.annotation.TableId;
import java.io.Serializable;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.ToString;
import lombok.experimental.Accessors;
/**
* <p>
*
* </p>
*
* @author Sakura
* @since 2024-07-03
*/
@Data
@EqualsAndHashCode(callSuper = false)
@Accessors(chain = true)
@TableName("tb_seller")
@ToString
public class TbSeller implements Serializable {
private static final long serialVersionUID = 1L;
@TableId(value = "sellerid", type = IdType.AUTO)
private String sellerid;
private String name;
private String nickname;
private String password;
private Integer status;
private String address;
private LocalDateTime createtime;
}
OperationLog
package com.spring.demo.domain.po;
import com.baomidou.mybatisplus.annotation.TableName;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import java.time.LocalDateTime;
import java.io.Serializable;
import lombok.Builder;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.ToString;
import lombok.experimental.Accessors;
/**
* <p>
* 操作日志实体类,用于记录每个请求的详细信息。
* </p>
*
* @author Sakura
* @since 2024-07-03
*/
@Data
@EqualsAndHashCode(callSuper = false)
@Accessors(chain = true)
@Builder
@TableName("operation_log")
@ToString
public class OperationLog implements Serializable {
private static final long serialVersionUID = 1L;
/**
* 日志记录的主键ID,自动递增。
*/
@TableId(value = "id", type = IdType.AUTO)
private Long id;
/**
* 模块名称,表示在哪个模块执行了操作。
*/
private String moduleName;
/**
* 操作名称,表示执行了什么操作。
*/
private String operationName;
/**
* 请求方法类型(GET, POST, PUT, DELETE等)。
*/
private String methodType;
/**
* 请求的URL地址。
*/
private String url;
/**
* 请求的参数,记录请求时传递的参数信息。
*/
private String args;
/**
* 请求的IP地址,记录请求的来源IP。
*/
private String ipAddr;
/**
* 创建时间,记录请求的时间。
*/
private LocalDateTime createTime;
}
1.6 controller层
TbSellerController
/**
* <p>
* 前端控制器
* </p>
*
* @author Sakura
* @since 2024-07-03
*/
@RestController
@RequestMapping("/tb-seller")
@RequiredArgsConstructor
public class TbSellerController {
private final ITbSellerService tbSellerService;
/**
* 获取供应商信息
*/
@GetMapping("/getById/{id}")
@MyLog(moduleName = "供应商模块", operationName = "获取供应商信息")
public TbSeller getTbSellerById(@PathVariable String id) {
return tbSellerService.getTbSellerById(id);
}
}
1.7 service层
ITbSellerService
package com.spring.demo.service;
import com.spring.demo.domain.po.TbSeller;
import com.baomidou.mybatisplus.extension.service.IService;
/**
* <p>
* 服务类
* </p>
*
* @author Sakura
* @since 2024-07-03
*/
public interface ITbSellerService extends IService<TbSeller> {
TbSeller getTbSellerById(String id);
}
TbSellerServiceImpl
package com.spring.demo.service.impl;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.spring.demo.domain.po.OperationLog;
import com.spring.demo.domain.po.TbSeller;
import com.spring.demo.mapper.TbSellerMapper;
import com.spring.demo.service.IOperationLogService;
import com.spring.demo.service.ITbSellerService;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Service;
import java.util.List;
/**
* <p>
* 服务实现类
* </p>
*
* @author Sakura
* @since 2024-07-03
*/
@Service
@Slf4j
@RequiredArgsConstructor
public class TbSellerServiceImpl extends ServiceImpl<TbSellerMapper, TbSeller> implements ITbSellerService {
private final TbSellerMapper tbSellerMapper;
@Override
public TbSeller getTbSellerById(String id) {
return this.lambdaQuery().eq(TbSeller::getSellerid,id).one();
}
}
IOperationLogService
package com.spring.demo.service;
import com.spring.demo.domain.po.OperationLog;
import com.baomidou.mybatisplus.extension.service.IService;
/**
* <p>
* 服务类
* </p>
*
* @author Sakura
* @since 2024-07-03
*/
public interface IOperationLogService extends IService<OperationLog> {
}
OperationLogServiceImpl
package com.spring.demo.service.impl;
import com.spring.demo.domain.po.OperationLog;
import com.spring.demo.mapper.OperationLogMapper;
import com.spring.demo.service.IOperationLogService;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.spring.demo.service.ITbSellerService;
import lombok.RequiredArgsConstructor;
import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Service;
/**
* <p>
* 服务实现类
* </p>
*
* @author Sakura
* @since 2024-07-03
*/
@Service
@RequiredArgsConstructor
public class OperationLogServiceImpl extends ServiceImpl<OperationLogMapper, OperationLog> implements IOperationLogService {
}
1.8 mapper层
TbSellerMapper
import com.spring.demo.domain.po.TbSeller;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
/**
* <p>
* Mapper 接口
* </p>
*
* @author Sakura
* @since 2024-07-03
*/
public interface TbSellerMapper extends BaseMapper<TbSeller> {
/**
* 根据sellerid查询
*/
TbSeller getTbSellerById(String sellerid);
}
OperationLogMapper
package com.spring.demo.mapper;
import com.spring.demo.domain.po.OperationLog;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
/**
* <p>
* Mapper 接口
* </p>
*
* @author Sakura
* @since 2024-07-03
*/
public interface OperationLogMapper extends BaseMapper<OperationLog> {
}
1.9 util包
package com.spring.demo.util;
import javax.servlet.http.HttpServletRequest;
/**
* @author:
* @date 2024-07-03 17:23
*/
public class IpUtil {
/**
* 获取ip地址
*/
public static String getIpAddr(HttpServletRequest request) {
String ip = request.getHeader("x-forwarded-for");
if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
ip = request.getHeader("Proxy-Client-IP");
}
if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
ip = request.getHeader("WL-Proxy-Client-IP");
}
if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
ip = request.getRemoteAddr();
}
return "0:0:0:0:0:0:0:1".equals(ip) ? "127.0.0.1" : ip;
}
}
1.10 启动类
package com.spring.demo;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.context.annotation.EnableAspectJAutoProxy;
@SpringBootApplication
@MapperScan(basePackages = "com.spring.demo.mapper")
public class SpringDemoApplication {
public static void main(String[] args) {
ConfigurableApplicationContext run = SpringApplication.run(SpringDemoApplication.class, args);
System.out.println();
}
}
2. AOP配置
2.1 开启AOP代理
在启动类上添加注解@EnableAspectJAutoProxy
开启AOP代理。
2.2 定义日志注解
在aop包下新建注解MyLog
。
package com.spring.demo.aop;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* 自定义注解:实现登录日志记录
*/
@Retention(RetentionPolicy.RUNTIME) // 运行时生效
@Target(ElementType.METHOD) // 作用在方法上
public @interface MyLog {
/**
* 模块名称
*/
String moduleName() default "";
/**
* 操作名称
*/
String operationName() default "";
}
2.3 定义切面类
在aop包下定义切面类
package com.spring.demo.aop;
import com.spring.demo.domain.po.OperationLog;
import com.spring.demo.service.IOperationLogService;
import com.spring.demo.util.IpUtil;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.Signature;
import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestAttributes;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import javax.servlet.http.HttpServletRequest;
import java.lang.reflect.Method;
import java.time.LocalDateTime;
import java.util.Arrays;
import java.util.stream.Collectors;
/**
* 日志切面类
*
* @author:
* @date 2024-07-03 16:30
*/
@Slf4j
@Component
@Aspect
@RequiredArgsConstructor
public class MyAspect {
private final IOperationLogService operationLogService;
@AfterReturning("@annotation(com.spring.demo.aop.MyLog)")
public void afterReturningMethod(JoinPoint joinPoint) {
// 获取被增强类和方法的信息
Signature signature = joinPoint.getSignature();
MethodSignature methodSignature = (MethodSignature) signature;
// 获取被增强的方法对象
Method method = methodSignature.getMethod();
String moduleName = ""; // 模块名称
String operationName = ""; // 操作名称
// 从方法中解析注解
MyLog myLog = null;
if (method != null) {
myLog = method.getAnnotation(MyLog.class);
moduleName = myLog.moduleName();
operationName = myLog.operationName();
}
// 通过工具类获取Request对象
RequestAttributes reqa = RequestContextHolder.getRequestAttributes();
ServletRequestAttributes sra = (ServletRequestAttributes) reqa;
HttpServletRequest request = sra.getRequest();
// 请求方式
String methodType = request.getMethod();
// 请求地址
String url = request.getRequestURI().toString();
// 请求参数
String args = Arrays.stream(joinPoint.getArgs()).map(Object::toString).collect(Collectors.joining(","));
// 登录IP
String ipAddr = IpUtil.getIpAddr(request);
// 请求时间
LocalDateTime createTime = LocalDateTime.now();
// 打印日志
log.info("moduleName:{}, operationName:{}, methodType:{}, url:{}, args:{}, ipAddr:{}, createTime:{}",
moduleName, operationName, methodType, url, args, ipAddr, createTime);
// 保存日志
OperationLog operationLog = OperationLog.builder()
.moduleName(moduleName)
.operationName(operationName)
.methodType(methodType)
.url(url)
.args(args)
.ipAddr(ipAddr)
.createTime(createTime)
.build();
operationLogService.save(operationLog);
}
}
2.4 日志注解使用
修改TbSellerController,在方法上添加MyLog
注解
/**
* 获取供应商信息
*/
@GetMapping("/getById/{id}")
@MyLog(moduleName = "供应商模块", operationName = "获取供应商信息")
public TbSeller getTbSellerById(@PathVariable String id) {
return tbSellerService.getTbSellerById(id);
}
3.测试
启动springboot项目,访问路径:http://localhost:8080/tb-seller/getById/baidu
控制台输出如下:
JDBC Connection [HikariProxyConnection@130007367 wrapping com.mysql.cj.jdbc.ConnectionImpl@116246bc] will not be managed by Spring
==> Preparing: SELECT s.sellerid AS sellerid, s.name AS name, s.nickname AS nickname, s.password AS password, s.status AS status, s.address AS address, s.createtime AS createtime, s.uid AS uid FROM tb_seller s where s.sellerid = ?;
==> Parameters: baidu(String)
<== Columns: sellerid, name, nickname, password, status, address, createtime, uid
<== Row: baidu, 百度科技有限公司, 百度小店, e10adc3949ba59abbe56e057f20f883e, 1, 北京市, 2088-01-01 12:00:00, 1
<== Total: 1
Closing non transactional SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@118dd810]
2024-07-04 18:25:54.559 INFO 251072 --- [nio-8080-exec-1] com.spring.demo.aop.MyAspect : moduleName:供应商模块, operationName:获取供应商信息, methodType:GET, url:/tb-seller/getById/baidu, args:baidu, ipAddr:127.0.0.1, createTime:2024-07-04T18:25:54.559
Creating a new SqlSession
SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@1416af2f] was not registered for synchronization because synchronization is not active
JDBC Connection [HikariProxyConnection@136495746 wrapping com.mysql.cj.jdbc.ConnectionImpl@116246bc] will not be managed by Spring
==> Preparing: INSERT INTO operation_log ( module_name, operation_name, method_type, url, args, ip_addr, create_time ) VALUES ( ?, ?, ?, ?, ?, ?, ? )
==> Parameters: 供应商模块(String), 获取供应商信息(String), GET(String), /tb-seller/getById/baidu(String), baidu(String), 127.0.0.1(String), 2024-07-04T18:25:54.559(LocalDateTime)
<== Updates: 1
Closing non transactional SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@1416af2f]