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

springboot利用切面保存操作日志(支持Spring表达式语言(简称SpEL))

springboot利用切面保存操作日志(支持Spring表达式语言(简称SpEL))


文章目录

  • springboot利用切面保存操作日志(支持Spring表达式语言(简称SpEL))
  • 前言
  • 一、Spring EL是什么?
  • 二、使用步骤
    • 1.定义日志实体类LogRecord
    • 2.定义日志记录注解LogSnipper
    • 3.定义上下文容器SnipperContext
    • 4.实现切面
    • 5.定义日志模板解析器LogTplParser
    • 6.定义业务代码


前言

在注解中使用Spring EL表达式,切面解析实现自定义操作日志。
在这里插入图片描述


一、Spring EL是什么?

Spring表达式语言(简称SpEL)是一个支持查询并在运行时操纵一个对象图的功能强大的表达式语言。SpEL语言的语法类似于统一EL,但提供了更多的功能,最主要的是显式方法调用和基本字符串模板函数。参考:SpringEL 表达式语言(Spring Expression Language)

二、使用步骤

1.定义日志实体类LogRecord

/*** 日志实体类,属性对应{@link LogSnipper}注解中的属性*/
@Data
@Builder
public class LogRecord {private String code;/*** 客户端ip*/private String ipAddr;/*** 业务编号(可以是任何标识)*/private String bizNo;/*** 对应{@link LogSnipper}注解中的success或者fail,当方法成功调用时,获取success中的值,反之则获取fail中的值*/private String content;/*** 日志类型*/private String category;/*** 操作者*/private String operator;/*** 备用字段,当其他字段不够用时可以使用该字段*/private String addition;/*** 操作时间*/private Date createTime;}

2.定义日志记录注解LogSnipper

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@Documented
public @interface LogSnipper {/*** 操作人* @return*/String operator() default "";/*** 操作成功时定义的模板* @return*/String success();/*** 操作失败时定义的模板* @return*/String fail() default "";/*** 业务主键* @return*/String bizNo() default "";/*** 日志类型* @return*/String category();/*** 其他信息,用于保存其他额外信息** @return*/String addition() default "";}

3.定义上下文容器SnipperContext

/*** 上下文容器,用来存放线程处理过程中的信息* <p>*/
public class SnipperContext {private static ThreadLocal<Map<String, Object>> context = new ThreadLocal<>();static {context.set(new HashMap<>());}public static Map<String, Object> getContext() {return context.get();}public static void set(String key, Object val) {Map<String, Object> map = new HashMap<>();map.put(key, val);context.set(map);}public static Object get(String key) {return context.get().get(key);}public static void clear() {context.remove();}}

4.实现切面

