脚本怎样记录操作日志

wen 实用脚本 26

本文目录导读:

脚本怎样记录操作日志

  1. 核心原则
  2. 方案一:后端 AOP 切面记录(最推荐,Java/Spring Boot 示例)
  3. 方案二:数据库触发器 + 审计表(适合数据变更审计)
  4. 方案三:前端拦截器记录(适用于 SPA 单页应用)
  5. 方案四:Linux 用户操作审计(服务器运维)
  6. 最佳实践总结 & 脚本选择建议
  7. 重要提醒

记录操作日志是软件开发中非常关键的安全和审计手段,根据你的技术栈(前端、后端、数据库)和具体需求,实现方式有所不同。

以下是从简单到复杂、从前端到后端的几种主流实现方案,并附上具体脚本示例。

核心原则

在写脚本前,需明确要记录什么(5W1H原则):

  • Who:谁操作的(用户ID/IP)
  • What:做了什么(增删改查、具体数据)
  • When:操作时间
  • Where:操作的模块/功能/API路径
  • Why/How:请求参数、结果、耗时

后端 AOP 切面记录(最推荐,Java/Spring Boot 示例)

这是最标准、侵入性最低的方式,利用AOP(Aspect Oriented Programming)拦截所有Controller方法,自动记录。

步骤:

  1. 自定义注解 @Log:标记哪些方法需要记录。
  2. 切面类 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 或日志脚本 (方案四) 操作系统级别,监控所有命令执行。

重要提醒

  1. 异步处理:写日志不要阻塞主业务流程(使用MQ、线程池或异步写入)。
  2. 日志脱敏:记录参数时,密码、身份证、Token等敏感字段要处理。
  3. 保留期限:设计日志表时加expire_time字段,定期归档或删除。
  4. 日志存储:高并发场景建议用 ElasticSearch 存储,用 LogstashFilebeat 收集,而不是直接写数据库。

抱歉,评论功能暂时关闭!