Files
novalon-manage-system/docs/plans/2026-04-03-operation-log-design.md
T
张翔 03a9eb73c7 docs: add operation log feature design document
- Define annotation-driven operation log recording approach
- Specify key business operations to be logged
- Outline testing and deployment strategy
- Address performance and security considerations
2026-04-03 19:26:16 +08:00

8.2 KiB
Raw Blame History

操作日志记录功能设计文档

日期: 2026-04-03
作者: 张翔
版本: 1.0

1. 概述

1.1 背景

当前系统的Dashboard操作日志一直显示0,原因是缺少操作日志记录功能。虽然数据库表、服务层和API都已就绪,但没有自动记录用户操作的机制。

1.2 目标

实现一个基于注解的操作日志记录功能,自动记录关键业务操作,为系统审计和问题追踪提供数据支持。

1.3 范围

只记录关键业务操作,包括:

  • 用户管理:创建、更新、删除用户、修改密码、分配角色
  • 角色管理:创建、更新、删除角色、分配权限
  • 菜单管理:创建、更新、删除菜单
  • 系统配置:创建、更新、删除配置
  • 数据字典:创建、更新、删除字典
  • 公告管理:创建、更新、删除公告

2. 架构设计

2.1 整体架构

采用AOP切面 + 注解驱动的架构:

用户请求 → Handler方法(带@OperationLog注解)
    ↓
OperationLogAspect拦截
    ↓
记录开始时间、获取请求参数
    ↓
执行业务方法
    ↓
记录结束时间、获取返回结果
    ↓
异步保存操作日志到数据库
    ↓
返回结果给用户

2.2 核心组件

  1. @OperationLog注解:标记需要记录日志的方法
  2. OperationLogAspect切面:拦截注解方法,自动记录操作日志
  3. OperationLogService服务:已有的服务层,负责保存日志到数据库
  4. 异步处理:使用Reactor的异步机制,不阻塞主业务流程

2.3 关键设计点

  • 响应式编程:使用Reactor的Mono/Flux,与现有WebFlux架构保持一致
  • 异步记录:日志记录不影响主业务流程性能
  • 错误容错:日志记录失败不影响业务方法执行
  • 自动获取上下文:从SecurityContext获取当前用户,从ServerWebExchange获取IP地址

3. 详细设计

3.1 注解定义

package cn.novalon.manage.sys.audit;

import java.lang.annotation.*;

/**
 * 操作日志注解
 * 标记需要记录操作日志的方法
 * 
 * @author 张翔
 * @date 2026-04-03
 */
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface OperationLog {
    
    /**
     * 操作名称
     * 例如:"创建用户"、"删除角色"
     */
    String operation();
    
    /**
     * 模块名称
     * 例如:"用户管理"、"角色管理"
     */
    String module();
}

3.2 切面实现

@Aspect
@Component
public class OperationLogAspect {
    
    private final IOperationLogService logService;
    private final ObjectMapper objectMapper;
    
    @Around("@annotation(operationLog)")
    public Object around(ProceedingJoinPoint point, OperationLog operationLog) throws Throwable {
        long startTime = System.currentTimeMillis();
        
        // 1. 获取请求信息
        String username = getCurrentUsername();
        String ip = getCurrentIp();
        String method = point.getSignature().toShortString();
        String params = serializeParams(point.getArgs());
        
        // 2. 执行业务方法
        Object result = null;
        String status = "0";  // 0-成功, 1-失败
        String errorMsg = null;
        
        try {
            result = point.proceed();
            
            // 3. 处理响应式结果
            if (result instanceof Mono) {
                return ((Mono<?>) result)
                    .doOnSuccess(res -> {
                        long duration = System.currentTimeMillis() - startTime;
                        saveLogAsync(operationLog, username, ip, method, 
                                   params, res, duration, "0", null);
                    })
                    .doOnError(error -> {
                        long duration = System.currentTimeMillis() - startTime;
                        saveLogAsync(operationLog, username, ip, method, 
                                   params, null, duration, "1", error.getMessage());
                    });
            }
            
            return result;
        } catch (Throwable error) {
            status = "1";
            errorMsg = error.getMessage();
            throw error;
        } finally {
            if (!(result instanceof Mono)) {
                long duration = System.currentTimeMillis() - startTime;
                saveLogAsync(operationLog, username, ip, method, 
                           params, result, duration, status, errorMsg);
            }
        }
    }
    
