springboot实现用户操作日志记录

springboot实现用户操作日志记录

简介:之前写了《aop实现日志持久化记录》一文,主要介绍自定义aop标注方法上,通过切面方法对用户操作插入mysql。思路正确但是实际操作上存在一些小问题,本文将从项目出发,对细节进行补充。

另外值得一提的是,因为是基于AOP对controller方法做环绕通知实现的日志持久化记录,所以如果请求在Filter或者Interceptor中被拦截,则不会进入环绕通知,也就无法记录日志

1. 创建日志数据库表

数据库表结构大致如下

image-20231231171416729.png

建表语句(基于MySQL 5.7)

CREATE TABLE `admin_log` (`id` BIGINT(20) NOT NULL AUTO_INCREMENT COMMENT '日志id',`ip` VARCHAR(20) DEFAULT NULL COMMENT '操作ip',`uri` VARCHAR(100) DEFAULT NULL COMMENT '请求URI',`method_type` VARCHAR(10) DEFAULT NULL COMMENT '请求类型(GET,POST)',`method_name` VARCHAR(100) DEFAULT NULL COMMENT '目标方法名',`method_desc` VARCHAR(20) DEFAULT NULL COMMENT '接口介绍',`request_param` TEXT COMMENT '请求参数',`status` VARCHAR(20) DEFAULT NULL COMMENT '请求状态',`result` TEXT COMMENT '返回结果',`user_id` VARCHAR(20) DEFAULT NULL COMMENT '操作者id',`execution_time` BIGINT(20) DEFAULT NULL COMMENT '方法耗时(ms)',`create_time` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '操作时间',PRIMARY KEY (`id`)
) ENGINE=INNODB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8mb4

2. 引入maven依赖

		<!-- 其他依赖在此不列出,springboot,mybatis等等	……  --><!-- aop依赖 --><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-aop</artifactId></dependency>

3. 创建数据库表对应实体类

