Spring Boot通過Redis實現(xiàn)防止重復提交
一、什么是冪等。
1、什么是冪等:相同的條極下,執(zhí)行多次結果拿到的結果都是一樣的。舉個例子比如相同的參數(shù)情況,執(zhí)行多次,返回的數(shù)據都是樣的。那什么情況下要考慮冪等,重復新增數(shù)據。
2、解決方案:解決的方式有很多種,本文使用的是本地鎖。
二、實現(xiàn)思路
1、首先我們要確定多少時間內,相同請求參數(shù)視為一次請求,比如2秒內相同參數(shù),無論請求多少次都視為一次請求。
2、一次請求的判斷依據是什么,肯定是相同的形參,請求相同的接口,在1的時間范圍內視為相同的請求。所以我們可以考慮通過參數(shù)生成一個唯一標識,這樣我們根據唯一標識來判斷。
三、代碼實現(xiàn)
1、這里我選擇spring的redis來實現(xiàn),但如果有集群的情況,還是要用集群redis來處理。當?shù)谝淮潍@取請求時,根據請求url、controller層調用的方法、請求參數(shù)生成MD5值這三個條件作為依據,將其作為redis的key和value值,并設置失效時間,當在失效時間之內再次請求時,根據是否已存在key值來判斷接收還是拒絕請求來實現(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: 504、自定義異常類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();
// 過期時間
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);
}
}
}//注意這里不需要釋放鎖操作,因為如果釋放了,在限定時間內,請求還是會再進來,所以不能釋放鎖操作。
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中標記注解
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();
}
}到此這篇關于Spring Boot通過Redis實現(xiàn)防止重復提交的文章就介紹到這了,更多相關SpringBoot Redis防止重復提交內容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關文章希望大家以后多多支持腳本之家!
- SpringBoot+Redis大量重復提交問題的解決方案
- SpringBoot利用Redis解決海量重復提交問題
- SpringBoot+Redisson自定義注解一次解決重復提交問題
- SpringBoot+Redis海量重復提交問題解決
- 基于SpringBoot接口+Redis解決用戶重復提交問題
- SpringBoot整合redis+Aop防止重復提交的實現(xiàn)
- SpringBoot+Redis使用AOP防止重復提交的實現(xiàn)
- SpringBoot?使用AOP?+?Redis?防止表單重復提交的方法
- SpringBoot基于redis自定義注解實現(xiàn)后端接口防重復提交校驗
- SpringBoot?+?Redis如何解決重復提交問題(冪等)
- SpringBoot+Redis實現(xiàn)后端接口防重復提交校驗的示例
相關文章
Spring Boot WebSocket 兩種集成方式詳解
WebSocket 是實現(xiàn)服務器主動推送、實時通信的利器,常見于聊天室、消息通知、實時監(jiān)控大屏等場景,本文詳細介紹了SpringBoot集成WebSocket的兩種方式,感興趣的朋友跟隨小編一起看看吧2026-05-05
關于SpringMVC的數(shù)據綁定@InitBinder注解的使用
這篇文章主要介紹了關于SpringMVC的數(shù)據綁定@InitBinder注解的使用,在SpringMVC中,數(shù)據綁定的工作是由 DataBinder 類完成的,DataBinder可以將HTTP請求中的數(shù)據綁定到Java對象中,需要的朋友可以參考下2023-07-07
Java如何使用ReentrantLock實現(xiàn)長輪詢
這篇文章主要介紹了如何使用ReentrantLock實現(xiàn)長輪詢,對ReentrantLock感興趣的同學,可以參考下2021-04-04
詳解SpringMVC組件之HandlerMapping(二)
這篇文章主要介紹了詳解SpringMVC組件之HandlerMapping(二),HandlerMapping組件是Spring?MVC核心組件,用來根據請求的request查找對應的Handler,在Spring?MVC中,有各式各樣的Web請求,每個請求都需要一個對應的Handler來處理,需要的朋友可以參考下2023-08-08

