操作日志使用说明.md 11 KB

操作日志使用说明

1. 功能概述

系统集成了完整的操作日志记录功能,自动记录用户的所有重要操作,包括:

  • 登录日志:用户登录、登出操作
  • 操作日志:用户的增删改查等业务操作

2. 数据库表结构

2.1 sys_log 表

CREATE TABLE `sys_log` (
  `id` BIGINT(20) NOT NULL AUTO_INCREMENT COMMENT '日志ID',
  `user_id` BIGINT(20) DEFAULT NULL COMMENT '用户ID',
  `username` VARCHAR(50) DEFAULT NULL COMMENT '用户名',
  `real_name` VARCHAR(50) DEFAULT NULL COMMENT '真实姓名',
  `log_type` VARCHAR(20) NOT NULL COMMENT '日志类型:登录日志、操作日志',
  `operate_type` VARCHAR(50) DEFAULT NULL COMMENT '操作类型:增删改查等',
  `detail` VARCHAR(500) DEFAULT NULL COMMENT '日志详情描述',
  `controller` VARCHAR(100) DEFAULT NULL COMMENT '控制器名称',
  `method` VARCHAR(100) DEFAULT NULL COMMENT '方法名称',
  `request_url` VARCHAR(255) DEFAULT NULL COMMENT '请求URL',
  `request_method` VARCHAR(10) DEFAULT NULL COMMENT '请求方式:GET/POST/PUT/DELETE',
  `request_params` TEXT DEFAULT NULL COMMENT '请求参数',
  `operate_ip` VARCHAR(50) DEFAULT NULL COMMENT '操作IP地址',
  `operate_location` VARCHAR(100) DEFAULT NULL COMMENT '操作地点',
  `operate_time` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '操作时间',
  `use_time` BIGINT(20) DEFAULT NULL COMMENT '执行时长(毫秒)',
  `status` TINYINT(1) DEFAULT 1 COMMENT '状态:0-失败,1-成功',
  `error_msg` TEXT DEFAULT NULL COMMENT '错误信息',
  `browser` VARCHAR(100) DEFAULT NULL COMMENT '浏览器',
  `os` VARCHAR(100) DEFAULT NULL COMMENT '操作系统',
  PRIMARY KEY (`id`),
  KEY `idx_user_id` (`user_id`),
  KEY `idx_operate_time` (`operate_time`),
  KEY `idx_log_type` (`log_type`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='系统操作日志表';

3. 使用方法

3.1 在Controller方法上添加注解

在需要记录日志的方法上添加 @SystemLogHandler 注解:

@RestController
@RequestMapping("/system/user")
public class UserController {

    /**
     * 新增用户
     * 注解格式:@SystemLogHandler("操作描述|操作类型")
     */
    @SystemLogHandler("新增用户|新增")
    @PostMapping("/add")
    public RestResult<?> add(@RequestBody SysUser user) {
        userService.save(user);
        return RestResult.ok("新增成功");
    }

    /**
     * 修改用户
     */
    @SystemLogHandler("修改用户|修改")
    @PutMapping("/edit")
    public RestResult<?> edit(@RequestBody SysUser user) {
        userService.updateById(user);
        return RestResult.ok("修改成功");
    }

    /**
     * 删除用户
     */
    @SystemLogHandler("删除用户|删除")
    @DeleteMapping("/remove/{id}")
    public RestResult<?> remove(@PathVariable Long id) {
        userService.removeById(id);
        return RestResult.ok("删除成功");
    }

    /**
     * 查询用户列表
     */
    @SystemLogHandler("查询用户列表|查询")
    @GetMapping("/list")
    public RestResult<?> list() {
        List<SysUser> users = userService.list();
        return RestResult.ok(users);
    }

    /**
     * 导出用户数据
     */
    @SystemLogHandler("导出用户数据|导出")
    @GetMapping("/export")
    public void export(HttpServletResponse response) {
        // 导出逻辑
    }
}

3.2 注解格式说明

@SystemLogHandler("操作描述|操作类型")
  • 操作描述:中文描述,说明这个操作做了什么(如:新增用户、修改角色、删除菜单)
  • 操作类型:操作分类(如:新增、修改、删除、查询、导出、登录、登出)
  • 使用 | 分隔两部分

特殊处理

  • 如果操作类型是 登录登出,日志类型自动标记为 登录日志
  • 其他操作类型,日志类型自动标记为 操作日志

3.3 示例:登录/登出

@RestController
@RequestMapping("/auth")
public class SysUserController {

    /**
     * 用户登录
     */
    @SystemLogHandler("用户登录|登录")
    @PostMapping("/login")
    public RestResult<?> login(@RequestBody LoginBody loginBody) {
        // 登录逻辑
    }

    /**
     * 用户登出
     */
    @SystemLogHandler("用户登出|登出")
    @PostMapping("/logout")
    public RestResult<?> logout() {
        // 登出逻辑
    }

    /**
     * 手机号验证码登录
     */
    @SystemLogHandler("手机号验证码登录|登录")
    @PostMapping("/loginByPhone")
    public RestResult<?> loginByPhone(@RequestBody LoginBody loginBody) {
        // 登录逻辑
    }
}

4. 自动记录的信息

4.1 用户信息(从SecurityContext获取)

  • 用户ID
  • 用户名
  • 真实姓名(昵称)

4.2 操作信息

  • 日志类型(登录日志/操作日志)
  • 操作类型(新增/修改/删除等)
  • 操作描述
  • 控制器名称
  • 方法名称

4.3 请求信息

  • 请求URL
  • 请求方式(GET/POST/PUT/DELETE)
  • 请求参数(自动过滤密码字段)

4.4 环境信息

  • 操作IP地址
  • 浏览器类型
  • 操作系统

4.5 执行信息

  • 操作时间
  • 执行时长(毫秒)
  • 执行状态(成功/失败)
  • 错误信息(如果失败)

5. 敏感信息处理

5.1 密码字段自动过滤

系统自动将请求参数中的 password 字段替换为 ******

// 原始参数
{
  "username": "admin",
  "password": "admin123"
}

// 记录到日志
{
  "username": "admin",
  "password": "******"
}

5.2 参数长度限制

请求参数超过2000字符时,自动截断并添加 ... 后缀。

6. 性能优化

6.1 异步保存

日志保存使用 @Async 异步执行,不影响主业务性能:

@Async
public void saveLogAsync(SysLog log) {
    save(log);
}

6.2 异常隔离

日志保存失败不会影响主业务:

try {
    saveLog(jp, request, startTime, exception);
} catch (Exception e) {
    logger.error("保存操作日志失败", e);
}

7. 查询日志

7.1 按日志类型查询

-- 查询登录日志
SELECT * FROM sys_log WHERE log_type = '登录日志' ORDER BY operate_time DESC;

-- 查询操作日志
SELECT * FROM sys_log WHERE log_type = '操作日志' ORDER BY operate_time DESC;

7.2 按用户查询

-- 查询某个用户的所有操作
SELECT * FROM sys_log WHERE user_id = 1 ORDER BY operate_time DESC;

-- 查询某个用户的登录记录
SELECT * FROM sys_log
WHERE user_id = 1 AND log_type = '登录日志'
ORDER BY operate_time DESC;

7.3 按时间范围查询

-- 查询今天的操作日志
SELECT * FROM sys_log
WHERE DATE(operate_time) = CURDATE()
ORDER BY operate_time DESC;

-- 查询最近7天的操作日志
SELECT * FROM sys_log
WHERE operate_time >= DATE_SUB(NOW(), INTERVAL 7 DAY)
ORDER BY operate_time DESC;

7.4 按操作类型查询

-- 查询所有删除操作
SELECT * FROM sys_log WHERE operate_type = '删除' ORDER BY operate_time DESC;

-- 查询所有失败的操作
SELECT * FROM sys_log WHERE status = 0 ORDER BY operate_time DESC;

7.5 性能分析

-- 查询执行时间超过1秒的操作
SELECT * FROM sys_log WHERE use_time > 1000 ORDER BY use_time DESC;

-- 统计各操作类型的平均执行时间
SELECT
    operate_type,
    COUNT(*) as count,
    AVG(use_time) as avg_time,
    MAX(use_time) as max_time
FROM sys_log
GROUP BY operate_type
ORDER BY avg_time DESC;

8. 日志分析示例

8.1 用户活跃度统计

-- 统计每个用户的操作次数
SELECT
    username,
    real_name,
    COUNT(*) as operate_count,
    MAX(operate_time) as last_operate_time
FROM sys_log
WHERE log_type = '操作日志'
GROUP BY user_id, username, real_name
ORDER BY operate_count DESC;

8.2 登录统计

-- 统计每个用户的登录次数
SELECT
    username,
    COUNT(*) as login_count,
    MAX(operate_time) as last_login_time
FROM sys_log
WHERE log_type = '登录日志' AND operate_type = '登录'
GROUP BY username
ORDER BY login_count DESC;

-- 统计每天的登录次数
SELECT
    DATE(operate_time) as date,
    COUNT(*) as login_count
FROM sys_log
WHERE log_type = '登录日志' AND operate_type = '登录'
GROUP BY DATE(operate_time)
ORDER BY date DESC;

8.3 异常操作统计

-- 统计失败的操作
SELECT
    username,
    detail,
    error_msg,
    operate_time
FROM sys_log
WHERE status = 0
ORDER BY operate_time DESC
LIMIT 100;

-- 统计失败次数最多的操作
SELECT
    detail,
    COUNT(*) as fail_count
FROM sys_log
WHERE status = 0
GROUP BY detail
ORDER BY fail_count DESC;

9. 注意事项

9.1 性能考虑

  1. 异步保存:日志使用异步方式保存,不影响主业务性能
  2. 批量查询:查询大量日志时,建议添加时间范围限制
  3. 定期清理:建议定期清理历史日志(如保留最近6个月)

9.2 安全考虑

  1. 敏感信息:密码字段自动过滤,不会记录到日志
  2. 权限控制:日志查询接口需要添加权限控制
  3. 数据脱敏:如果日志包含其他敏感信息,需要手动脱敏

9.3 最佳实践

  1. 合理使用注解:只在重要操作上添加 @SystemLogHandler 注解
  2. 清晰的描述:操作描述要清晰明了,便于日后追踪
  3. 统一命名:操作类型使用统一的命名(新增、修改、删除、查询、导出等)
  4. 及时清理:定期清理历史日志,保持数据库性能

10. 常见问题

Q1: 为什么有些方法没有记录日志?

A: 只有添加了 @SystemLogHandler 注解的方法才会记录日志。请检查方法是否添加了该注解。

Q2: 日志中的用户信息为空?

A: 可能的原因:

  • 用户未登录(匿名访问)
  • SecurityContext中没有认证信息
  • 登录接口因为在认证前执行,可能获取不到用户信息

Q3: 如何关闭某个接口的日志记录?

A: 移除该方法上的 @SystemLogHandler 注解即可。

Q4: 日志保存失败怎么办?

A: 日志保存失败不会影响主业务,但会在控制台输出错误日志。请检查:

  • 数据库连接是否正常
  • sys_log 表是否存在
  • 字段类型是否匹配

Q5: 如何自定义日志内容?

A: 可以修改 ControllerAop.java 中的 saveLog 方法,添加自定义字段。

11. 扩展功能

11.1 添加IP地理位置解析

可以集成IP地址库,将IP地址解析为具体地理位置:

// 在 IpUtils 中添加方法
public static String getIpLocation(String ip) {
    // 调用IP地址库API,解析地理位置
    // 返回如:"北京市 联通"
}

// 在 ControllerAop 中使用
log.setOperateLocation(IpUtils.getIpLocation(ipAddr));

11.2 添加数据变更记录

对于重要数据的修改,可以记录修改前后的值:

@SystemLogHandler("修改用户|修改")
@PutMapping("/edit")
public RestResult<?> edit(@RequestBody SysUser user) {
    // 查询修改前的数据
    SysUser oldUser = userService.getById(user.getId());

    // 执行修改
    userService.updateById(user);

    // 可以将修改前后的对比记录到日志的备注字段
    return RestResult.ok("修改成功");
}

11.3 实时日志推送

可以集成WebSocket,将重要操作实时推送到管理员:

@Autowired
private WebSocketService webSocketService;

// 在保存日志后推送
if ("删除".equals(log.getOperateType())) {
    webSocketService.sendToAdmin("用户执行了删除操作", log);
}

文档版本:v1.0 最后更新:2025-10-29 维护者:开发团队