springboot3使用自定义注解+AOP+redis优雅实现防重复提交

 

⛰️个人主页:     蒾酒

🔥系列专栏:《spring boot实战》

🌊山高路远,行路漫漫,终有归途


目录

写在前面

实现思路

实现步骤

1.定义防重复提交注解

2.编写一个切面去发现该注解然后执行防重复提交逻辑

3.测试

依赖条件

1.接口上标记防重复提交注解

2.接口测试

写在最后


写在前面

本文介绍了springboot开发后端服务中,防重复提交功能的设计与实现,坚持看完相信对你有帮助。

同时欢迎订阅springboot系列专栏,持续分享spring boot的使用经验。

实现思路

通过定义一个防重复提交的自定义注解,再通过AOP的前置通知拦截带有该注解的方法,执行防重复提交逻辑,需要拼接一个唯一的key,如果redis中不存在则代表第一次请求,将这个key存入redis,设置注解类中指定的过期时间,遇到下次重复提交请求,直接抛出对应异常,全局异常处理返回对应信息即可。

需要注意

这个key的生成需要考虑有token和无token情况,同时满足唯一性。

  • 有 token;可以用 token+请求参数,做为唯一值!
  • 无 token:可以用请求路径+请求参数,做为唯一值!

实现步骤

1.定义防重复提交注解

import java.lang.annotation.*;
import java.util.concurrent.TimeUnit;/*** @author mijiupro*/
@Inherited
@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface RepeatSubmit {/*** 锁定时间,默认5000毫秒*/int interval() default 5000;/*** 锁定时间单位,默认毫秒*/TimeUnit timeUnit() default TimeUnit.MILLISECONDS;/*** 提示信息*/String message() default "不允许重复提交,请稍后再试!";}

2.编写一个切面去发现该注解然后执行防重复提交逻辑

因为缓存的key有拼接请求参数,所以遇到文件类型的参数需要进行过滤,拼接逻辑以及参数过滤方法都在下面代码中。

import cn.hutool.crypto.SecureUtil;
import cn.hutool.json.JSONUtil;
import com.mijiu.commom.aop.annotation.RepeatSubmit;
import com.mijiu.commom.exception.GeneralBusinessException;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Component;
import org.springframework.validation.BindingResult;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import org.springframework.web.multipart.MultipartFile;import java.util.Collection;
import java.util.Map;
import java.util.Objects;/*** @author mijiupro*/
@Aspect
@Component
@Slf4j
public class RepeatSubmitAspect {private final StringRedisTemplate redisTemplate;public RepeatSubmitAspect(StringRedisTemplate redisTemplate) {this.redisTemplate = redisTemplate;}@Before("@annotation(repeatSubmit)")public void before(JoinPoint joinPoint, RepeatSubmit repeatSubmit) {ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();HttpServletRequest request = null;if (attributes != null) {request = attributes.getRequest();}//请求参数拼接String requestParams = argsArrayToString(joinPoint.getArgs());String authorizationHeader = null;if (request != null) {authorizationHeader = request.getHeader("Authorization");}String submitKey = null;if (authorizationHeader != null) {//如果存在token则通过token+请求参数生成唯一标识String token = StringUtils.removeStart(authorizationHeader, "Bearer ");submitKey= SecureUtil.md5(token+":"+requestParams);} else{//不存在token则通过请求url+参数生成唯一标识if (request != null) {submitKey = SecureUtil.md5(request.getRequestURL().toString()+":"+requestParams);}}//缓存keyString cacheKey = "repeat_submit:"+submitKey;if (Boolean.TRUE.equals(redisTemplate.hasKey(cacheKey))) {throw new GeneralBusinessException(repeatSubmit.message());}redisTemplate.opsForValue().set(cacheKey, "1", repeatSubmit.interval(), repeatSubmit.timeUnit());}/*** 参数拼接* @param args  参数数组* @return 拼接后的字符串*/private String argsArrayToString(Object[] args){StringBuilder params = new StringBuilder();if(args!= null && args.length > 0){for(Object o:args){if(Objects.nonNull(o)&&!isFilterObject(o)){try {params.append(JSONUtil.toJsonStr(o)).append(" ");}catch (Exception e){log.error("参数拼接异常:{}",e.getMessage());}}}}return params.toString().trim();}/*** 判断是否需要过滤的对象。* @param o  对象* @return true:需要过滤;false:不需要过滤*/private boolean isFilterObject(final Object o) {Class<?> c = o.getClass();//如果是数组且类型为文件类型的需要过滤if(c.isArray()){return  c.getComponentType().isAssignableFrom(MultipartFile.class);}//如果是集合且类型为文件类型的需要过滤else if(Collection.class.isAssignableFrom(c)){Collection collection = (Collection) o;for(Object value:collection){return value instanceof MultipartFile;}}//如果是Map且类型为文件类型的需要过滤else if(Map.class.isAssignableFrom(c)){Map map = (Map) o;for(Object value:map.entrySet()){Map.Entry entry = (Map.Entry) value;return entry.getValue() instanceof MultipartFile;}}//如果是文件类型的需要过滤return o instanceof MultipartFile || o instanceof HttpServletRequest || o instanceof HttpServletResponse|| o instanceof BindingResult;}}

3.测试

依赖条件

redis:

Spring Boot3整合Redis_springboot3整合redis-CSDN博客icon-default.png?t=N7T8https://blog.csdn.net/qq_62262918/article/details/136067550?spm=1001.2014.3001.5502

全局异常捕获:

Spring Boot3自定义异常及全局异常捕获_全局异常捕获 自定义异常-CSDN博客icon-default.png?t=N7T8https://blog.csdn.net/qq_62262918/article/details/136110267?spm=1001.2014.3001.5502

swagger3:

Spring Boot3整合knife4j(swagger3)_springboot3 knife4j-CSDN博客icon-default.png?t=N7T8https://blog.csdn.net/qq_62262918/article/details/135761392?spm=1001.2014.3001.5502

hutool工具包:

<dependency><groupId>cn.hutool</groupId><artifactId>hutool-all</artifactId><version>5.8.25</version>
</dependency>

1.接口上标记防重复提交注解

随便写个测试接口添加防重复提交注解设置间隔5000毫秒