    private void saveLogAsync(OperationLog operationLog, String username, 
                             String ip, String method, String params, 
                             Object result, long duration, String status, 
                             String errorMsg) {
        // 异步保存日志,不阻塞主流程
        Mono.fromRunnable(() -> {
            cn.novalon.manage.sys.core.domain.OperationLog log = 
                new cn.novalon.manage.sys.core.domain.OperationLog();
            log.setUsername(username);
            log.setOperation(operationLog.module() + " - " + operationLog.operation());
            log.setMethod(method);
            log.setParams(params);
            log.setResult(serializeResult(result));
            log.setIp(ip);
            log.setDuration(duration);
            log.setStatus(status);
            log.setErrorMsg(errorMsg);
            
            logService.save(log).subscribe();
        }).subscribeOn(Schedulers.boundedElastic()).subscribe();
    }
}

3.3 需要记录的操作

用户管理模块

  • createUser() - 创建用户
  • updateUser() - 更新用户
  • deleteUser() - 删除用户
  • changePassword() - 修改密码
  • assignRoles() - 分配角色

角色管理模块

  • createRole() - 创建角色
  • updateRole() - 更新角色
  • deleteRole() - 删除角色
  • assignPermissionsToRole() - 分配权限

菜单管理模块

  • createMenu() - 创建菜单
  • updateMenu() - 更新菜单
  • deleteMenu() - 删除菜单

其他模块

  • 系统配置:创建、更新、删除配置
  • 数据字典:创建、更新、删除字典
  • 公告管理:创建、更新、删除公告

4. 测试策略

4.1 单元测试

OperationLogAspectTest:测试切面的核心逻辑

  • 测试成功场景:方法执行成功,日志正确记录
  • 测试失败场景:方法抛出异常,日志记录错误信息
  • 测试响应式场景:Mono返回值的处理
  • 测试上下文获取:用户、IP等信息的正确获取

4.2 集成测试

OperationLogIntegrationTest:测试完整的日志记录流程

  • 调用带注解的API接口
  • 验证日志是否正确保存到数据库
  • 验证日志内容的完整性

4.3 E2E测试

在现有E2E测试中验证:

  • 执行用户管理操作后,检查Dashboard操作日志数量是否增加
  • 验证操作日志显示是否正确

5. 部署计划

5.1 阶段1:开发与测试(当前)

  1. 创建@OperationLog注解
  2. 实现OperationLogAspect切面
  3. 编写单元测试和集成测试
  4. 在关键Handler方法上添加注解

5.2 阶段2:验证

  1. 运行所有测试,确保功能正常
  2. 手动测试Dashboard操作日志显示
  3. 验证日志记录不影响系统性能

5.3 阶段3:上线

  1. 提交代码到Git
  2. 更新文档
  3. 部署到开发环境验证

6. 性能考虑

  • 异步保存:日志保存使用异步方式,不阻塞主业务流程
  • 索引优化:数据库表已有索引(created_at, username
  • 日志清理:建议后续添加定时任务清理历史日志(保留最近3个月)

7. 风险与缓解

风险 影响 缓解措施
日志记录失败影响业务 使用异步保存,失败时只记录错误日志,不影响业务流程
日志量过大影响性能 只记录关键操作,使用异步保存,定期清理历史日志
敏感信息泄露 参数序列化时排除敏感字段(如password)

8. 后续优化

  1. 日志查询优化:添加更多查询条件(时间范围、操作类型等)
  2. 日志导出功能:支持导出操作日志为Excel
  3. 日志统计分析:统计用户操作频率、操作类型分布等
  4. 日志清理任务:定时清理历史日志,保留最近3个月数据