企业级档案操作留痕全流程实操指南:从搭建到上线可直接复用
一、前期准备
所需工具可直接通过以下地址或命令安装:
- MySQL 8.0:Linux执行
sudo apt install mysql-server-8.0,Windows下载地址:https://dev.mysql.com/downloads/mysql/8.0.html - JDK 1.8:下载地址:https://www.oracle.com/java/technologies/downloads/java8
- SpringBoot 2.7.x:初始化地址:https://start.spring.io/,选择对应版本即可
二、核心表结构设计
直接执行以下SQL创建留痕日志表,可直接复用:
```sql CREATE TABLE `sys_archive_operation_log` ( `id` bigint NOT NULL AUTO_INCREMENT COMMENT '主键ID', `archive_id` bigint NOT NULL COMMENT '关联档案ID', `operate_user_id` bigint NOT NULL COMMENT '操作人ID', `operate_user_name` varchar(32) NOT NULL COMMENT '操作人姓名', `operate_type` varchar(16) NOT NULL COMMENT '操作类型:VIEW/EDIT/DELETE/DOWNLOAD/UPLOAD/PREVIEW', `operate_content` text COMMENT '操作详情:修改字段新旧值、操作描述', `operate_ip` varchar(64) NOT NULL COMMENT '操作IP地址', `operate_ua` varchar(256) DEFAULT NULL COMMENT '操作端UA', `operate_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '操作时间', PRIMARY KEY (`id`), KEY `idx_archive_id` (`archive_id`), KEY `idx_operate_time` (`operate_time`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='档案操作留痕日志表'; ```三、后端核心逻辑实现
3.1 切面依赖与自定义注解
首先引入AOP切面依赖,无需修改现有业务代码即可实现埋点:
```xml自定义操作留痕注解,用于标记需要留痕的业务接口:
```java @Target(ElementType.METHOD) @Retention(RetentionPolicy.RUNTIME) public @interface ArchiveLog { // 操作类型,对应表中operate_type字段 String operateType(); // 档案ID所在请求参数的位置,默认第0位 int archiveIdIndex() default 0; } ```3.2 全局切面实现

以下代码可直接复制使用,所有切面逻辑用try-catch包裹,异常不会影响主业务流程:
```java @Aspect @Component public class ArchiveLogAspect { @Autowired private HttpServletRequest request; @Autowired private ArchiveOperationLogMapper logMapper; @Autowired private ArchiveMapper archiveMapper; // 切点:所有标注@ArchiveLog的方法 @Pointcut("@annotation(com.xxx.archive.annotation.ArchiveLog)") public void logPointcut() {} @AfterReturning(pointcut = "logPointcut()", returning = "result") public void afterOperate(JoinPoint joinPoint, Object result) { try { // 获取注解配置 MethodSignature signature = (MethodSignature) joinPoint.getSignature(); ArchiveLog logAnnotation = signature.getMethod().getAnnotation(ArchiveLog.class); String operateType = logAnnotation.operateType(); int archiveIdIndex = logAnnotation.archiveIdIndex(); // 获取档案ID Long archiveId = (Long) joinPoint.getArgs()[archiveIdIndex]; // 获取当前登录用户(需提前在登录拦截器将用户信息存入request attribute) LoginUser loginUser = (LoginUser) request.getAttribute("loginUser"); // 组装日志基础信息 ArchiveOperationLog log = new ArchiveOperationLog(); log.setArchiveId(archiveId); log.setOperateUserId(loginUser.getUserId()); log.setOperateUserName(loginUser.getUserName()); log.setOperateType(operateType); // 编辑操作自动对比新旧字段值 if ("EDIT".equals(operateType)) { Archive oldArchive = archiveMapper.selectById(archiveId); Archive newArchive = (Archive) joinPoint.getArgs()[archiveIdIndex + 1]; String content = compareFieldDiff(oldArchive, newArchive); log.setOperateContent(content); } // 获取操作IP和UA log.setOperateIp(getIpAddress(request)); log.setOperateUa(request.getHeader("User-Agent")); // 写入日志表 logMapper.insert(log); } catch (Exception e) { e.printStackTrace(); } } // 对比两个档案对象的字段差异 private String compareFieldDiff(Archive oldObj, Archive newObj) throws IllegalAccessException { StringBuilder sb = new StringBuilder(); Field[] fields = Archive.class.getDeclaredFields(); // 排除不需要对比的系统字段 List使用时只需在业务接口上添加注解即可,示例:@ArchiveLog(operateType = "EDIT", archiveIdIndex = 0)
四、前端补充埋点
针对预览、打印等前端触发无后端接口的操作,可通过全局指令上报留痕,Vue示例代码如下:
```js // 注册全局留痕指令 Vue.directive('archive-log', { bind: function (el, binding) { el.addEventListener('click', () => { const { archiveId, operateType } = binding.value // 调用后端留痕上报接口 axios.post('/api/archive/log/report', { archiveId, operateType }).catch(err => console.log('留痕上报失败', err)) }) } }) // 页面使用: ```五、验证与兜底配置
5.1 上线前验证
必须覆盖所有操作场景测试:查看、编辑、删除、下载、上传、预览,每个操作后查询留痕表,确认所有字段填充正确,编辑操作的新旧值对比准确
5.2 日志备份兜底
留痕日志不可删除,需配置定时备份,Linux执行crontab -e添加以下配置,每天凌晨1点自动备份:
六、常见问题排查
- 获取不到操作人:检查登录拦截器执行顺序是否早于切面,是否将登录用户信息存入request attribute
- IP获取为127.0.0.1:检查Nginx反向代理配置是否添加
proxy_set_header X-Forwarded-For $remote_addr; - 编辑操作新旧值对比为空:检查是否错误排除了需要对比的字段,请求参数的新值是否和数据库字段匹配