系统集成了完整的操作日志记录功能,自动记录用户的所有重要操作,包括:
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='系统操作日志表';
在需要记录日志的方法上添加 @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) {
// 导出逻辑
}
}
@SystemLogHandler("操作描述|操作类型")
| 分隔两部分特殊处理:
登录 或 登出,日志类型自动标记为 登录日志操作日志@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) {
// 登录逻辑
}
}
系统自动将请求参数中的 password 字段替换为 ******:
// 原始参数
{
"username": "admin",
"password": "admin123"
}
// 记录到日志
{
"username": "admin",
"password": "******"
}
请求参数超过2000字符时,自动截断并添加 ... 后缀。
日志保存使用 @Async 异步执行,不影响主业务性能:
@Async
public void saveLogAsync(SysLog log) {
save(log);
}
日志保存失败不会影响主业务:
try {
saveLog(jp, request, startTime, exception);
} catch (Exception e) {
logger.error("保存操作日志失败", e);
}
-- 查询登录日志
SELECT * FROM sys_log WHERE log_type = '登录日志' ORDER BY operate_time DESC;
-- 查询操作日志
SELECT * FROM sys_log WHERE log_type = '操作日志' ORDER BY operate_time DESC;
-- 查询某个用户的所有操作
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;
-- 查询今天的操作日志
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;
-- 查询所有删除操作
SELECT * FROM sys_log WHERE operate_type = '删除' ORDER BY operate_time DESC;
-- 查询所有失败的操作
SELECT * FROM sys_log WHERE status = 0 ORDER BY operate_time DESC;
-- 查询执行时间超过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;
-- 统计每个用户的操作次数
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;
-- 统计每个用户的登录次数
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;
-- 统计失败的操作
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;
@SystemLogHandler 注解A: 只有添加了 @SystemLogHandler 注解的方法才会记录日志。请检查方法是否添加了该注解。
A: 可能的原因:
A: 移除该方法上的 @SystemLogHandler 注解即可。
A: 日志保存失败不会影响主业务,但会在控制台输出错误日志。请检查:
A: 可以修改 ControllerAop.java 中的 saveLog 方法,添加自定义字段。
可以集成IP地址库,将IP地址解析为具体地理位置:
// 在 IpUtils 中添加方法
public static String getIpLocation(String ip) {
// 调用IP地址库API,解析地理位置
// 返回如:"北京市 联通"
}
// 在 ControllerAop 中使用
log.setOperateLocation(IpUtils.getIpLocation(ipAddr));
对于重要数据的修改,可以记录修改前后的值:
@SystemLogHandler("修改用户|修改")
@PutMapping("/edit")
public RestResult<?> edit(@RequestBody SysUser user) {
// 查询修改前的数据
SysUser oldUser = userService.getById(user.getId());
// 执行修改
userService.updateById(user);
// 可以将修改前后的对比记录到日志的备注字段
return RestResult.ok("修改成功");
}
可以集成WebSocket,将重要操作实时推送到管理员:
@Autowired
private WebSocketService webSocketService;
// 在保存日志后推送
if ("删除".equals(log.getOperateType())) {
webSocketService.sendToAdmin("用户执行了删除操作", log);
}
文档版本:v1.0 最后更新:2025-10-29 维护者:开发团队