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" …...
浏览器访问 AWS ECS 上部署的 Docker 容器(监听 80 端口)
✅ 一、ECS 服务配置 Dockerfile 确保监听 80 端口 EXPOSE 80 CMD ["nginx", "-g", "daemon off;"]或 EXPOSE 80 CMD ["python3", "-m", "http.server", "80"]任务定义(Task Definition&…...

地震勘探——干扰波识别、井中地震时距曲线特点
目录 干扰波识别反射波地震勘探的干扰波 井中地震时距曲线特点 干扰波识别 有效波:可以用来解决所提出的地质任务的波;干扰波:所有妨碍辨认、追踪有效波的其他波。 地震勘探中,有效波和干扰波是相对的。例如,在反射波…...

Linux 文件类型,目录与路径,文件与目录管理
文件类型 后面的字符表示文件类型标志 普通文件:-(纯文本文件,二进制文件,数据格式文件) 如文本文件、图片、程序文件等。 目录文件:d(directory) 用来存放其他文件或子目录。 设备…...

大型活动交通拥堵治理的视觉算法应用
大型活动下智慧交通的视觉分析应用 一、背景与挑战 大型活动(如演唱会、马拉松赛事、高考中考等)期间,城市交通面临瞬时人流车流激增、传统摄像头模糊、交通拥堵识别滞后等问题。以演唱会为例,暖城商圈曾因观众集中离场导致周边…...

【大模型RAG】Docker 一键部署 Milvus 完整攻略
本文概要 Milvus 2.5 Stand-alone 版可通过 Docker 在几分钟内完成安装;只需暴露 19530(gRPC)与 9091(HTTP/WebUI)两个端口,即可让本地电脑通过 PyMilvus 或浏览器访问远程 Linux 服务器上的 Milvus。下面…...

【快手拥抱开源】通过快手团队开源的 KwaiCoder-AutoThink-preview 解锁大语言模型的潜力
引言: 在人工智能快速发展的浪潮中,快手Kwaipilot团队推出的 KwaiCoder-AutoThink-preview 具有里程碑意义——这是首个公开的AutoThink大语言模型(LLM)。该模型代表着该领域的重大突破,通过独特方式融合思考与非思考…...

第一篇:Agent2Agent (A2A) 协议——协作式人工智能的黎明
AI 领域的快速发展正在催生一个新时代,智能代理(agents)不再是孤立的个体,而是能够像一个数字团队一样协作。然而,当前 AI 生态系统的碎片化阻碍了这一愿景的实现,导致了“AI 巴别塔问题”——不同代理之间…...

HBuilderX安装(uni-app和小程序开发)
下载HBuilderX 访问官方网站:https://www.dcloud.io/hbuilderx.html 根据您的操作系统选择合适版本: Windows版(推荐下载标准版) Windows系统安装步骤 运行安装程序: 双击下载的.exe安装文件 如果出现安全提示&…...

【Java_EE】Spring MVC
目录 Spring Web MVC 编辑注解 RestController RequestMapping RequestParam RequestParam RequestBody PathVariable RequestPart 参数传递 注意事项 编辑参数重命名 RequestParam 编辑编辑传递集合 RequestParam 传递JSON数据 编辑RequestBody …...
[Java恶补day16] 238.除自身以外数组的乘积
给你一个整数数组 nums,返回 数组 answer ,其中 answer[i] 等于 nums 中除 nums[i] 之外其余各元素的乘积 。 题目数据 保证 数组 nums之中任意元素的全部前缀元素和后缀的乘积都在 32 位 整数范围内。 请 不要使用除法,且在 O(n) 时间复杂度…...