    @PostMapping("/add")@RepeatSubmit(interval= 5000)public void test(@RequestBody User user){//添加用户的操作逻辑。。。}

2.接口测试

第一次提交

可以看到对应缓存已经存入redis了

5s内第二次提交

写在最后

springboot使用自定义注解+AOP+redis优雅实现防重复提交到这里就结束了,本文介绍了一种通用的防重复提交的实现方式,代码逻辑清晰。任何问题评论区或私信讨论,欢迎指正。

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

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

相关文章

RAGFlow:基于OCR和文档解析的下一代 RAG 引擎

一、引言 在人工智能的浪潮中&#xff0c;检索增强生成&#xff08;Retrieval-Augmented Generation&#xff0c;简称RAG&#xff09;技术以其独特的优势成为了研究和应用的热点。RAG技术通过结合大型语言模型&#xff08;LLMs&#xff09;的强大生成能力和高效的信息检索系统…

书生·浦语大模型实战营 | 第2次学习笔记

前言 书生浦语大模型应用实战营 第二期正在开营&#xff0c;欢迎大家来学习。&#xff08;参与链接&#xff1a;课程升级&#xff0c;算力免费&#xff0c;书生浦语实战营第二期学员招募&#xff5c;活动预告https://mp.weixin.qq.com/s/YYSr3re6IduLJCAh-jgZqg&#xff09; …

GFS部署实验

目录 1、部署环境 ​编辑 2、更改节点名称 3、准备环境 4、磁盘分区&#xff0c;并挂载 5. 做主机映射--/etc/hosts/ 6. 复制脚本文件 7. 执行脚本完成分区 8. 安装客户端软件 1. 创建gfs 2. 安装解压源包 3. 安装 gfs 4. 开启服务 9、 添加节点到存储信任池中 1…

SpringBoot项目如何国际化操作,让你可以随意切换语言

1.前言 最近接触的项目需要中文/英文或者其他国家语言的切换&#xff0c;在后台的时候有一个选择&#xff0c;你可以选择中文还是英文&#xff0c;或者其他语言&#xff0c;选择完毕界面语言就都变了&#xff0c;咱不知道前端怎么操作的&#xff0c;但是后台在处理提示语的时候…

软件SPI读写W25Q64

文章目录 前言接线引脚定义图 软件SPI读写W25Q64代码规划代码实现ThisSPI.cThisW25Q64.cmain.c 前言 SPI介绍&#xff1a;https://blog.csdn.net/qq_53922901/article/details/137142038 W25Q64介绍&#xff1a; https://blog.csdn.net/qq_53922901/article/details/137197048…

C++性能测试工具

使用示例main.cpp // g-13 -O3 -stdc17 main.cpp profile.cpp #include <iostream> #include <chrono> #include <stdint.h> #include <mutex> // std::mutex#include "profile.h" #include "profile_rdtsc.h"std::mut…

Java基于SpringBoot+Vue 的医院预约挂号系统

博主介绍&#xff1a;✌程序员徐师兄、10年大厂程序员经历。全网粉丝12W、csdn博客专家、掘金/华为云/阿里云/InfoQ等平台优质作者、专注于Java技术领域和毕业项目实战✌ &#x1f345;文末获取源码联系&#x1f345; &#x1f447;&#x1f3fb; 精彩专栏推荐订阅&#x1f447…

【问题记录】使用Audition播放时低8位数据会被修改

一&#xff0c;问题现象记录 使用Audition连接UAC播放采样点数据比较大的时候&#xff0c;低8位的数据会被修改。如果对低8位数据敏感的情况&#xff0c;需要使用其他播放器进行播放。 二&#xff0c;问题复现 1&#xff0c;使用C代码生成一个PCM文件&#xff1a; #include…

智慧水库解决方案(打造水库智慧监测体系)

​作为一名水利自动化系统集成商,最近我司接手了一个智慧水库建设项目。这个项目位于一座山区的大型水库,目的是对其进行现代化、智能化改造,提升供水、防洪等管理水平。&#xff08;key-iot.com.cn&#xff09; 在方案设计之初,我们组织了现场勘测,全面了解水库的实际情况。这…

win11系统和ubuntu双系统首次连接网线上网流程

硬件准备 首先需要将网线连接到电脑&#xff0c;另一头可以连接交换机或者路由器 上网前需要拨号上网&#xff0c;如果是连的路由器&#xff0c;那么一台路由器上拨号一次就行了。 如果是连的交换机需要拨号上网 这里踩的第一个坑是刚开始电脑连的是交换机1又连的交换机2&…

STM32中C编程引入C++程序

C具备类的创建思想很实用于实际场景多相似性的框架搭建&#xff1b;同种类型或相似类型的C的优势明显因此进行相互嵌套使用 需要在C中使用C类的话&#xff0c;你可以通过C的“extern "C"”语法来实现。这允许你在C代码中使用C的链接方式&#xff0c;而在C代码中使用…

【Linux】UDP编程【上】{诸多编程接口/小白入门式讲解}

文章目录 0.预备知识0.1套接字0.2TCP/UDP0.3大小端问题 1.socket 常见API1.1socket1.2各个接口1.3int bind();1.3网络头文件四件套1.4bzero1.5recvfrom1.6sendto() 2.UDP编程2.1服务器编程2.2客户端编程2.3运行测试2.3.1本机通信2.3.2popen2.3.3strcasestr2.3.4回顾C11智能指针…

微电网优化:基于肝癌算法(Liver Cancer algorithm, LCA)的微电网优化(提供MATLAB代码)

一、微电网优化模型 微电网是一个相对独立的本地化电力单元&#xff0c;用户现场的分布式发电可以支持用电需求。为此&#xff0c;您的微电网将接入、监控、预测和控制您本地的分布式能源系统&#xff0c;同时强化供电系统的弹性&#xff0c;保障您的用电更经济。您可以在连接…

Mamba解读(FlashAttention,SSM,LSSL,S4,S5,Mamba)

Sequence modelScale and EfficiencyFlashAttentionMotivationMethodFlashDecoding MambaState-Space Models&#xff08;SSM&#xff09;Selective State Space Models&#xff08;Mamba&#xff09; Sequence model seq2seq任务将 输入序列 x ( t ) x(t) x(t) 映射为 输出序…

每日两题 / 1.两数之和 49.字母异位词分组(leetcode热题100)

1. 两数之和 - 力扣&#xff08;LeetCode&#xff09; 暴力解法&#xff1a; O ( N 2 ) O(N^2) O(N2)遍历数组&#xff0c;找两个数相加为target O ( N ) O(N) O(N)解法&#xff1a;将所有数排序&#xff0c;并记录这些数的下标&#xff0c;双指针从左右向中间走。指向的两数…

吴恩达深度学习笔记:深层神经网络(Deep Neural Networks)4.1-4.4

目录 第一门课&#xff1a;神经网络和深度学习 (Neural Networks and Deep Learning)第四周&#xff1a;深层神经网络(Deep Neural Networks)4.1 深层神经网络&#xff08;Deep L-layer neural network&#xff09;4.2 前向传播和反向传播&#xff08;Forward and backward pro…

vscode 重命名很慢或失败 vscode renames are slow

网上问题&#xff0c; 插件问题&#xff08;我遇见的排除&#xff0c;不是&#xff09;被其他程序占用问题&#xff0c;&#xff08;我这边是这个&#xff09; 解决方案&#xff1a; 打开【资源管理器】&#xff0c;使用火绒 或其他软件&#xff0c;查看文件夹 or 文件 被哪个…

智慧公厕系统3.0有哪些新功能呢

随着科技的不断进步&#xff0c;智慧公厕系统也在不断升级和完善。最新推出的智慧公厕系统3.0&#xff0c;集成了一系列创新功能&#xff0c;为城市公共卫生设施管理带来了全新的体验和便利。让我们一起来看看智慧公厕系统3.0有哪些新功能吧。 1、云端管理平台&#xff1a;智慧…

家用洗地机哪个牌子好?四大热销机型推荐,值得推荐!

随着科技的进步&#xff0c;洗地机在日常生活中能够帮助人们省时省力地打扫卫生&#xff0c;但市面上出现了各种各样的洗地机&#xff0c;好坏参差不齐&#xff0c;选择一个好品牌的洗地机非常重要&#xff0c;因为它们有着可靠的质量保证。那市面上如此众多的洗地机品牌&#…

8×8点阵数码管显示驱动/大电流数码管驱动IC-VK16K33C SOP20

产品品牌&#xff1a;永嘉微电/VINKA 产品型号&#xff1a;VK16K33C 封装形式&#xff1a;SOP20 概述 VK16K33C是一种带按键扫描接口的数码管或点阵LED驱动控制专用芯片&#xff0c;内部集成有数据锁 存器、键盘扫描、LED 驱动模块等电路。数据通过I2C通讯接口与MCU通信。SE…