1. 准备工作
项目环境:jdk8+springboot2.6.13+mysql8
1.1 MySQL表
/*Navicat Premium Data TransferSource Server : localhostSource Server Type : MySQLSource Server Version : 50730Source Host : 127.0.0.1:3306Source Schema : testTarget Server Type : MySQLTarget Server Version : 50730File Encoding : 65001Date: 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: rootpassword: xxxxdriver-class-name: com.mysql.cj.jdbc.Driverurl: jdbc:mysql://localhost:3306/test?useUnicode=true&characterEncoding=utf-8&serverTimezone=GMT%2B8&useSSL=falsemvc: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;@Overridepublic 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(","));// 登录IPString 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]