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.mzph.cn/news/596889.shtml

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

相关文章

【JaveWeb教程】(2)Web前端基础:JavaScript入门不再难:一篇文章教你轻松搞定JavaScript的基础语法与函数

目录 1 介绍2 引入方式3 基础语法3.1 书写语法3.2 变量3.3 数据类型和运算符 4 函数4.1 第一种定义格式4.2 第二种定义格式 html完成了架子&#xff0c;css做了美化&#xff0c;但是网页是死的&#xff0c;我们需要给他注入灵魂&#xff0c;所以接下来我们需要学习JavaScript&a…

普通用户用哪款电脑杀毒软件最好?

前言 各位小伙伴接触到电脑的时候&#xff0c;都一定有听过“电脑一定要安装杀毒软件”这句话。 毕竟在电脑诞生之初到今天&#xff0c;电脑木马和病毒依旧存在。 中了木马或病毒的电脑会出现什么现象&#xff1f;具体得看中了什么样的病毒。 但轻则资料泄漏、电脑瘫痪&…

SSL/TLS 握手过程详解

SSL握手过程详解 1、SSL/TLS 历史发展2、SSL/TLS握手过程概览2.1、协商交换密码套件和参数2.2、验证一方或双方的身份2.3、创建/交换对称会话密钥 3、TLS 1.2 握手过程详解4、TLS 1.3 握手过程详解5、The TLS 1.2 handshake – Diffie-Hellman Edition 1、SSL/TLS 历史发展 可…

自然语言转SQL,一个微调ChatGPT3.5的实例(下)--模型微调及模型性能

提交训练集进行微调 一旦我们创建了JSONL文件&#xff08;可以在这里或ipfs_here找到一个小样本&#xff09;&#xff0c;下一步是使用以下命令将创建的文件上传到OpenAI&#xff1a; openai.api_key os.getenv("OPENAI_API_KEY") print(openai.File.create(fileo…

【水浸传感器】软硬件一体水浸监测整套方案远程监测解决各种环境漏水问题

一、痛点分析 在工业生产中&#xff0c;水浸传感器可以安装在数据中心、半导体厂房、输油管道、车间仓库、变电室等易发生水浸的区域。一旦检测到漏水情况&#xff0c;立即发出信号反馈。然而&#xff0c;水浸传感器分散在各个地点&#xff0c;导致管理不集中、不便捷&#xf…

Fiber Golang 中的路由和中间件

掌握 GoLang Fiber 中的路由和中间件艺术&#xff0c;以进行高效的 Web 开发 在网络开发领域中&#xff0c;创建一个有效地路由和管理各种任务的 Web 应用程序至关重要。路由决定了如何处理传入的请求&#xff0c;而中间件在执行任务&#xff0c;如身份验证、日志记录和请求解…

力扣刷题-二叉树-最大二叉树

654.最大二叉树 给定一个不含重复元素的整数数组。一个以此数组构建的最大二叉树定义如下&#xff1a; 二叉树的根是数组中的最大元素。 左子树是通过数组中最大值左边部分构造出的最大二叉树。 右子树是通过数组中最大值右边部分构造出的最大二叉树。 通过给定的数组构建最大…

Dockerfile语法和简单镜像构建

Dockerfile是一个用于定义Docker镜像的文本文件&#xff0c;包含了一系列的指令和参数&#xff0c;用于指示Docker在构建镜像时应该执行哪些操作&#xff0c;例如基于哪个基础镜像、复制哪些文件到镜像中、运行哪些命令等。 Dockerfile文件的内容主要有几个部分组成&#xff0c…

Unity3D入门基础知识汇总

1、参考链接 01.游戏引擎是啥玩意&#xff1f;_哔哩哔哩_bilibili 2、unity概念 它是一套具有完善体系与编辑器的跨平台游戏开发工具&#xff0c;也称之为游戏引擎。游戏引擎是指一些编写好的可重复利用的代码与开发游戏所用的各功能编辑器。 3、unity界面 右上边可以切换布局…

Python 快速合并PDF表格转换输出CSV文件

单位的刷脸考勤机后台系统做得比较差&#xff0c;只能导出每个部门的出勤统计表pdf&#xff0c;格式如下&#xff1a; 近期领导要看所有部门的考勤数据&#xff0c;于是动手快速写了个合并pdf并输出csv文件的脚本。 安装模块 pypdf2&#xff0c;pdfplumber&#xff0c;前者用…