@Data
public class AdminLog {private Integer id;private String userId;          // 用户idprivate String ip;              // 操作者ipprivate String uri;             // 请求URIprivate String methodType;      // 请求类型【GET,POST】private String methodName;      // 方法名称private String methodDesc;      // 接口简介private String requestParam;    // post请求参数private String status;          // 方法执行最终状态private String result;          // 返回结果private Long executionTime;     // 方法耗时private String createTime;      // 执行时间}

4. 创建自定义注解

自定义注解,标注在要保存用户操作日志的controller方法上,被标注的方法会通过下面写的环绕通知进行日志记录

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface WriteLog {/*** 方法描述(描述目标方法的作用)*/String value();}

5. 创建上下文对象

从上面数据库表可以看出,日志类需要的ip、uri、methodType等参数需要在请求的request参数中获取,为记录这些参数信息,通过ThreadLocal设置上下文对象,方便获取。

先创建需要的请求信息的实体类

@Data
public class RequestBaseInfo {private String ip;private String methodType;  // 请求类型,如【GET,POST,PUT,DELETE】private String uri;public RequestBaseInfo(){}public RequestBaseInfo(String ip, String methodType, String uri){this.ip = ip;this.methodType = methodType;this.uri = uri;}}

然后创建上下文对象,保存该类的对象

public class RequestContextHolder {private static final ThreadLocal<RequestBaseInfo> ipThreadLocal = new ThreadLocal<>();public static void setRequestBaseInfo(RequestBaseInfo requestBaseInfo) {ipThreadLocal.set(requestBaseInfo);}public static RequestBaseInfo getRequestBaseInfo() {return ipThreadLocal.get();}public static void clear() {ipThreadLocal.remove();}}

6. 创建拦截器

创建拦截器,为每个线程保存上下文对象

/*** 该拦截器主要为线程保存请求的基本信息,例如来源ip,请求uri,请求方法等*/
@Slf4j
@Component
public class SaveRequestBaseInterceptor implements HandlerInterceptor {@Overridepublic boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {// 将请求基本信息存到ThreadLocal,[ip,method,uri]RequestBaseInfo rbi = new RequestBaseInfo(request.getRemoteAddr(),request.getMethod(),request.getRequestURI());RequestContextHolder.setRequestBaseInfo(rbi);// 另外自己的框架里是否可以获取用户已登录的ip信息,没有的话这里还可以多设置一个获取登录用户的id// 因为我们的数据库日志表中有userId字段,如果没办法在后续的aop切面方法中获取,亦可以在这里拦截器中获取// 例如我项目中的spring security可以在aop切面中获取登录主体,拦截器就不需要获取了// 具体思路就是设置多一个上下文对象或者在RequestBaseInfo中设置多一个userId字段// 然后在这里获取请求token,然后在缓存中获取登录信息,获取登录者id// 当然实现的方式有多种,根据实际项目配置,或者不记录userId也可以// ……todoreturn true;}
}

将拦截器注册生效(配置进WebMvcConfigurer)

@Configuration
public class WebConfig  implements WebMvcConfigurer {@AutowiredSaveRequestBaseInterceptor saveRequestBaseInterceptor;/*** 添加自定义拦截器* @param registry*/@Overridepublic void addInterceptors(InterceptorRegistry registry) {registry.addInterceptor(saveRequestBaseInterceptor).addPathPatterns("/**");}}

7. 创建AOP切面方法

在创建切面方法前还需要创建AdminLog表dao操作的相关代码,例如具体插入的service文件,mapper文件,这里就省略不说了,很基础的东西

aop实现针对以上自定义注解@WriteLog标注切面的环绕通知

@Aspect
@Component
public class WriteLogAspect {@AutowiredAdminLogService adminLogService;// com.jankin.inoteadmin.system.annotation.WriteLog 是我定义接口的文件路径@Pointcut("@annotation(com.jankin.inoteadmin.system.annotation.WriteLog)")public void writeLogAspect() {}/*** 返回通知切面方法* @param joinPoint 切点,就是被注解的目标方法*/@Around("writeLogAspect()")public Object logPostMapping(ProceedingJoinPoint joinPoint) throws Throwable {String userId = null; // 获取操作用户Id//  //我这里用的是SpringSecurity框架,这样获取UserId//	Authentication authentication = SecurityContextHolder.getContext().getAuthentication();//	if (authentication!=null){//  	// SecurityUser是自定义的UserDetails类,其中包含了UserId//    	SecurityUser principal = (SecurityUser)authentication.getPrincipal();//    	userId = principal.getUserId();//	}String status = "ERROR";String resultStr = "";Object result = null;long startTime = System.currentTimeMillis();    // 执行前时间try {result = joinPoint.proceed();} catch (Throwable e) {resultStr = e.getMessage();throw e;}finally {long finishTime = System.currentTimeMillis();   // 目标方法执行后时间if (result instanceof Result) {resultStr = result.toString();   // 返回结果字符串status = ((Result) result).getCode()==200? "SUCCESS":"EXCEPTION";}// 其他to do ……AdminLog sysLog = new AdminLog();sysLog.setUserId(userId);RequestBaseInfo rbi = RequestContextHolder.getRequestBaseInfo();sysLog.setIp(rbi.getIp());sysLog.setUri(rbi.getUri());sysLog.setMethodType(rbi.getMethodType());sysLog.setMethodName(joinPoint.getSignature().toShortString());// 获取注解上的方法描述MethodSignature signature = (MethodSignature) joinPoint.getSignature();WriteLog annotation = signature.getMethod().getAnnotation(WriteLog.class);sysLog.setMethodDesc(annotation.value());sysLog.setRequestParam(Arrays.toString(joinPoint.getArgs()));sysLog.setStatus(status);sysLog.setResult(resultStr);sysLog.setExecutionTime(finishTime-startTime);adminLogService.addLog(sysLog);}return result;}}

8. 应用

在接口(controller方法)上标注自定义注解(@WriteLog),即可完成接口日志的插入

