SpringBoot+AOP+Redis 防止重复请求提交
本文项目基于以下教程的代码版本: https://javaxbfs.blog.csdn.net/article/details/135224261
代码仓库: springboot一些案例的整合_1: springboot一些案例的整合
1、实现步骤
2.引入依赖
我们需要redis、aop的依赖。
<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-data-redis</artifactId>
</dependency><dependency><groupId>org.aspectj</groupId><artifactId>aspectjrt</artifactId><version>1.9.6</version>
</dependency><dependency><groupId>org.aspectj</groupId><artifactId>aspectjweaver</artifactId><version>1.9.6</version>
</dependency>
redis的访问参数,默认的就行,不需要特别配置。
3、定义拦截注解
我们最终希望的效果是,你想要哪个方法有防止重复提交的功能,直接加上@RepeatSubmit
注解即可。 代码如下:
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface RepeatSubmit {/*** 加锁过期时间,默认是5秒* @return*/long lockTime() default 5L;
}
这段代码定义了一个Java注解(Annotation)叫做RepeatSubmit。注解是Java提供的一种元数据机制,它可以被用于为代码提供附加的信息,这些信息可以被编译器用于生成代码、生成文档、代码检查等。
下面是对这段代码的详细解释:
-
@Target(ElementType.METHOD)
: 这个注解指定RepeatSubmit只能被用于方法上。ElementType.METHOD表示这个注解只能用于方法。 -
@Retention(RetentionPolicy.RUNTIME)
: 这个注解指定了RepeatSubmit的保留策略是RUNTIME。这意味着在运行时,这个注解仍然可以被读取。 -
@Documented
: 这个注解表明RepeatSubmit应当被Java文档生成器包含在生成文档中。 -
public @interface RepeatSubmit
: 定义了一个名为RepeatSubmit的公共注解。 -
long lockTime() default 5L
: 这是RepeatSubmit注解的一个元素,名为lockTime。这个元素返回一个长整型值,并且有一个默认值5秒(5L表示5秒)。
这个注解是为了防止方法的重复提交,并提供了一个默认的加锁过期时间为5秒。如果一个方法被这个注解标记,那么在特定的加锁过期时间内,这个方法只会被执行一次。
4、实现防止重复提交切面
关于Spring Aop切面,可以看我之前的文章。
NoRepeatSubmitAspect
package com.it.demo.aspect;import cn.hutool.core.util.StrUtil;
import cn.hutool.crypto.digest.DigestUtil;
import com.it.demo.annotation.RepeatSubmit;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import java.util.Objects;
import java.util.concurrent.TimeUnit;@Component
@Slf4j
@Aspect
public class NoRepeatSubmitAspect {@Resourceprivate RedisTemplate redisTemplate;@Pointcut("@annotation(repeatSubmit)")public void pointCutNoRepeatSubmitAspect(RepeatSubmit repeatSubmit) {}@Around("pointCutNoRepeatSubmitAspect(repeatSubmit)")public Object around(ProceedingJoinPoint joinPoint,RepeatSubmit repeatSubmit) throws Throwable {String reqSignature = buildReqSignature(joinPoint);//加锁时间long lockTime = repeatSubmit.lockTime();Boolean res = redisTemplate.opsForValue().setIfAbsent(reqSignature, 1, lockTime, TimeUnit.SECONDS);if(!res){throw new RuntimeException("重复请求!");}return joinPoint.proceed();}/*** 构建请求签名* @param joinPoint* @return*/private String buildReqSignature(ProceedingJoinPoint joinPoint) {ServletRequestAttributes requestAttributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();if(Objects.isNull(requestAttributes)){throw new RuntimeException("请求参数异常!");}HttpServletRequest request = requestAttributes.getRequest();String remoteAddr =request.getRemoteAddr() + ":" + request.getRemoteHost();String method = request.getMethod();String requestURI = request.getRequestURI();StringBuffer sb = new StringBuffer();for (Object arg : joinPoint.getArgs()) {sb.append(arg);}//请求签名(MD5加密)return DigestUtil.md5Hex(StrUtil.format("{}-{}-{}-{}",remoteAddr,method,requestURI,sb));}
}
这段代码是一个切面(Aspect),用于处理重复提交的情况。以下是对代码中的主要部分的解释:
-
导入包及注解:代码首先导入了一些类,并使用了一些注解,例如
@Component
,@Aspect
,@Resource
,@Slf4j
等。 -
NoRepeatSubmitAspect 类:这是一个切面类,用于实现对重复提交的处理逻辑。
-
成员变量:代码中使用了
@Resource
注解注入了一个RedisTemplate
对象,用于操作 Redis。 -
切入点:使用
@Pointcut
注解定义了一个切入点,这里使用了@annotation(repeatSubmit)
表达式,表示会拦截所有标注了@RepeatSubmit
注解的方法。 -
Around 通知:使用
@Around
注解定义了一个环绕通知,在拦截的方法执行前后会执行这里定义的逻辑。 -
around() 方法:在该方法中,首先调用了
buildReqSignature(joinPoint)
方法构建了请求的签名信息,然后根据RepeatSubmit
注解中的配置,在 Redis 中设置了一个锁,以防止重复提交。如果设置锁失败,则抛出了一个运行时异常,表示重复请求,否则执行被拦截的方法。 -
buildReqSignature() 方法:该方法用于构建请求的签名信息,首先获取了请求相关的信息,然后将这些信息进行拼接,并使用 MD5 加密生成请求签名。
这段代码是一个使用 AOP 实现的防止重复提交的切面,在方法执行前通过生成请求签名并利用 Redis 进行锁定的方式,来避免重复提交。
环绕通知实现逻辑如下图所示
构建 Redis 加锁需要使用的 key,加锁key由指定前缀 + MD5(请求签名)组成,对请求签名进行MD5,一方面减少Key的长度,另一方面保证签名信息中的敏感信息不可见,其实现逻辑如下图所示:
/*** 构建请求签名* @param joinPoint* @return*/
private String buildReqSignature(ProceedingJoinPoint joinPoint) {ServletRequestAttributes requestAttributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();if(Objects.isNull(requestAttributes)){throw new RuntimeException("请求参数异常!");}HttpServletRequest request = requestAttributes.getRequest();String remoteAddr =request.getRemoteAddr() + ":" + request.getRemoteHost();String method = request.getMethod();String requestURI = request.getRequestURI();StringBuffer sb = new StringBuffer();for (Object arg : joinPoint.getArgs()) {sb.append(arg);}//请求签名(MD5加密)return DigestUtil.md5Hex(StrUtil.format("{}-{}-{}-{}",remoteAddr,method,requestURI,sb));
}
5、 测试
查询方法加一个@RepeatSubmit
@GetMapping("/list")
@RepeatSubmit
public List<Device> list(){List<Device> list = deviceService.list();System.out.println(list);return deviceService.list();
}
访问localhost:8080/v1/device/list
五秒内再访问,报错:
{"code": 400,"msg": "重复请求!","data": null
}
验证通过。
相关文章:

SpringBoot+AOP+Redis 防止重复请求提交
本文项目基于以下教程的代码版本: https://javaxbfs.blog.csdn.net/article/details/135224261 代码仓库: springboot一些案例的整合_1: springboot一些案例的整合 1、实现步骤 2.引入依赖 我们需要redis、aop的依赖。 <dependency><groupId>org.spr…...
偷流量、端口占用、网络负载高、socket创建释放异常等Android高阶TCP/IP网络问题定位思路
一,背景 通常一些偷流量、端口占用、网络负载高、socket创建释放异常等Android网络相关问题,可以通过使用tcpdump抓tcp/ip报文,来定位。但是tcpdump无进程信息,也没有APK包名信息,无法确认异常的报文来自哪些Apk或者n…...
《人人都能用英语》学习笔记
https://github.com/xiaolai/everyone-can-use-english 核心: 用 What──它究竟是什么?Why──为什么它是那个样子?How──要掌握它、应用它,必须得遵循什么样的步骤? 在运行程序之前,要反复浏览代码&a…...

NFC与ZigBee技术在智慧农业物联网监测系统中的应用
近年来,我国农业物联网技术飞速发展,基于物联网技术的智能农业监测系统有望得到较大规模的推广应用。但传统的物联网农业监测系统其网络结构层次单一,多采用基于有线或无线结构的节点-上位机数据采集模式,节点数据访问模式缺乏灵活…...

k8s-cni网络 10
Flannel vxlan模式跨主机通信原理 在同一个节点上的pod 流量通过cni网桥可以直接进行转发; 在需要跨主机访问时,数据包通过flannel(隧道) 知道另一边的mac地址,就可以拿到另一边的ip地址,然后构建常规的以太网数据包,…...

听GPT 讲Rust源代码--src/tools(27)
File: rust/src/tools/clippy/clippy_lints/src/methods/suspicious_to_owned.rs 文件rust/src/tools/clippy/clippy_lints/src/methods/suspicious_to_owned.rs的作用是实施Clippy lint规则,检测产生潜在性能问题的字符转换代码,并给出相关建议。 在Rus…...

经济危机下,我们普通人如何翻身?2024创业新风口,适合普通人的创业项目
明年的商业环境会比今年更残酷,不是贩卖危机。旅游行业还在刺激性消费,再过几个月大家就没钱了,估计慢慢也消停。中小微企业资金链断裂,大部分公司倒闭,大批人失业,所以经济恢复需要一个周期。 30年河东&am…...

深入浅出图解C#堆与栈 C# Heap(ing) VS Stack(ing) 第五节 引用类型复制问题及用克隆接口ICloneable修复
深入浅出图解C#堆与栈 C# Heaping VS Stacking 第五节 引用类型复制问题及用克隆接口ICloneable修复 [深入浅出图解C#堆与栈 C# Heap(ing) VS Stack(ing) 第一节 理解堆与栈](https://mp.csdn.net/mdeditor/101021023)[深入浅出图解C#堆与栈 C# Heap(ing) VS Stack(ing) 第二节…...
python中基本元素的pop函数
python中基本元素的pop函数 一、列表List二、元组Tuple三、字典dict四、集合set 一、列表List pop() 根据索引删除并返回被删除的元素,索引默认为-1 a [1, 2, 3, 2, 5] b a.pop() # b5,默认返回最后一个值 print(b) b a.pop(2) # b3,返回a[2] pri…...

MPLS动态协议LDP配置示例
一、预习: MPLS是一种根据报文中携带的标签来转发数据的技术,两台LSR必须在它们之间转的数据 的标签使用上“达成共识”。LSR之间可以运行LDP来告知其他LSR本设备上的标签绑定信息,从而实现标签报文的正确转发。 LSR:Label Switch…...

JS调用栈:为何会栈溢出
JS调用栈:为何会栈溢出 JS调用栈什么是函数调用什么是栈在开发中利用调用栈栈溢出 JS调用栈 JavaScript 经常会出现一个函数中调用另外一个函数的情况,调用栈就是用来管理函数调用关系的一种数据结构,首先你要先弄明白函数调用和栈结构 什么…...
代码随想Day52 | 300.最长递增子序列、674. 最长连续递增序列、718. 最长重复子数组
300.最长递增子序列 这道题目的重点在于动态数组的定义 dp[i]:以nums[i]为结尾的最长递增子序列,因为这样定义可以进行递推; 递推:j从0-i进行对比,如果nums[i]大于nums[j],dp[i]dp[j]1; 初始化…...

使用 pytest 相关特性重构 appium_helloworld
一、前置说明 在 pytest 基础讲解 章节,介绍了 pytest 的特性和基本用法,现在我们可以使用 pytest 的一些机制,来重构 appium_helloworld 。 appium_helloworld 链接: 编写第一个APP自动化脚本 appium_helloworld ,将脚本跑起来 代码目录结构: pytest.ini 设置: [pyt…...

猪目标检测数据集VOC格式600张
猪是一种常见的哺乳动物,通常被人们认为是肉食动物,但实际上猪是杂食性动物,以植物性食物为主,也有偶尔食肉的习性。猪的体型较大,圆胖的体型和圆润的脸庞使其显得憨态可掬。它们主要通过嗅觉来感知周围环境࿰…...

Pandas中concat的用法
Pandas中concat的用法 pd.concat 是 pandas 库中的一个函数,用于将多个 pandas 对象(如 Series、DataFrame)沿指定轴进行合并连接。 pd.concat(objs, axis0, joinouter, ignore_indexFalse, keysNone, levelsNone, namesNone, verify_in…...

【C++】引用详解
前言 在学习C语言时,我们通常会遇到两个数交换的问题,为了实现这一功能,我们会编写一个经典的Swap函数,如下所示: void Swap(int *a, int *b) {int tmp *a;*a *b;*b tmp; } 然而,这个Swap函数看起来可…...
平时的一些思考内容
文章目录 阶乘位运算求概率 阶乘 阶乘是一很迷人的,刚开始的的变化还不是很大,到后面变化类似于直线上升的,不知道现实中哪些实例来表示阶乘。19的阶乘就已经超过了long了,在竞赛或者其他中要求2023或者很大数字的阶乘就需要考虑…...

AIGC时代下,结合ChatGPT谈谈儿童教育
引言 都2024年了,谈到儿童教育,各位有什么新奇的想法嘛 我觉得第一要务,要注重习惯养成,我觉得聊习惯养成这件事情范围有点太大了,我想把习惯归纳于底层逻辑,我们大家都知道,在中国式教育下&a…...
Java中的锁(一)
一、前言 在Java中,锁是用于多线程同步的重要概念。它可以保护共享资源,确保多个线程在访问共享资源时的数据一致性。 共享资源指的是多个线程同时对同一份资源进行访问 (读写操作),被多个线程访问的资源就称为共享资源。 如何保证多个线程访…...

CSS-SVG-环形进度条
线上代码地址 <div class"circular-progress-bar"><svg><circle class"circle-bg" /><circle class"circle-progress" style"stroke-dasharray: calc(2 * 3.1415 * var(--r) * (var(--percent) / 100)), 1000" …...
将对透视变换后的图像使用Otsu进行阈值化,来分离黑色和白色像素。这句话中的Otsu是什么意思?
Otsu 是一种自动阈值化方法,用于将图像分割为前景和背景。它通过最小化图像的类内方差或等价地最大化类间方差来选择最佳阈值。这种方法特别适用于图像的二值化处理,能够自动确定一个阈值,将图像中的像素分为黑色和白色两类。 Otsu 方法的原…...

select、poll、epoll 与 Reactor 模式
在高并发网络编程领域,高效处理大量连接和 I/O 事件是系统性能的关键。select、poll、epoll 作为 I/O 多路复用技术的代表,以及基于它们实现的 Reactor 模式,为开发者提供了强大的工具。本文将深入探讨这些技术的底层原理、优缺点。 一、I…...

初探Service服务发现机制
1.Service简介 Service是将运行在一组Pod上的应用程序发布为网络服务的抽象方法。 主要功能:服务发现和负载均衡。 Service类型的包括ClusterIP类型、NodePort类型、LoadBalancer类型、ExternalName类型 2.Endpoints简介 Endpoints是一种Kubernetes资源…...

七、数据库的完整性
七、数据库的完整性 主要内容 7.1 数据库的完整性概述 7.2 实体完整性 7.3 参照完整性 7.4 用户定义的完整性 7.5 触发器 7.6 SQL Server中数据库完整性的实现 7.7 小结 7.1 数据库的完整性概述 数据库完整性的含义 正确性 指数据的合法性 有效性 指数据是否属于所定…...

R 语言科研绘图第 55 期 --- 网络图-聚类
在发表科研论文的过程中,科研绘图是必不可少的,一张好看的图形会是文章很大的加分项。 为了便于使用,本系列文章介绍的所有绘图都已收录到了 sciRplot 项目中,获取方式: R 语言科研绘图模板 --- sciRplothttps://mp.…...

Matlab实现任意伪彩色图像可视化显示
Matlab实现任意伪彩色图像可视化显示 1、灰度原始图像2、RGB彩色原始图像 在科研研究中,如何展示好看的实验结果图像非常重要!!! 1、灰度原始图像 灰度图像每个像素点只有一个数值,代表该点的亮度(或…...
Linux安全加固:从攻防视角构建系统免疫
Linux安全加固:从攻防视角构建系统免疫 构建坚不可摧的数字堡垒 引言:攻防对抗的新纪元 在日益复杂的网络威胁环境中,Linux系统安全已从被动防御转向主动免疫。2023年全球网络安全报告显示,高级持续性威胁(APT)攻击同比增长65%,平均入侵停留时间缩短至48小时。本章将从…...
【实施指南】Android客户端HTTPS双向认证实施指南
🔐 一、所需准备材料 证书文件(6类核心文件) 类型 格式 作用 Android端要求 CA根证书 .crt/.pem 验证服务器/客户端证书合法性 需预置到Android信任库 服务器证书 .crt 服务器身份证明 客户端需持有以验证服务器 客户端证书 .crt 客户端身份…...
AWS vs 阿里云:功能、服务与性能对比指南
在云计算领域,Amazon Web Services (AWS) 和阿里云 (Alibaba Cloud) 是全球领先的提供商,各自在功能范围、服务生态系统、性能表现和适用场景上具有独特优势。基于提供的引用[1]-[5],我将从功能、服务和性能三个方面进行结构化对比分析&#…...
【大厂机试题解法笔记】矩阵匹配
题目 从一个 N * M(N ≤ M)的矩阵中选出 N 个数,任意两个数字不能在同一行或同一列,求选出来的 N 个数中第 K 大的数字的最小值是多少。 输入描述 输入矩阵要求:1 ≤ K ≤ N ≤ M ≤ 150 输入格式 N M K N*M矩阵 输…...