当前位置: 首页 > news >正文

JAVA-实战4 AOP记录操作日志

さあ飞んでみなくちゃ

AOP记录操作日志

记录操作用户ID,操作时间,执行方法全类名,执行方法名,方法运行参数,返回结果,方法运行时长等相关信息的操作日志,并存入数据库

用于设计到方法运行时长,所以通知函数注解采用@Around环绕注解

切入点表达式采用更加灵活的@annotation匹配特定注解

封装数据实体类如下:

//日志数据实体类
public class OperateLogData {private Integer id; //IDprivate Integer operateEmpId; //操作人IDprivate LocalDateTime operateTime; //操作时间private String className; //操作类名private String methodName; //操作方法名private String methodParams; //操作方法参数private String returnValue; //操作方法返回值private Long costTime; //操作耗时
}

数据库存储日志表如下:

-- 操作日志表
create table operate_log(id int unsigned primary key auto_increment comment 'ID',operate_emp_id int unsigned comment '操作人ID',operate_time datetime comment '操作时间',class_name varchar(100) comment '操作的类名',method_name varchar(100) comment '操作的方法名',method_params varchar(2000) comment '方法参数',return_value varchar(2000) comment '返回值',cost_time bigint unsigned comment '方法执行耗时, 单位:ms'
) comment '操作日志表';

其它代码实现如下:

//@LogOperation注解
@Target(ElementType.METHOD) //该注解作用于方法
@Retention(RetentionPolicy.RUNTIME) //该注解运行时生效
public @interface LogOperation {
}
//AOP类
@Slf4j
@Aspect
@Component
public class RecordLogAspect {@Autowiredprivate OperateLogMapper operateLogMapper;@Around("@annotation(org.example.anno.LogOperation)")public Object recordTime2(ProceedingJoinPoint pjp) throws Throwable {Long startTime = System.currentTimeMillis();Object result = pjp.proceed();Long endTime = System.currentTimeMillis();Long costTime = endTime - startTime; //计算耗时OperateLogData logData = new OperateLogData();logData.setOperateEmpId(1008611); //暂时未能实现获取操作用户IDlogData.setOperateTime(LocalDateTime.now()); //获取当前操作时间logData.setClassName(pjp.getTarget().getClass().getName()); //获取操作全类名logData.setMethodName(pjp.getSignature().getName()); //获取操作方法名logData.setMethodParams(Arrays.toString(pjp.getArgs())); //获取方法运行参数并转化为字符串存储logData.setReturnValue(result != null ? result.toString():"void"); //获取方法返回值logData.setCostTime(costTime); log.info("生成操作日志! {}",logData);operateLogMapper.insertLogData(logData); //使用持久层接口存入数据库return result;}
}

实现操作用户ID获取

之前实现JWT令牌时存储了当前登录用户的相关个人信息,可以以此获得登录用户的ID

那么问题来了,在TokenFilter中获取到JWT并从JWT中解析出当前操作用户的ID,如何将当前操作用户ID传递给AOP程序以及Controller控制层、Service业务层呢?

ThreadLocal

ThreadLocal提供了一种线程封闭的机制,使得每个线程都可以访问自己独立的变量副本,而不影响其他线程。这种机制常被称为“线程局部变量”,在多线程编程中特别有用,可以避免复杂的同步控制,提高程序的性能和可读性。

在 Java 中,每个线程都拥有一个ThreadLocalMap成员变量,用来存储与该线程相关的ThreadLocal变量副本。这些变量副本是通过ThreadLocal对象作为键、实际值作为值存储在ThreadLocalMap中。因此,每个线程只能访问自己的变量副本,不会对其他线程造成干扰。

我们可以看到如果令牌校验通过随后执行操作的话将会是由同一个线程变量搭载运行,因此考虑使用ThreadLocal传递操作用户ID
image

具体代码如下:

//设置ThreadLocal工具类如下:
public class CurrentHolder {private static final ThreadLocal<Integer> CURRENT_LOCAL = new ThreadLocal<>();public static void setCurrentLocal(Integer userId) { //设置当前线程的userIDCURRENT_LOCAL.set(userId);}public static Integer getCurrentId() { //获取return CURRENT_LOCAL.get();}public static void removeCurrent() { //删除CURRENT_LOCAL.remove();}
}
//TokenFilter代码修改如下:
@Slf4j
@WebFilter("/*")
public class TokenFilter implements Filter {@Overridepublic void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {HttpServletRequest request = (HttpServletRequest) servletRequest;HttpServletResponse response = (HttpServletResponse) servletResponse;log.info("开启过滤器请求");//1. 获取请求路径String requestURI = request.getRequestURI();//2. 判断是否是登录请求,如果是则放行if(requestURI.contains("/login")) {log.info("登录请求,放行!");filterChain.doFilter(request,response);return;}//3. 获取tokenString token = request.getHeader("token");//4. 判断token是否存在,不存在返回401if(token == null || token.isEmpty()) {log.info("令牌为空,响应401");response.setStatus(HttpServletResponse.SC_UNAUTHORIZED); //设置401状态return;}//5.校验token,校验失败返回401,成功则放行且传递用户Idtry {Claims claims = JwtUtils.parseToken(token);Integer userId = Integer.valueOf(claims.get("id").toString());CurrentHolder.setCurrentLocal(userId);log.info("当前登录员工Id:{}",userId);} catch (Exception e) {log.info("令牌非法,响应401");response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);return;}//6.校验通过,放行log.info("令牌合法,放行");filterChain.doFilter(request,response);//7. 删除ThreadLocal数据CurrentHolder.removeCurrent();}
}
//AOP切面类实现如下:
@Slf4j
@Aspect
@Component
public class RecordLogAspect {@Autowiredprivate OperateLogMapper operateLogMapper;private Integer GetCurrentUserId() {return CurrentHolder.GetCurrentId();}@Around("@annotation(org.example.anno.LogOperation)")public Object recordTime2(ProceedingJoinPoint pjp) throws Throwable {Long startTime = System.currentTimeMillis();Object result = pjp.proceed();Long endTime = System.currentTimeMillis();Long costTime = endTime - startTime;OperateLogData logData = new OperateLogData();logData.setOperateEmpId(1008611);logData.setOperateTime(LocalDateTime.now());logData.setClassName(pjp.getTarget().getClass().getName());logData.setMethodName(pjp.getSignature().getName());logData.setMethodParams(Arrays.toString(pjp.getArgs()));logData.setReturnValue(result != null ? result.toString():"void");logData.setCostTime(costTime);log.info("生成操作日志! {}",logData);operateLogMapper.insertLogData(logData);return result;}
}

效果如下:
image

http://www.jsqmd.com/news/539810/

相关文章:

