本文目录导读:

- 核心原则
- 方案一:后端 AOP 切面记录(最推荐,Java/Spring Boot 示例)
- 方案二:数据库触发器 + 审计表(适合数据变更审计)
- 方案三:前端拦截器记录(适用于 SPA 单页应用)
- 方案四:Linux 用户操作审计(服务器运维)
- 最佳实践总结 & 脚本选择建议
- 重要提醒
记录操作日志是软件开发中非常关键的安全和审计手段,根据你的技术栈(前端、后端、数据库)和具体需求,实现方式有所不同。
以下是从简单到复杂、从前端到后端的几种主流实现方案,并附上具体脚本示例。
核心原则
在写脚本前,需明确要记录什么(5W1H原则):
- Who:谁操作的(用户ID/IP)
- What:做了什么(增删改查、具体数据)
- When:操作时间
- Where:操作的模块/功能/API路径
- Why/How:请求参数、结果、耗时
后端 AOP 切面记录(最推荐,Java/Spring Boot 示例)
这是最标准、侵入性最低的方式,利用AOP(Aspect Oriented Programming)拦截所有Controller方法,自动记录。
步骤:
- 自定义注解
@Log:标记哪些方法需要记录。 - 切面类
LogAspect:在方法执行前后织入代码。
注解定义
import java.lang.annotation.*;
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Log {
String module() default ""; // 模块名称
String operation() default ""; // 操作类型: 添加/删除/修改/查询
}
切面类脚本(核心)
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import javax.servlet.http.HttpServletRequest;
import java.time.LocalDateTime;
import java.util.Arrays;
@Aspect
@Component
public class LogAspect {
@Around("@annotation(com.example.demo.Log)") // 指定切点
public Object logAround(ProceedingJoinPoint joinPoint) throws Throwable {
long startTime = System.currentTimeMillis();
Object result = null;
boolean isException = false;
String errorMsg = null;
try {
// 执行目标方法
result = joinPoint.proceed();
} catch (Throwable e) {
isException = true;
errorMsg = e.getMessage();
throw e; // 继续抛异常,不吞掉
} finally {
// 无论成功失败,都记录日志
recordLog(joinPoint, startTime, result, isException, errorMsg);
}
return result;
}
private void recordLog(ProceedingJoinPoint joinPoint, long startTime, Object result, boolean isException, String errorMsg) {
try {
// 1. 获取请求信息
ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
HttpServletRequest request = attributes.getRequest();
// 2. 获取注解上的信息
MethodSignature signature = (MethodSignature) joinPoint.getSignature();
Log logAnnotation = signature.getMethod().getAnnotation(Log.class);
// 3. 组装日志对象 (这里直接打印,实际应存入数据库或文件)
System.out.println("====== 操作日志开始 ======");
System.out.println("时间: " + LocalDateTime.now());
System.out.println("IP: " + request.getRemoteAddr());
System.out.println("URL: " + request.getRequestURI());
System.out.println("模块: " + logAnnotation.module());
System.out.println("操作: " + logAnnotation.operation());
System.out.println("用户ID: " + request.getHeader("userId")); // 从请求头获取当前用户
System.out.println("请求参数: " + Arrays.toString(joinPoint.getArgs()));
System.out.println("执行耗时(ms): " + (System.currentTimeMillis() - startTime));
System.out.println("是否异常: " + isException);
if (isException) System.out.println("异常信息: " + errorMsg);
System.out.println("====== 操作日志结束 ======");
// TODO: 将 logInfo 存入数据库 (异步执行,避免影响主业务)
} catch (Exception e) {
// 日志记录本身不能抛出异常,避免影响业务
System.err.println("记录操作日志失败: " + e.getMessage());
}
}
}
用法: 在Controller方法上添加注解即可。
@RestController
public class UserController {
@PostMapping("/user/add")
@Log(module = "用户管理", operation = "新增用户")
public Result addUser(@RequestBody User user) {
// 业务逻辑
return Result.success();
}
}
数据库触发器 + 审计表(适合数据变更审计)
不依赖业务代码,直接在数据库层面记录对某张表的更改。
缺点: 只能记录INSERT/UPDATE/DELETE,无法记录“查询”和“用户登录”等行为。
MySQL 示例:记录用户表 users 的变更
-- 1. 创建日志表
CREATE TABLE `audit_log` (
`id` int NOT NULL AUTO_INCREMENT,
`table_name` varchar(50) DEFAULT NULL,
`operation` varchar(10) DEFAULT NULL, -- INSERT, UPDATE, DELETE
`old_data` json DEFAULT NULL, -- 旧数据
`new_data` json DEFAULT NULL, -- 新数据
`operator` varchar(50) DEFAULT NULL, -- 操作人 (需应用传入)
`create_time` datetime DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (`id`)
) ENGINE=InnoDB;
-- 2. 创建触发器 (UPDATE 为例)
DELIMITER $$
CREATE TRIGGER `users_audit_update`
AFTER UPDATE ON `users`
FOR EACH ROW
BEGIN
INSERT INTO `audit_log` (`table_name`, `operation`, `old_data`, `new_data`, `operator`)
VALUES (
'users',
'UPDATE',
JSON_OBJECT('id', OLD.id, 'name', OLD.name, 'email', OLD.email),
JSON_OBJECT('id', NEW.id, 'name', NEW.name, 'email', NEW.email),
@current_user -- 需要应用程序在执行 SQL 前设置 SET @current_user = '张三';
);
END$$
DELIMITER ;
注意: 触发器无法自动感知Who,需在应用层执行SQL前设置会话变量(如 SET @current_user = ‘admin’;)。
前端拦截器记录(适用于 SPA 单页应用)
主要用于记录页面行为(点击、路由跳转、按钮点击)。
Vue.js 示例(使用路由守卫 + Axios 拦截器)
// 1. 路由守卫:记录页面跳转
router.beforeEach((to, from, next) => {
const logInfo = {
type: 'ROUTE_CHANGE',
from: from.fullPath,
to: to.fullPath,
time: new Date().toISOString(),
user: store.state.userInfo?.username
};
// 发送到后端 (使用 navigator.sendBeacon 确保页面卸载时也能发送)
navigator.sendBeacon('/api/log/operate', JSON.stringify(logInfo));
next();
});
// 2. Axios 响应拦截器:记录 API 请求结果(增删改操作)
axios.interceptors.response.use(
response => {
// 如果请求是 POST/PUT/DELETE,记录操作
if (['post', 'put', 'delete'].includes(response.config.method)) {
recordApiLog(response.config, response.data);
}
return response;
},
error => {
// 记录失败的请求
recordApiLog(error.config, { status: error.status, message: error.message });
return Promise.reject(error);
}
);
function recordApiLog(config, responseData) {
const log = {
type: 'API_CALL',
method: config.method.toUpperCase(),
url: config.url,
params: config.params || config.data,
status: responseData?.status || responseData?.code,
time: new Date().toISOString()
};
// 异步发送(不阻塞主流程)
fetch('/api/log/operate', { method: 'POST', body: JSON.stringify(log), keepalive: true });
}
Linux 用户操作审计(服务器运维)
记录谁登录了服务器,执行了什么命令。
使用 history + 系统日志
# 在 /etc/profile 或 ~/.bashrc 中添加如下脚本
export HISTTIMEFORMAT="%F %T "
export PROMPT_COMMAND='\
if [ -z "$OLD_PWD" ];then
OLD_PWD=$PWD
fi
if [ ! -z "$LAST_CMD" ]; then
logger -p local1.notice -t bash_history "[USER=$USER] [PWD=$OLD_PWD] [CMD=$LAST_CMD]"
fi
LAST_CMD=$(history 1 | sed "s/^[ ]*[0-9]\+[ ]*//") '
使用 auditd(Linux 审计系统,更专业)
# 1. 安装 sudo apt install auditd audispd-plugins -y # 2. 添加规则:监控 /etc/passwd 文件的写入 sudo auditctl -w /etc/passwd -p wa -k password_change # 3. 查看日志 sudo ausearch -k password_change | aureport -f
最佳实践总结 & 脚本选择建议
| 场景 | 推荐方案 | 原因 |
|---|---|---|
| 业务系统操作 (增删改查) | AOP切面 (方案一) | 低侵入,解耦,可记录详细业务上下文。 |
| 数据库敏感表审计 | 数据库触发器 (方案二) | 无法被应用日志绕过,安全性高,适合合规要求。 |
| 用户界面行为分析 | 前端拦截器 (方案三) | 记录页面停留时间、按钮点击率等。 |
| 服务器安全审计 | auditd 或日志脚本 (方案四) | 操作系统级别,监控所有命令执行。 |
重要提醒
- 异步处理:写日志不要阻塞主业务流程(使用MQ、线程池或异步写入)。
- 日志脱敏:记录参数时,密码、身份证、Token等敏感字段要处理。
- 保留期限:设计日志表时加
expire_time字段,定期归档或删除。 - 日志存储:高并发场景建议用 ElasticSearch 存储,用 Logstash 或 Filebeat 收集,而不是直接写数据库。