/*** 日志记录切面**/
@Aspect
public class LogAspect {@Autowiredprivate LogTplParser logTplParser;@Autowired(required = false)private LogPersistent logPersistent;@Pointcut("@annotation(com.sztech.logsnipper.LogSnipper)")public void snip(){}/*** 正常执行方法,执行success模板*/@AfterReturning(pointcut = "snip()", returning = "returnValue")public void doAfter(JoinPoint joinPoint, Object returnValue) {//获取注解的实例LogSnipper logSnipper = getLogSnipper(joinPoint);String success = logSnipper.success();//表达式上下文,将方法中的参数设置到spel容器中EvaluationContext evaluationContext = generateEvaluationContext(joinPoint);/*** 将方法的返回结果设置进内置变量中* 其中_rs_对应为返回值* _ex_对应异常原因* _context_对应上下文内容,为map类型**/evaluationContext.setVariable(LogTplParser._RS_, returnValue);evaluationContext.setVariable(LogTplParser._CONTEXT, SnipperContext.getContext());saveLog(logSnipper, success, evaluationContext);}/*** 非正常执行,执行fail模板*/@AfterThrowing(pointcut = "snip()", throwing = "exception")public void doFail(JoinPoint joinPoint, Exception exception) {LogSnipper logSnipper = getLogSnipper(joinPoint);String fail = logSnipper.fail();/*** 将方法的返回结果设置进内置变量中* 其中_rs_对应为返回值* _ex_对应异常原因* _context_对应上下文内容,为map类型**/EvaluationContext evaluationContext = generateEvaluationContext(joinPoint);evaluationContext.setVariable(LogTplParser._EX_, exception);evaluationContext.setVariable(LogTplParser._CONTEXT, SnipperContext.getContext());saveLog(logSnipper, fail, evaluationContext);}/*** 保存日志* @param logSnipper* @param tpl* @param evaluationContext*/private void saveLog(LogSnipper logSnipper, String tpl, EvaluationContext evaluationContext) {String content = logTplParser.parseTpl(tpl, evaluationContext);String operator = logTplParser.parseTpl(logSnipper.operator(), evaluationContext);String bizNo = logTplParser.parseTpl(logSnipper.bizNo(), evaluationContext);String category = logTplParser.parseTpl(logSnipper.category(), evaluationContext);String addition = logTplParser.parseTpl(logSnipper.addition(), evaluationContext);LogRecord logRecord =LogRecord.builder().content(content).code(IdUtil.simpleUUID()).operator(operator).bizNo(bizNo).category(category).addition(addition).createTime(new Date()).build();if(null != logPersistent) {//实现日志存储logPersistent.save(logRecord);}}/*** 将方法中的参数设置到spel容器中** @param joinPoint* @return*/private EvaluationContext generateEvaluationContext(JoinPoint joinPoint) {EvaluationContext evaluationContext = new StandardEvaluationContext();//获取方法参数值Object[] args = joinPoint.getArgs();//获取方法参数名String[] parameterNames = ((MethodSignature) joinPoint.getSignature()).getParameterNames();int i = 0;for(Object arg : args) {evaluationContext.setVariable(parameterNames[i++], arg);}return evaluationContext;}/*** 获取方法{@link LogSnipper}的注解实例* @param joinPoint* @return*/private LogSnipper getLogSnipper(JoinPoint joinPoint) {MethodSignature signature = (MethodSignature) joinPoint.getSignature();LogSnipper logSnipper = signature.getMethod().getAnnotation(LogSnipper.class);return logSnipper;}/*** 最后执行,用于释放资源*/@After("snip()")public void finish() {//释放上下文SnipperContext.clear();}
}

5.定义日志模板解析器LogTplParser

/*** 日志模板解析器**/
@Component
public class LogTplParser {private ExpressionParser expressionParser = new SpelExpressionParser();/*** 通配符正则表达式:{#user.name},user为实例对象,name为对象属性,也可以使用getName()方法*/private static final String expressionRegex = "\\{#.*?}";public static final String _CONTEXT = "_context_";public static final String _EX_ = "_ex_";public static final String _RS_ = "_rs_";private static final Pattern pattern = Pattern.compile(expressionRegex);/*** 解析Spel模板字符串** @param tpl 模板字符串,例如:"操作人:{#username}, 操作模块:{#module.name}"等,使用了spring的spel表达式* @param context 对象容器,将spel表达式对应的值或者对象呢set进容器中* @return*/public String parseTpl(String tpl, EvaluationContext context) {Matcher matcher = pattern.matcher(tpl);List<String> sPelList = new ArrayList<>();//匹配正则表达式,可能会出现匹配到多个while (matcher.find()) {sPelList.add(matcher.group());}//如果没有匹配到通配符,直接返回原字符串if(sPelList.size() == 0) {return tpl;}String[] datas = new String[sPelList.size()];int i = 0;//从context中取出通配符对应的值for(String sp : sPelList) {Expression expression = expressionParser.parseExpression(sp);datas[i++] = expression.getValue(context, String.class);}//格式化字符串,并将通配符的值填充到字符串中String formatStr = tpl.replaceAll(expressionRegex, "%s");return String.format(formatStr, datas);}}

6.定义业务代码

@RestController
public class AController {@Autowiredprivate AService aService;@GetMapping("/sayBye")public String sayBye(String msg) {User user = new User();user.setName("李白");user.setAge(12);user.setDepartment("001");SnipperContext.set("currentUser", user);SnipperContext.set("nickName", "汪沦");return aService.sayBye(msg, user);}}@Component
public class aService {@LogSnipper(category = "打招呼行为",success = "{#_context_.get(\"nickName\")}向{#user.name}说:{#msg}",fail = "{#user.name}有事,请下次再来",bizNo = "{#user.department}",operator = "{#_context_.get(\"nickName\")}")public String sayBye(String msg, User user) {return "sayBye";}
}

相关文章:

springboot利用切面保存操作日志(支持Spring表达式语言(简称SpEL))

springboot利用切面保存操作日志&#xff08;支持Spring表达式语言&#xff08;简称SpEL&#xff09;&#xff09; 文章目录 springboot利用切面保存操作日志&#xff08;支持Spring表达式语言&#xff08;简称SpEL&#xff09;&#xff09;前言一、Spring EL是什么&#xff1f…...

遂宁专业知识付费系统报价,免费网课平台怎么开通?需要哪些条件?

其实&#xff0c;不少的大咖老师都不愿意在大平台上开课&#xff0c;因为学员的留存并不是自己的&#xff0c;所以&#xff0c;很多人也考虑自己开通网课平台&#xff0c;那免费的平台怎么开通?这就是我们今天要跟老师们分享的内容了。 需要哪些条件? 大家如果想要开通免费的…...

【linuxC语言】fcntl和ioctl函数

文章目录 前言一、功能介绍二、具体使用2.1 fcntl函数2.2 ioctl函数三、拓展:填写arg总结前言 在Linux系统编程中,经常会涉及到对文件描述符、套接字以及设备的控制操作。fcntl和ioctl函数就是用来进行这些控制操作的两个重要的系统调用。它们提供了对文件、设备和套接字进行…...

java——继承(一)

一&#xff1a;匿名对象 只能使用一次&#xff0c;每一次使用都会创建一个新的对象&#xff0c;默认值和数组的默认值的规则相同。所以适用于调用一次对象的情况&#xff1a; public class ClassAnonymous {String name;public void show(){System.out.println(name"真厉…...

【Linux】进程间通信方式之管道

&#x1f916;个人主页&#xff1a;晚风相伴-CSDN博客 &#x1f496;如果觉得内容对你有帮助的话&#xff0c;还请给博主一键三连&#xff08;点赞&#x1f49c;、收藏&#x1f9e1;、关注&#x1f49a;&#xff09;吧 &#x1f64f;如果内容有误的话&#xff0c;还望指出&…...

【Linux】yum与vim

文章目录 软件包管理器&#xff1a;yumLinux安装和卸载软件包Linux中的编辑器&#xff1a;vimvim下的底行模式vim下的正常模式vim下的替换模式vim下的视图模式vim下的多线程 软件包管理器&#xff1a;yum yum其实就是一个软件,也可以叫商店 和你手机上的应用商店或app store一…...

苍穹外卖Day06笔记

疯玩了一个月&#xff0c;效率好低&#xff0c;今天开始捡起来苍穹外卖~ 1. 为什么不需要单独引入HttpClient的dependency&#xff1f; 因为我们在sky-common的pom.xml中已经引入了aliyun-sdk-oss的依赖&#xff0c;而这个依赖低层就引入了httpclinet的依赖&#xff0c;根据依…...

Maximo 使用 REST API 创建 Cron Task

接前面几篇文章&#xff0c;我没有了 automation script 以后&#xff0c;有时候需要让其定期自动执行&#xff0c;这时候就可以通过 Cron Task 来实现了。 通过Maximo REST API 来创建 Cron Task request&#xff1a; POST {{base_url}}/api/os/mxapicrontaskdef?apikey{{…...

【镜像仿真篇】磁盘镜像仿真常见错误

【镜像仿真篇】磁盘镜像仿真常见错误 记系统镜像仿真常见错误集—【蘇小沐】 1、实验环境 2023AFS39.E01&#xff08;Windows11系统镜像&#xff09;Arsenal Image Mounter&#xff0c;[v3.10.262]‍Vmware Workstation 17 Pro&#xff0c;[v17.5.1]Windows 11 专业工作站版…...

代码随想录算法训练营DAY45|C++动态规划Part7|70.爬楼梯(进阶版)、322. 零钱兑换、279.完全平方数

文章目录 70.爬楼梯&#xff08;进阶版&#xff09;⭐️322. 零钱兑换思路CPP代码总结 279.完全平方数思路CPP代码 70.爬楼梯&#xff08;进阶版&#xff09; 卡码网&#xff1a;57. 爬楼梯 文章讲解&#xff1a;70.爬楼梯(进阶版) 本题就是典型了完全背包排列问题&#xff0c;…...

Linux(openEuler、CentOS8)企业内网DHCP服务器搭建(固定Mac获取指定IP)

----本实验环境为openEuler系统<以server方式安装>&#xff08;CentOS8基本一致&#xff0c;可参考本文&#xff09;---- 目录 一、知识点二、实验&#xff08;一&#xff09;为服务器配置网卡和IP&#xff08;二&#xff09;为服务器安装DHCP服务软件&#xff08;三&a…...

c#读取hex文件方法,相对来说比较清楚

Hex文件解读_c#读取hex文件-CSDN博客 https://wenku.csdn.net/answer/d67f30cf834c435ca37c3d1ef5e78a62?ops_request_misc%257B%2522request%255Fid%2522%253A%2522171498156816800227423661%2522%252C%2522scm%2522%253A%252220140713.130102334.pc%255Fall.%2522%257D&…...

【ytb数据采集器】按关键词批量爬取视频数据,界面软件更适合文科生!

一、背景介绍 1.1 爬取目标 用Python独立开发的爬虫工具&#xff0c;作用是&#xff1a;通过搜索关键词采集油管的搜索结果&#xff0c;包含14个关键字段&#xff1a;关键词,页码,视频标题,视频id,视频链接,发布时间,视频时长,频道名称,频道id,频道链接,播放数,点赞数,评论数…...

三条命令快速配置Hugging Face

大家好啊&#xff0c;我是董董灿。 本文给出一个配置Hugging Face的方法&#xff0c;让你在国内可快速从Hugging Face上下在模型和各种文件。 1. 什么是 Hugging Face Hugging Face 本身是一家科技公司&#xff0c;专注于自然语言处理&#xff08;NLP&#xff09;和机器学习…...

Python网络编程 03 实验:FTP详解

文章目录 一、小实验FTP程序需求二、项目文件架构三、服务端1、conf/settings.py2、conf/accounts.cgf3、conf/STATUS_CODE.py4、启动文件 bin/ftp_server.py5、core/main.py6、core/server.py 四、客户端1、conf/STATUS_CODE.py2、bin/ftp_client.py 五、在终端操作示例 一、小…...

个人银行账户管理程序(2)

在&#xff08;1&#xff09;的基础上进行改进 1&#xff1a;增加一个静态成员函数total&#xff0c;记录账户总金额和静态成员函数getTotal 2对不需要改变的对象进行const修饰 3多文件实现 account。h文件 #ifndef _ACCOUNT_ #define _ACCOUNT_ class SavingAccount {pri…...

2024.04.19校招 实习 内推 面经

绿*泡*泡VX&#xff1a; neituijunsir 交流*裙 &#xff0c;内推/实习/校招汇总表格 1、校招&转正实习 | 美团无人机业务部招聘&#xff08;内推&#xff09; 校招&转正实习 | 美团无人机业务部招聘&#xff08;内推&#xff09; 2、校招&实习 | 快手 这些岗位…...

Python并发编程 04 进程与线程基础

文章目录 一、操作系统简介二、进程三、线程四、线程的调用1、示例2、join方法3、setDaemon方法4、继承式调用&#xff08;不推荐&#xff09;5、其他方法 一、操作系统简介 ①操作系统是一个用来协调、管理和控制计算机硬件和软件资源的系统程序&#xff0c;它位于硬件和应用…...

模板引擎Freemarker

什么是模板引擎 根据前边的数据模型分析&#xff0c;课程预览就是把课程的相关信息进行整合&#xff0c;在课程预览界面进行展示&#xff0c;课程预览界面与课程发布的课程详情界面一致。 项目采用模板引擎技术实现课程预览界面。什么是模板引擎&#xff1f; 早期我们采用的…...

刷题训练之模拟

> 作者&#xff1a;დ旧言~ > 座右铭&#xff1a;松树千年终是朽&#xff0c;槿花一日自为荣。 > 目标&#xff1a;熟练掌握模拟算法。 > 毒鸡汤&#xff1a;学习&#xff0c;学习&#xff0c;再学习 ! 学&#xff0c;然后知不足。 > 专栏选自&#xff1a;刷题训…...

设计模式和设计原则回顾

设计模式和设计原则回顾 23种设计模式是设计原则的完美体现,设计原则设计原则是设计模式的理论基石, 设计模式 在经典的设计模式分类中(如《设计模式:可复用面向对象软件的基础》一书中),总共有23种设计模式,分为三大类: 一、创建型模式(5种) 1. 单例模式(Sing…...

高等数学(下)题型笔记(八)空间解析几何与向量代数

目录 0 前言 1 向量的点乘 1.1 基本公式 1.2 例题 2 向量的叉乘 2.1 基础知识 2.2 例题 3 空间平面方程 3.1 基础知识 3.2 例题 4 空间直线方程 4.1 基础知识 4.2 例题 5 旋转曲面及其方程 5.1 基础知识 5.2 例题 6 空间曲面的法线与切平面 6.1 基础知识 6.2…...

【2025年】解决Burpsuite抓不到https包的问题

环境&#xff1a;windows11 burpsuite:2025.5 在抓取https网站时&#xff0c;burpsuite抓取不到https数据包&#xff0c;只显示&#xff1a; 解决该问题只需如下三个步骤&#xff1a; 1、浏览器中访问 http://burp 2、下载 CA certificate 证书 3、在设置--隐私与安全--…...

反射获取方法和属性

Java反射获取方法 在Java中&#xff0c;反射&#xff08;Reflection&#xff09;是一种强大的机制&#xff0c;允许程序在运行时访问和操作类的内部属性和方法。通过反射&#xff0c;可以动态地创建对象、调用方法、改变属性值&#xff0c;这在很多Java框架中如Spring和Hiberna…...

PL0语法,分析器实现!

简介 PL/0 是一种简单的编程语言,通常用于教学编译原理。它的语法结构清晰,功能包括常量定义、变量声明、过程(子程序)定义以及基本的控制结构(如条件语句和循环语句)。 PL/0 语法规范 PL/0 是一种教学用的小型编程语言,由 Niklaus Wirth 设计,用于展示编译原理的核…...

IoT/HCIP实验-3/LiteOS操作系统内核实验(任务、内存、信号量、CMSIS..)

文章目录 概述HelloWorld 工程C/C配置编译器主配置Makefile脚本烧录器主配置运行结果程序调用栈 任务管理实验实验结果osal 系统适配层osal_task_create 其他实验实验源码内存管理实验互斥锁实验信号量实验 CMISIS接口实验还是得JlINKCMSIS 简介LiteOS->CMSIS任务间消息交互…...

Java + Spring Boot + Mybatis 实现批量插入

在 Java 中使用 Spring Boot 和 MyBatis 实现批量插入可以通过以下步骤完成。这里提供两种常用方法&#xff1a;使用 MyBatis 的 <foreach> 标签和批处理模式&#xff08;ExecutorType.BATCH&#xff09;。 方法一&#xff1a;使用 XML 的 <foreach> 标签&#xff…...

Git 3天2K星标:Datawhale 的 Happy-LLM 项目介绍(附教程)

引言 在人工智能飞速发展的今天&#xff0c;大语言模型&#xff08;Large Language Models, LLMs&#xff09;已成为技术领域的焦点。从智能写作到代码生成&#xff0c;LLM 的应用场景不断扩展&#xff0c;深刻改变了我们的工作和生活方式。然而&#xff0c;理解这些模型的内部…...

为什么要创建 Vue 实例

核心原因:Vue 需要一个「控制中心」来驱动整个应用 你可以把 Vue 实例想象成你应用的**「大脑」或「引擎」。它负责协调模板、数据、逻辑和行为,将它们变成一个活的、可交互的应用**。没有这个实例,你的代码只是一堆静态的 HTML、JavaScript 变量和函数,无法「活」起来。 …...

提升移动端网页调试效率:WebDebugX 与常见工具组合实践

在日常移动端开发中&#xff0c;网页调试始终是一个高频但又极具挑战的环节。尤其在面对 iOS 与 Android 的混合技术栈、各种设备差异化行为时&#xff0c;开发者迫切需要一套高效、可靠且跨平台的调试方案。过去&#xff0c;我们或多或少使用过 Chrome DevTools、Remote Debug…...