Spring Boot通過Redis實(shí)現(xiàn)防止重復(fù)提交
一、什么是冪等。
1、什么是冪等:相同的條極下,執(zhí)行多次結(jié)果拿到的結(jié)果都是一樣的。舉個(gè)例子比如相同的參數(shù)情況,執(zhí)行多次,返回的數(shù)據(jù)都是樣的。那什么情況下要考慮冪等,重復(fù)新增數(shù)據(jù)。
2、解決方案:解決的方式有很多種,本文使用的是本地鎖。
二、實(shí)現(xiàn)思路
1、首先我們要確定多少時(shí)間內(nèi),相同請求參數(shù)視為一次請求,比如2秒內(nèi)相同參數(shù),無論請求多少次都視為一次請求。
2、一次請求的判斷依據(jù)是什么,肯定是相同的形參,請求相同的接口,在1的時(shí)間范圍內(nèi)視為相同的請求。所以我們可以考慮通過參數(shù)生成一個(gè)唯一標(biāo)識(shí),這樣我們根據(jù)唯一標(biāo)識(shí)來判斷。
三、代碼實(shí)現(xiàn)
1、這里我選擇spring的redis來實(shí)現(xiàn),但如果有集群的情況,還是要用集群redis來處理。當(dāng)?shù)谝淮潍@取請求時(shí),根據(jù)請求url、controller層調(diào)用的方法、請求參數(shù)生成MD5值這三個(gè)條件作為依據(jù),將其作為redis的key和value值,并設(shè)置失效時(shí)間,當(dāng)在失效時(shí)間之內(nèi)再次請求時(shí),根據(jù)是否已存在key值來判斷接收還是拒絕請求來實(shí)現(xiàn)攔截。
2、pom.xml依賴
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-redis</artifactId> <exclusions> <exclusion> <groupId>io.lettuce</groupId> <artifactId>lettuce-core</artifactId> </exclusion> </exclusions> <version>2.0.6.RELEASE</version> </dependency> <dependency> <groupId>org.aspectj</groupId> <artifactId>aspectjweaver</artifactId> <version>1.8.9</version> </dependency> <dependency> <groupId>redis.clients</groupId> <artifactId>jedis</artifactId> <version>2.9.1</version> </dependency> <dependency> <groupId>org.apache.commons</groupId> <artifactId>commons-pool2</artifactId> <version>2.6.0</version> </dependency> <dependency> <groupId>com.alibaba</groupId> <artifactId>fastjson</artifactId> <version>1.2.31</version> </dependency>
3、在application.yml中配置redis
spring: redis: database: 0 host: XXX port: 6379 password: XXX timeout: 1000 pool: max-active: 1 max-wait: 10 max-idle: 2 min-idle: 50
4、自定義異常類IdempotentException
package com.demo.controller; public class IdempotentException extends RuntimeException { public IdempotentException(String message) { super(message); } @Override public String getMessage() { return super.getMessage(); } }
5、自定義注解
package com.demo.controller; import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; @Target(ElementType.METHOD) @Retention(RetentionPolicy.RUNTIME) @Documented public @interface Idempotent { // redis的key值一部分 String value(); // 過期時(shí)間 long expireMillis(); }
6、自定義切面
package com.demo.controller; import java.lang.reflect.Method; import java.util.Objects; import javax.annotation.Resource; 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.aspectj.lang.reflect.MethodSignature; import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; import org.springframework.data.redis.core.RedisCallback; import org.springframework.data.redis.core.RedisTemplate; import org.springframework.stereotype.Component; import redis.clients.jedis.JedisCommands; @Component @Aspect @ConditionalOnClass(RedisTemplate.class) public class IdempotentAspect { private static final String KEY_TEMPLATE = "idempotent_%s"; @Resource private RedisTemplate<String, String> redisTemplate; //填寫掃描加了自定義注解的類所在的包。 @Pointcut("@annotation(com.demo.controller.Idempotent)") public void executeIdempotent() { } @Around("executeIdempotent()") //環(huán)繞通知 public Object around(ProceedingJoinPoint joinPoint) throws Throwable { Method method = ((MethodSignature) joinPoint.getSignature()).getMethod(); Idempotent idempotent = method.getAnnotation(Idempotent.class); //注意Key的生成規(guī)則 String key = String.format(KEY_TEMPLATE, idempotent.value() + "_" + KeyUtil.generate(method, joinPoint.getArgs())); String redisRes = redisTemplate .execute((RedisCallback<String>) conn -> ((JedisCommands) conn.getNativeConnection()).set(key, key, "NX", "PX", idempotent.expireMillis())); if (Objects.equals("OK", redisRes)) { return joinPoint.proceed(); } else { throw new IdempotentException("Idempotent hits, key=" + key); } } }
//注意這里不需要釋放鎖操作,因?yàn)槿绻尫帕耍谙薅〞r(shí)間內(nèi),請求還是會(huì)再進(jìn)來,所以不能釋放鎖操作。
7、Key生成工具類
package com.demo.controller; import java.io.UnsupportedEncodingException; import java.lang.reflect.Method; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; import com.alibaba.fastjson.JSON; public class KeyUtil { public static String generate(Method method, Object... args) throws UnsupportedEncodingException { StringBuilder sb = new StringBuilder(method.toString()); for (Object arg : args) { sb.append(toString(arg)); } return md5(sb.toString()); } private static String toString(Object object) { if (object == null) { return "null"; } if (object instanceof Number) { return object.toString(); } return JSON.toJSONString(object); } public static String md5(String str) { StringBuilder buf = new StringBuilder(); try { MessageDigest md = MessageDigest.getInstance("MD5"); md.update(str.getBytes()); byte b[] = md.digest(); int i; for (int offset = 0; offset < b.length; offset++) { i = b[offset]; if (i < 0) i += 256; if (i < 16) buf.append(0); buf.append(Integer.toHexString(i)); } } catch (NoSuchAlgorithmException e) { e.printStackTrace(); } return buf.toString(); }
8、在Controller中標(biāo)記注解
package com.demo.controller; import javax.annotation.Resource; import org.springframework.data.redis.core.RedisTemplate; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RestController; @RestController public class DemoController { @Resource private RedisTemplate<String, String> redisTemplate; @PostMapping("/redis") @Idempotent(value = "/redis", expireMillis = 5000L) public String redis(@RequestBody User user) { return "redis access ok:" + user.getUserName() + " " + user.getUserAge(); } }
到此這篇關(guān)于Spring Boot通過Redis實(shí)現(xiàn)防止重復(fù)提交的文章就介紹到這了,更多相關(guān)SpringBoot Redis防止重復(fù)提交內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
- SpringBoot+Redis大量重復(fù)提交問題的解決方案
- SpringBoot利用Redis解決海量重復(fù)提交問題
- SpringBoot+Redisson自定義注解一次解決重復(fù)提交問題
- SpringBoot+Redis海量重復(fù)提交問題解決
- 基于SpringBoot接口+Redis解決用戶重復(fù)提交問題
- SpringBoot整合redis+Aop防止重復(fù)提交的實(shí)現(xiàn)
- SpringBoot+Redis使用AOP防止重復(fù)提交的實(shí)現(xiàn)
- SpringBoot?使用AOP?+?Redis?防止表單重復(fù)提交的方法
- SpringBoot基于redis自定義注解實(shí)現(xiàn)后端接口防重復(fù)提交校驗(yàn)
- SpringBoot?+?Redis如何解決重復(fù)提交問題(冪等)
- SpringBoot+Redis實(shí)現(xiàn)后端接口防重復(fù)提交校驗(yàn)的示例
相關(guān)文章
在SpringBoot中注入RedisTemplate實(shí)例異常的解決方案
這篇文章主要介紹了在SpringBoot中注入RedisTemplate實(shí)例異常的解決方案,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2022-01-01深度解析Spring AI請求與響應(yīng)機(jī)制的核心邏輯
我們在前面的兩個(gè)章節(jié)中基本上對(duì)Spring Boot 3版本的新變化進(jìn)行了全面的回顧,以確保在接下來研究Spring AI時(shí)能夠避免任何潛在的問題,本文給大家介紹Spring AI請求與響應(yīng)機(jī)制的核心邏輯,感興趣的朋友跟隨小編一起看看吧2024-11-11Mybatis 實(shí)現(xiàn)打印sql語句的代碼
這篇文章主要介紹了Mybatis 實(shí)現(xiàn)打印sql語句的代碼,本文通過實(shí)例代碼給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2020-07-07Bean實(shí)例化之前修改BeanDefinition示例詳解
這篇文章主要為大家介紹了Bean實(shí)例化之前修改BeanDefinition示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2022-12-12Java使用MySQL實(shí)現(xiàn)連接池代碼實(shí)例
這篇文章主要介紹了Java使用MySQL實(shí)現(xiàn)連接池代碼實(shí)例,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2020-03-03SpringBoot整合redis實(shí)現(xiàn)計(jì)數(shù)器限流的示例
本文主要介紹了SpringBoot整合redis實(shí)現(xiàn)計(jì)數(shù)器限流的示例,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2025-04-04maven基礎(chǔ)教程——簡單了解maven的特點(diǎn)與功能
這篇文章主要介紹了Maven基礎(chǔ)教程的相關(guān)資料,文中講解非常細(xì)致,幫助大家開始學(xué)習(xí)maven,感興趣的朋友可以了解下2020-07-07springboot整合阿里云百煉DeepSeek實(shí)現(xiàn)sse流式打印的操作方法
這篇文章主要介紹了springboot整合阿里云百煉DeepSeek實(shí)現(xiàn)sse流式打印,本文給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2025-04-04Java多線程教程之如何利用Future實(shí)現(xiàn)攜帶結(jié)果的任務(wù)
Callable與Future兩功能是Java?5版本中加入的,這篇文章主要給大家介紹了關(guān)于Java多線程教程之如何利用Future實(shí)現(xiàn)攜帶結(jié)果任務(wù)的相關(guān)資料,需要的朋友可以參考下2021-12-12