python包chromadb安装失败总结

1&#xff0c;背景&#xff1a; 最近在学习langchain的课程&#xff0c;里面创建自己的知识库的Retrieval模块中&#xff0c;需要用到向量数据库。 所以按照官方的教程&#xff08;vectorstores&#xff09;&#xff0c;准备使用chroma的向量数据库。图片来源 2&#xff0c;问…

有没有可以多渠道报修的维修管理系统?

以前我们买电视、空调这些电器&#xff0c;如果出了故障&#xff0c;一般都是打电话给门店&#xff0c;然后门店就会派人来修理。有的人则会自己找维修工人来维修&#xff0c;一般也是通过电话报修。不过现在是互联网时代&#xff0c;电话报修效率低下、信息传递不畅、实时跟踪…

mysql死锁排查

查看正在进行中的事务 SELECT * FROM information_schema.INNODB_TRX;字段解释trx_id唯一事务id号&#xff0c;只读事务和非锁事务是不会创建id的trx_state事务的执行状态&#xff0c;值一般分为&#xff1a;RUNNING, LOCK WAIT, ROLLING BACK, and COMMITTING.trx_started事务…

用户管理第2节课--idea 2023.2 后端--实现基本数据库操作(操作user表) -- 自动生成 --合并生成后的代码【鱼皮】

一、模块页面功能 1.1 domain 【实体对象】 1.2 mapper 【操作数据库的对象】--> UserMapper 1&#xff09;UserMapper 其实就是我们用来操作数据库的一个对象 2) 继承了mybatis- plus&#xff0c;它会自动帮我们去定义一些增删改查的方法。 继承可以看下图&#xf…

ROS 系列学习教程(总目录)

ROSLearning 一、ROS概览 1.1 ROS简介 To be continued… 1.2 ROS安装 Ubuntu 安装 ROS 详细教程&#xff08;以最后一个ROS1版本Noetic为例&#xff09; 1.3 ROS Hello World ROS创建工作空间添加包并编译 ROS Hello World 1.4 ROS架构 ROS架构&#xff1a;文件系统 …

[数据库] MySQL之MHA高可用

一、MHA相关知识 1.1 什么是mha MHA&#xff08;MasterHigh Availability&#xff09;是一套优秀的MySQL高可用环境下故障切换和主从复制的软件。 MHA 的出现就是解决MySQL 单点的问题。 MySQL故障切换过程中&#xff0c;MHA能做到0-30秒内自动完成故障切换操作。 MHA能在故障…

深入理解CRON表达式:时间调度的艺术

&#x1f604; 19年之后由于某些原因断更了三年&#xff0c;23年重新扬帆起航&#xff0c;推出更多优质博文&#xff0c;希望大家多多支持&#xff5e; &#x1f337; 古之立大事者&#xff0c;不惟有超世之才&#xff0c;亦必有坚忍不拔之志 &#x1f390; 个人CSND主页——Mi…

《作家天地》期刊投稿邮箱投稿方式

《作家天地》是国家新闻出版总署批准的正规文学刊物。对各种流派的作品兼收并蓄&#xff0c;力求题材、形式、风格多样化&#xff0c;适用于发表高品质文学学术作品&#xff0c;科研机构的专家学者以及高等院校的师生等。具有原创性的学术理论、工作实践、科研成果和科研课题及…

javascript 常见工具函数(二)

11.数组等分切片&#xff1a; this.newMapList []; for (var i 0; i < this.mapDataList.length; i 2) {this.newMapList.push(this.mapDataList.slice(i, i 2)); } 12.js做奇偶判断&#xff1a; if (this.mapDataList.length ! 0) {this.mapDataList.length % 2 0 ?…

从0到1入门C++编程——03 内存分区、引用、函数高级应用

文章目录 一、内存分区二、引用三、函数的高级应用1.默认参数2.占位参数3.函数重载 一、内存分区 C程序在执行时&#xff0c;会将内存大致分为4个区&#xff0c;分别是代码区、全局区、栈区和堆区。 代码区用来存放函数体和二进制代码&#xff0c;由操作系统进行管理。 全局区…