RuoYi AOP应用:日志切面编程实战

RuoYi AOP应用:日志切面编程实战

【免费下载链接】RuoYi 🎉 基于SpringBoot的权限管理系统 易读易懂、界面简洁美观。 核心技术采用Spring、MyBatis、Shiro没有任何其它重度依赖。直接运行即可用 【免费下载链接】RuoYi 项目地址: https://gitcode.com/yangzongzhuan/RuoYi

引言:为什么需要操作日志切面?

在企业级应用开发中,操作日志记录是保障系统安全性和可追溯性的关键环节。传统的手动日志记录方式存在诸多痛点:

  • 代码重复:每个业务方法都需要编写相似的日志记录代码
  • 维护困难:日志逻辑分散在各个业务方法中,修改困难
  • 侵入性强:业务代码与日志代码高度耦合
  • 遗漏风险:开发人员可能忘记添加日志记录

RuoYi框架通过AOP(Aspect-Oriented Programming,面向切面编程)技术,优雅地解决了这些问题。本文将深入解析RuoYi的日志切面实现原理,并提供实战应用指南。

AOP核心概念与RuoYi实现架构

AOP基本概念

mermaid

RuoYi日志切面核心组件

组件名称作用位置
LogAspect核心切面类,处理日志记录逻辑ruoyi-framework/aspectj
@Log自定义注解,标记需要记录日志的方法ruoyi-common/annotation
BusinessType业务类型枚举,定义操作类型ruoyi-common/enums
SysOperLog操作日志实体类ruoyi-system/domain

深入解析LogAspect实现原理

1. 切面配置与线程安全

@Aspect
@Component
public class LogAspect {
    private static final Logger log = LoggerFactory.getLogger(LogAspect.class);
    
    // 排除敏感属性字段
    public static final String[] EXCLUDE_PROPERTIES = { 
        "password", "oldPassword", "newPassword", "confirmPassword" 
    };
    
    // 计算操作消耗时间(线程安全)
    private static final ThreadLocal<Long> TIME_THREADLOCAL = 
        new NamedThreadLocal<Long>("Cost Time");
}

2. 切入点表达式与注解驱动

RuoYi使用注解驱动的方式定义切入点,通过@annotation(controllerLog)表达式匹配所有被@Log注解标记的方法:

@Before(value = "@annotation(controllerLog)")
public void doBefore(JoinPoint joinPoint, Log controllerLog) {
    TIME_THREADLOCAL.set(System.currentTimeMillis());
}

3. 完整的日志处理流程

mermaid

@Log注解详解与配置选项

注解定义

@Target({ ElementType.PARAMETER, ElementType.METHOD })
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Log {
    String title() default "";                    // 模块名称
    BusinessType businessType() default BusinessType.OTHER; // 业务类型
    OperatorType operatorType() default OperatorType.MANAGE; // 操作人类别
    boolean isSaveRequestData() default true;     // 是否保存请求参数
    boolean isSaveResponseData() default true;    // 是否保存响应参数
    String[] excludeParamNames() default {};      // 排除的参数名
}

业务类型枚举

RuoYi定义了丰富的业务操作类型:

业务类型描述
OTHER0其他操作
INSERT1新增操作
UPDATE2修改操作
DELETE3删除操作
GRANT4授权操作
EXPORT5导出操作
IMPORT6导入操作
FORCE7强退操作
GENCODE8生成代码
CLEAN9清空操作

实战应用:自定义日志切面

1. 基础用法示例

在Controller方法上添加@Log注解即可启用日志记录:

@RestController
@RequestMapping("/system/user")
public class SysUserController {
    
    @Log(title = "用户管理", businessType = BusinessType.INSERT)
    @PostMapping
    public AjaxResult add(@RequestBody SysUser user) {
        // 业务逻辑
        return AjaxResult.success();
    }
    
    @Log(title = "用户管理", businessType = BusinessType.UPDATE)
    @PutMapping
    public AjaxResult update(@RequestBody SysUser user) {
        // 业务逻辑
        return AjaxResult.success();
    }
}

2. 高级配置示例

@Log(
    title = "敏感操作",
    businessType = BusinessType.UPDATE,
    isSaveRequestData = true,
    isSaveResponseData = false,
    excludeParamNames = {"password", "token"}  // 排除敏感参数
)
@PutMapping("/sensitive")
public AjaxResult sensitiveOperation(@RequestBody SensitiveData data) {
    // 敏感操作逻辑
    return AjaxResult.success("操作成功");
}

3. 自定义业务类型扩展

如果需要扩展新的业务类型,只需在BusinessType枚举中添加:

public enum BusinessType {
    // 现有类型...
    
    /**
     * 审核操作
     */
    AUDIT,
    
