若依框架中实现用户行为日志记录方案

2025-06发布2次浏览

若依框架(RuoYi)是一个基于Spring Boot和Spring Cloud的快速开发平台,其功能丰富且易于扩展。在实际项目中,记录用户行为日志是一项非常重要的功能,它可以帮助我们追踪用户的操作历史、排查问题以及分析系统使用情况。本文将详细介绍如何在若依框架中实现用户行为日志记录方案。


一、用户行为日志的核心需求

在设计用户行为日志功能时,需要明确以下几点核心需求:

  1. 记录哪些信息:包括但不限于用户名、操作时间、操作模块、操作方法、请求参数、响应结果等。
  2. 存储方式:可以选择数据库、文件或分布式存储系统。
  3. 性能优化:日志记录不应影响主业务流程的性能,因此需要异步处理。
  4. 安全性:避免敏感数据泄露,对日志中的敏感信息进行脱敏处理。

二、实现步骤

1. 数据库表设计

首先,在数据库中创建一张用于存储用户行为日志的表。以下是表结构的设计示例:

CREATE TABLE sys_oper_log (
    oper_id          BIGINT(20)   NOT NULL AUTO_INCREMENT COMMENT '日志主键',
    title            VARCHAR(50)  DEFAULT '' COMMENT '模块标题',
    business_type    INT(2)       DEFAULT 0 COMMENT '业务类型(0其他 1新增 2修改 3删除)',
    method           VARCHAR(100) DEFAULT '' COMMENT '方法名称',
    request_method   VARCHAR(10)  DEFAULT '' COMMENT '请求方式 GET/POST',
    operator_type    INT(1)       DEFAULT 0 COMMENT '操作类别(0其它 1后台用户 2手机端用户)',
    oper_name        VARCHAR(50)  DEFAULT '' COMMENT '操作人员',
    dept_name        VARCHAR(50)  DEFAULT '' COMMENT '部门名称',
    oper_url         VARCHAR(255) DEFAULT '' COMMENT '请求URL',
    oper_ip          VARCHAR(128) DEFAULT '' COMMENT '主机地址',
    oper_location    VARCHAR(255) DEFAULT '' COMMENT '操作地点',
    oper_param       VARCHAR(2000) DEFAULT '' COMMENT '请求参数',
    json_result      VARCHAR(2000) DEFAULT '' COMMENT '返回参数',
    status           INT(1)       DEFAULT 0 COMMENT '操作状态(0正常 1异常)',
    error_msg        VARCHAR(2000) DEFAULT '' COMMENT '错误消息',
    oper_time        DATETIME     DEFAULT NULL COMMENT '操作时间',
    PRIMARY KEY (oper_id)
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8mb4 COMMENT='操作日志记录';

2. 配置AOP切面

为了实现非侵入式的日志记录,可以使用Spring AOP技术拦截目标方法并记录日志。

(1)定义注解

创建一个自定义注解@OperLog,用于标记需要记录日志的方法。

import java.lang.annotation.*;

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface OperLog {
    String title() default ""; // 日志标题
    int businessType() default 0; // 业务类型
}
(2)编写切面类

通过切面类拦截带有@OperLog注解的方法,并记录日志。

import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.stereotype.Component;

@Aspect
@Component
public class LogAspect {

    @Pointcut("@annotation(com.example.annotation.OperLog)")
    public void logPointCut() {}

    @Around("logPointCut()")
    public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
        long startTime = System.currentTimeMillis();
        Object result = joinPoint.proceed();
        long endTime = System.currentTimeMillis();

        // 记录日志逻辑
        saveOperLog(joinPoint, result, endTime - startTime);
        return result;
    }

    private void saveOperLog(ProceedingJoinPoint joinPoint, Object result, long time) {
        try {
            // 获取方法签名、参数、返回值等信息
            MethodSignature signature = (MethodSignature) joinPoint.getSignature();
            Method method = signature.getMethod();

            // 获取注解信息
            OperLog operLog = method.getAnnotation(OperLog.class);

            // 构造日志对象
            SysOperLog sysOperLog = new SysOperLog();
            sysOperLog.setTitle(operLog.title());
            sysOperLog.setBusinessType(operLog.businessType());
            sysOperLog.setMethod(signature.getName());
            sysOperLog.setRequestMethod(joinPoint.getArgs()[0].toString());
            sysOperLog.setOperName(getCurrentUserName()); // 获取当前用户
            sysOperLog.setOperIp(getRequestIp()); // 获取IP地址
            sysOperLog.setOperParam(JSON.toJSONString(joinPoint.getArgs()));
            sysOperLog.setJsonResult(JSON.toJSONString(result));
            sysOperLog.setStatus(0); // 默认成功
            sysOperLog.setOperTime(new Date());

            // 异常处理
            if (result instanceof Exception) {
                sysOperLog.setStatus(1);
                sysOperLog.setErrorMsg(ExceptionUtils.getMessage((Exception) result));
            }

            // 异步保存日志
            logService.save(sysOperLog);
        } catch (Exception e) {
            // 记录日志失败处理
        }
    }

    private String getCurrentUserName() {
        // 获取当前登录用户
        return SecurityUtils.getUsername();
    }

    private String getRequestIp() {
        // 获取请求IP
        HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
        return IpUtils.getIpAddr(request);
    }
}

3. 异步日志存储

为了避免日志记录影响主业务流程,建议采用异步方式存储日志。可以通过Spring的@Async注解实现。

(1)启用异步支持

在Spring Boot配置类上添加@EnableAsync注解。

import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.annotation.EnableAsync;

@Configuration
@EnableAsync
public class AsyncConfig {
}
(2)异步保存日志

在日志服务类中添加异步方法。

import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Service;

@Service
public class LogService {

    @Async
    public void save(SysOperLog operLog) {
        // 调用DAO层保存日志
        operLogMapper.insertOperLog(operLog);
    }
}

4. 敏感信息脱敏

在保存日志之前,应对请求参数中的敏感信息进行脱敏处理。例如,手机号码、身份证号等。

private String desensitize(String param) {
    if (param.contains("password")) {
        param = param.replaceAll("(?<=password=)[^&]*", "******");
    }
    return param;
}

三、测试与验证

完成上述实现后,可以在控制器中添加测试接口,并标注@OperLog注解。

@RestController
@RequestMapping("/test")
public class TestController {

    @GetMapping("/hello")
    @OperLog(title = "测试接口", businessType = 1)
    public String hello() {
        return "Hello World!";
    }
}

访问该接口后,检查数据库中的sys_oper_log表是否正确记录了操作日志。


四、总结

本文详细介绍了在若依框架中实现用户行为日志记录的完整方案,包括数据库表设计、AOP切面实现、异步日志存储以及敏感信息脱敏处理等内容。通过这种方式,我们可以高效地记录和管理用户行为日志,为系统的运维和分析提供有力支持。