    @WriteLog("测试接口Get")@GetMappingpublic Result get(){return Result.success("测试成功");}@WriteLog("测试接口Post")@PostMapping("post")public Result post(TestDto testDto){return Result.success("测试成功");}

测试结果

image-20240101002407422.png

至此,全篇结束

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/312907.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Nginx 代理静态资源,解决跨域问题

&#x1f602; 背景&#xff1a;移动端 H5 项目&#xff0c;依赖了一个外部的 JS 文件。访问时&#xff0c;出现跨域&#xff0c;导致请求被 block。 当前域名&#xff1a;https://tmcopss.test.com要访问的 JS 文件&#xff1a;https://tm.test.com/public/scripts/y-jssdk.j…

C++每日一练(8):图像相似度

题目描述 给出两幅相同大小的黑白图像&#xff08;用0-1矩阵&#xff09;表示&#xff0c;求它们的相似度。 说明&#xff1a;若两幅图像在相同位置上的像素点颜色相同&#xff0c;则称它们在该位置具有相同的像素点。两幅图像的相似度定义为相同像素点数占总像素点数的百分比。…

【华为机试】2023年真题B卷(python)-猴子爬山

一、题目 题目描述&#xff1a; 一天一只顽猴想去从山脚爬到山顶&#xff0c;途中经过一个有个N个台阶的阶梯&#xff0c;但是这猴子有一个习惯&#xff1a; 每一次只能跳1步或跳3步&#xff0c;试问猴子通过这个阶梯有多少种不同的跳跃方式&#xff1f; 二、输入输出 输入描述…

2024年,幸运如期而至,愿我们将来不慌不忙,却有岁月的馈赠。

文章目录 一、工作和项目方面1、商城项目2、业务项目13、业务项目24、管理事项 二、家庭&#xff0c;生活&#xff0c;投资和理财方面1、家庭变故2、单一工资收入的结构挑战。3、投资和理财之路 三、技术学习方面读书和阅读AI技术以及工具学习&#xff0c;应用学习和参与知名的…

【C++】类与对象

文章目录 1. 面向过程和面向对象的初步认识2. 类的引入3. 类的定义4. 类的访问限定符及封装4.1 访问限定符4.2 封装 5. 类的作用域6. 类的实例化7. 类对象模型7.1 如何计算类对象的大小7.2 类对象的存储方式猜测7.3 结构体内存对齐规则 8. this指针8.1 this指针的引出8.2 this指…

计算机网络 (期末救命版)

文章目录 Ⅰ 网络概述1. 互联网概述与组成2. 计算机网络的类别3. 计算机网络的性能指标4. 计算机网络体系结构 Ⅱ 物理层1. 物理层的任务2. 信道复用技术 Ⅲ 数据链路层1. 点对点信道2. 基本问题3. 点对点协议 PPP4. 使用广播信道的数据链路层 Ⅳ 网络层1. 网络层的服务2. 网际…

【华为机试】2023年真题B卷(python)-喊七的次数重排

一、题目 题目描述&#xff1a; 喊7是一个传统的聚会游戏&#xff0c;N个人围成一圈&#xff0c;按顺时针从1到N编号。 编号为1的人从1开始喊数&#xff0c;下一个人喊的数字为上一个人的数字加1&#xff0c;但是当将要喊出来的数字是7的倍数或者数字本身含有7的话&#xff0c;…

认识Linux指令之 “man” 指令

01.man指令&#xff08;重要&#xff09; Linux的命令有很多参数&#xff0c;我们不可能全记住&#xff0c;我们可以通过查看联机手册获取帮助。访问Linux手册页的命令是 man 语法: man [选项] 命令 常用选项 -k 根据关键字搜索联机帮助 num 只在第num章节找 -a 将所有章…

Big-endian与Little-endian详尽说明

大端与小端存储详尽说明 大端与小端存储详尽说明 大端与小端存储详尽说明一. 什么是字节序二. 什么是大端存储模式三. 什么是小端存储模式四. 大小端各自的特点五. 为什么会有大小端模式之分六. 为什么要注意大小端问题六. 大小端判定程序七. 大端小端的转换1&#xff09;16位大…

Pandas的apply方法的应用练习

1.使用自定义函数的原因 Pandas虽然提供了大量处理数据的API&#xff0c;但是当提供的API无法满足需求的时候&#xff0c;这时候就需要使用自定义函数来解决相关的问题 2. data {column1:[1, 2, 15, 4, 8]} df pd.DataFrame(data) 请创建一个新的列new_column&#xff0c;其…

车载电子电器架构 —— 电子电气系统功能开发

车载电子电器架构 —— 电子电气系统功能开发 我是穿拖鞋的汉子&#xff0c;魔都中坚持长期主义的汽车电子工程师。 老规矩&#xff0c;分享一段喜欢的文字&#xff0c;避免自己成为高知识低文化的工程师&#xff1a; 屏蔽力是信息过载时代一个人的特殊竞争力&#xff0c;任何…

Windows反调试技术学习

Windows反调试 前言元旦快乐&#xff01;&#xff01;&#xff01;通过 API 调用IsDebuggerPresentCheckRemoteDebuggerPresent&#xff08;NtQueryInformationProcess&#xff09;OutputDebugStringZwSetInformationThread&#xff08;ThreadHideFromDebugger&#xff09; 手动…