  • 3步完成游戏模组管理:XXMI启动器统一平台终极指南
  • QMCDecode:解锁QQ音乐加密文件的macOS终极解决方案
  • EgoVerse 数据集全量拉取:保姆级实操指南
  • League-Toolkit:英雄联盟玩家效率提升工具全指南
  • 2026 广州国际教育消费指南:英语培训机构怎么选?看完这篇就够了 - 服务品牌热点
  • 【出版 | 检索】第二届智慧交通与未来出行国际学术会议(ITFM 2026)
  • 大疆司空平台接入实战:司空 Sync文件同步
  • 告别反复重录!这款 AI 口播提词器,让你一次过稿不翻车
  • Claude Remote Control 技术详解:跨设备无缝协作的远程会话控制方案
  • 启世计划遭黑客入侵 平台暂停服务启动紧急修复
  • RK3588 编译GDB
  • STM32F1XX 的 CAN 的 波特率配置
  • linux查看文件夹总大小
  • 2026贵州源能达钢材批发联系方式公布,在贵州做镀锌板现货批发怕踩坑?认准这个电话 - 精选优质企业推荐榜
  • 构建高效自动化抖音内容采集系统:专业级批量下载解决方案
  • Aseprite进阶指南:从像素瓦片到Unity动态Tilemap构建
  • 深圳技校哪家强?宝山技工学校专业全、实训强 - 服务品牌热点
  • 计算机毕业设计springboot移动端机房管理系统 基于SpringBoot的高校实验教学资源智能管理平台 基于SpringBoot的智慧实训中心数字化运营系统
  • 告别Joplin!用MarkDownload+Obsidian打造你的网页剪藏工作流(附完整配置JSON)
  • 保姆级教程:手把手教你从ENSEMBL官网下载GRCh38/GRCh37的GTF注释文件(附网址规律总结)
  • 收藏!5种Agent Skill设计模式,让你的大模型Agent更稳定、可复用、不跑偏!
  • 黔东南工程钢材怕踩坑?2026贵州源能达钢材批发官方电话与选购指南 - 精选优质企业推荐榜
  • Claude Code 工程化实战:从工具使用者到 Agent 构建者的进阶之路
  • 从两套系统到一条 SQL:SelectDB search() 搞定日志的搜索与分析
  • vscode-markdown-preview-enhanced 配置实战指南:从场景需求到性能优化
  • 如何快速修复TranslucentTB在Windows 11更新后无法启动:终极解决方案指南
  • ai测试文档first
  • 使用pycharm调试后端项目
  • 【实战指南】利用n8n工作流实现SQLBot MCP服务的自动化数据查询
  • 3步掌握暗黑2存档编辑:无需安装的网页工具全解析