    /**
     * 批量操作
     */
    BATCH
}

日志数据处理与安全考虑

敏感信息过滤机制

RuoYi提供了完善的敏感信息过滤机制:

// 默认排除的敏感字段
public static final String[] EXCLUDE_PROPERTIES = { 
    "password", "oldPassword", "newPassword", "confirmPassword" 
};

// 自定义排除字段
public PropertyPreFilters.MySimplePropertyPreFilter excludePropertyPreFilter(
    String[] excludeParamNames) {
    return new PropertyPreFilters().addFilter()
        .addExcludes(ArrayUtils.addAll(EXCLUDE_PROPERTIES, excludeParamNames));
}

文件上传参数处理

为了避免记录文件内容,切面会自动过滤文件类型参数:

public boolean isFilterObject(final Object o) {
    return o instanceof MultipartFile || 
           o instanceof HttpServletRequest || 
           o instanceof HttpServletResponse ||
           o instanceof BindingResult;
}

性能优化与异步处理

异步日志记录

RuoYi采用异步方式记录日志,避免影响主业务流程性能:

// 保存数据库
AsyncManager.me().execute(AsyncFactory.recordOper(operLog));

线程局部变量优化

使用ThreadLocal存储时间信息,确保线程安全:

private static final ThreadLocal<Long> TIME_THREADLOCAL = 
    new NamedThreadLocal<Long>("Cost Time");

// 方法开始时记录
TIME_THREADLOCAL.set(System.currentTimeMillis());

// 方法结束时计算耗时
operLog.setCostTime(System.currentTimeMillis() - TIME_THREADLOCAL.get());

// 最终清理
TIME_THREADLOCAL.remove();

常见问题与解决方案

1. 日志不生效问题排查

问题现象可能原因解决方案
注解添加但无日志切面未扫描到检查组件扫描配置
部分方法无日志切入点表达式不匹配检查注解位置是否正确
异步日志未保存线程池配置问题检查异步线程池配置

2. 性能优化建议

  • 合理设置参数记录:非必要不记录大量请求响应数据
  • 控制日志长度:使用StringUtils.substring限制字段长度
  • 异步处理:确保日志记录不影响主业务流程

3. 安全注意事项

  • 敏感信息过滤:务必配置排除敏感参数
  • 日志权限控制:操作日志应只有管理员可查看
  • 日志存储安全:数据库日志表需要适当的安全措施

扩展应用场景

1. 自定义日志切面

如果需要更复杂的日志逻辑,可以创建自定义切面:

@Aspect
@Component
public class CustomLogAspect {
    
    @AfterReturning(
        pointcut = "@annotation(com.ruoyi.common.annotation.Log)",
        returning = "result"
    )
    public void afterReturning(JoinPoint joinPoint, Object result) {
        // 自定义后置处理逻辑
    }
}

2. 多数据源日志记录

对于多租户系统,可以扩展支持多数据源日志记录:

@Aspect
@Component
public class MultiTenantLogAspect extends LogAspect {
    
    @Override
    protected void handleLog(JoinPoint joinPoint, Log controllerLog, 
                           Exception e, Object jsonResult) {
        // 获取当前租户信息
        String tenantId = TenantContext.getCurrentTenant();
        
        // 调用父类处理并添加租户信息
        super.handleLog(joinPoint, controllerLog, e, jsonResult);
        
        // 额外的租户相关日志处理
    }
}

总结与最佳实践

RuoYi的日志切面编程提供了强大而灵活的操作日志记录能力,通过本文的深入解析,我们可以总结出以下最佳实践:

  1. 合理使用注解:根据业务重要性选择是否记录日志
  2. 安全第一:务必排除敏感参数,防止信息泄露
  3. 性能优化:利用异步处理避免影响主业务流程
  4. 可扩展性:通过继承和组合方式扩展切面功能
  5. 监控告警:结合日志监控实现异常操作实时告警

通过掌握RuoYi的AOP日志切面技术,开发者可以构建出更加健壮、安全、可维护的企业级应用系统。


进一步学习建议

  • 深入学习Spring AOP原理和实现机制
  • 研究更多AOP应用场景,如事务管理、权限控制等
  • 探索分布式系统中的日志追踪方案
  • 了解日志分析与监控平台集成

掌握AOP日志切面编程,将为你的系统开发和维护工作带来极大的便利和效率提升。

【免费下载链接】RuoYi 🎉 基于SpringBoot的权限管理系统 易读易懂、界面简洁美观。 核心技术采用Spring、MyBatis、Shiro没有任何其它重度依赖。直接运行即可用 【免费下载链接】RuoYi 项目地址: https://gitcode.com/yangzongzhuan/RuoYi

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值