创建数据库表
sql
CREATE TABLE `sys_operation_log` (
`log_id` bigint NOT NULL AUTO_INCREMENT COMMENT '日志ID',
`operation_type` varchar(20) NOT NULL COMMENT '操作类型',
`operation_module` varchar(50) NOT NULL COMMENT '操作模块',
`operation_desc` varchar(200) DEFAULT NULL COMMENT '操作描述',
`request_method` varchar(10) DEFAULT NULL COMMENT '请求方法',
`request_url` varchar(500) DEFAULT NULL COMMENT '请求URL',
`request_params` text DEFAULT NULL COMMENT '请求参数',
`response_result` text DEFAULT NULL COMMENT '响应结果',
`user_id` varchar(36) DEFAULT NULL COMMENT '操作用户ID',
`user_name` varchar(50) DEFAULT NULL COMMENT '操作用户名',
`user_ip` varchar(50) DEFAULT NULL COMMENT '用户IP',
`user_agent` varchar(500) DEFAULT NULL COMMENT '用户代理',
`operation_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '操作时间',
`execution_time` bigint DEFAULT NULL COMMENT '执行耗时(ms)',
`status` tinyint DEFAULT 1 COMMENT '操作状态(0失败 1成功)',
`error_msg` text DEFAULT NULL COMMENT '错误信息',
PRIMARY KEY (`log_id`),
KEY `idx_user_id` (`user_id`),
KEY `idx_operation_time` (`operation_time`),
KEY `idx_module_type` (`operation_module`, `operation_type`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='系统操作日志表';
添加pom.xml依赖
XML
<dependencies>
<!-- Spring Boot Web -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- MyBatis-Plus -->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>最新版本</version>
</dependency>
<!-- AOP -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
<!-- Lombok -->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
</dependencies>
创建实体类
java
import com.baomidou.mybatisplus.annotation.*;
import java.util.Date;
@Data
@TableName("sys_operation_log")
public class SysOperationLog {
@TableId(value = "log_id", type = IdType.AUTO)
private Long logId;
private String operationType;
private String operationModule;
private String operationDesc;
private String requestMethod;
private String requestUrl;
private String requestParams;
private String responseResult;
private String userId;
private String userName;
private String userIp;
private String userAgent;
@TableField(fill = FieldFill.INSERT)
private Date operationTime;
private Long executionTime;
private Integer status;
private String errorMsg;
}
添加mapper
java
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
public interface SysOperationLogMapper extends BaseMapper<SysOperationLog> {
}
创建自定义注解
java
import java.lang.annotation.*;
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Loggable {
String operationType() default "";
String module() default "";
String description() default "";
}
创建AOP切面
java
import com.fasterxml.jackson.databind.ObjectMapper;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.stereotype.Component;
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.util.Date;
@Aspect
@Component
@Slf4j
public class OperationLogAspect {
private final SysOperationLogMapper logMapper;
private final ObjectMapper objectMapper;
public OperationLogAspect(SysOperationLogMapper logMapper, ObjectMapper objectMapper) {
this.logMapper = logMapper;
this.objectMapper = objectMapper;
}
@Around("@annotation(com.yourpackage.annotation.Loggable)")
public Object logOperation(ProceedingJoinPoint joinPoint) throws Throwable {
long startTime = System.currentTimeMillis();
SysOperationLog operationLog = new SysOperationLog();
Object result = null;
try {
// 获取注解信息
MethodSignature signature = (MethodSignature) joinPoint.getSignature();
Method method = signature.getMethod();
Loggable loggable = method.getAnnotation(Loggable.class);
// 填充日志基本信息
operationLog.setOperationType(loggable.operationType());
operationLog.setOperationModule(loggable.module());
operationLog.setOperationDesc(loggable.description());
operationLog.setOperationTime(new Date());
// 获取请求信息
ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
if (attributes != null) {
HttpServletRequest request = attributes.getRequest();
operationLog.setRequestMethod(request.getMethod());
operationLog.setRequestUrl(request.getRequestURI());
operationLog.setUserIp(request.getRemoteAddr());
operationLog.setUserAgent(request.getHeader("User-Agent"));
}
// 获取用户信息(根据你的系统实现)
operationLog.setUserId("当前用户ID");
operationLog.setUserName("当前用户名");
// 获取请求参数
Object[] args = joinPoint.getArgs();
operationLog.setRequestParams(objectMapper.writeValueAsString(args));
// 执行目标方法
result = joinPoint.proceed();
// 记录响应结果
operationLog.setResponseResult(objectMapper.writeValueAsString(result));
operationLog.setStatus(1);
} catch (Exception e) {
operationLog.setStatus(0);
operationLog.setErrorMsg(e.getMessage());
throw e;
} finally {
long endTime = System.currentTimeMillis();
operationLog.setExecutionTime(endTime - startTime);
// 异步保存日志(建议使用异步方式)
logMapper.insert(operationLog);
}
return result;
}
}
这是一个获取用户id和用户名的示例方法
java
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.stereotype.Component;
@Component
public class UserInfoUtils {
// 根据你的安全框架实现获取用户信息
public String getCurrentUserId() {
// 示例:从Spring Security获取
return SecurityContextHolder.getContext().getAuthentication().getName();
}
public String getCurrentUsername() {
// 实现获取用户名逻辑
return "用户名";
}
}
使用
java
@RestController
@RequestMapping("/api")
public class DemoController {
@Loggable(operationType = "QUERY", module = "用户管理", description = "查询用户列表")
@GetMapping("/users")
public List<User> getUsers() {
// 业务逻辑
}
}
为了防止数据库越来越大,可以删除一年前的数据,我们可以通过数据库层面实现
- 创建存储过程
sql
DELIMITER $$
CREATE PROCEDURE clean_old_logs()
BEGIN
DECLARE batch_size INT DEFAULT 1000;
DECLARE deleted_rows INT DEFAULT 1;
WHILE deleted_rows > 0 DO
DELETE FROM sys_operation_log
WHERE operation_time < NOW() - INTERVAL 1 YEAR
LIMIT batch_size;
SET deleted_rows = ROW_COUNT();
COMMIT;
DO SLEEP(0.5); -- 防止锁表太久
END WHILE;
END$$
DELIMITER ;
- 创建定时事件,一天一次
sql
CREATE EVENT execute_clean_procedure
ON SCHEDULE EVERY 1 DAY
STARTS CURRENT_TIMESTAMP + INTERVAL 1 DAY
DO
BEGIN
CALL clean_old_logs();
END