redis分布式鎖解決表單重復(fù)提交的問題
假如用戶的網(wǎng)速慢,用戶點擊提交按鈕,卻因為網(wǎng)速慢,而沒有跳轉(zhuǎn)到新的頁面,這時的用戶會再次點擊提交按鈕,舉個例子:用戶點擊訂單頁面,當(dāng)點擊提交按鈕的時候,也許因為網(wǎng)速的原因,沒有跳轉(zhuǎn)到新的頁面,這時的用戶會再次點擊提交按鈕,如果沒有經(jīng)過處理的話,這時用戶就會生成兩份訂單,類似于這種場景都叫重復(fù)提交。
使用redis的setnx和getset命令解決表單重復(fù)提交的問題。
1.引入redis依賴和aop依賴
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-redis</artifactId>
<version>1.3.8.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
2.編寫加鎖和解鎖的方法。
/**
* @author wangbin
* @description redis分布式鎖
* @date 2019年09月20日
*/
@Component
public class RedisLock {
private final Logger logger = LoggerFactory.getLogger(RedisLock.class);
@Autowired
private StringRedisTemplate redisTemplate;
/**
* @author wangbin
* @description 進(jìn)行加鎖的操作(該方法是單線程運行的)
* @date 2019年09月20日
* @param key 某個方法請求url加上cookie中的用戶身份使用md5加密生成
* @param value 當(dāng)前時間+過期時間(10秒)
* @return true表示加鎖成功 false表示未獲取到鎖
*/
public boolean lock(String key,String value){
//加鎖成功返回true
if(redisTemplate.opsForValue().setIfAbsent(key,value,10, TimeUnit.SECONDS)){
return true;
}
String currentValue = redisTemplate.opsForValue().get(key);
//加鎖失敗,再判斷是否由于解鎖失敗造成了死鎖的情況
if(StringUtils.isNotEmpty(currentValue) && Long.parseLong(currentValue) < System.currentTimeMillis()){
//獲取上一個鎖的時間,并且重新設(shè)置鎖
String oldValue = redisTemplate.opsForValue().getAndSet(key, value);
if(StringUtils.isNotEmpty(oldValue) && oldValue.equals(currentValue)){
//設(shè)置成功,重新設(shè)置鎖是保證了單線程的運行
return true;
}
}
return false;
}
/**
* @author wangbin
* @description 進(jìn)行解鎖的操作
* @date 2019年09月20日
* @param key 某個方法請求url使用md5加密生成
* @param value 當(dāng)前時間+過期時間
* @return
*/
public void unLock(String key,String value){
try {
String currentValue = redisTemplate.opsForValue().get(key);
if(StringUtils.isNotEmpty(currentValue) && currentValue.equals(value)){
redisTemplate.delete(key);
}
}catch (Exception e){
logger.error("redis分布式鎖,解鎖異常",e);
}
}
/**
* @author wangbin
* @description 進(jìn)行解鎖的操作
* @date 2019年09月20日
* @param key 某個方法請求url使用md5加密生成
* @return
*/
public void unLock(String key){
try {
String currentValue = redisTemplate.opsForValue().get(key);
if(StringUtils.isNotEmpty(currentValue)){
redisTemplate.delete(key);
}
}catch (Exception e){
logger.error("redis分布式鎖,解鎖異常",e);
}
}
}
3.使用攔截器在請求之前進(jìn)行加鎖的判斷。
@Configuration
public class LoginInterceptor extends HandlerInterceptorAdapter {
private final Logger logger = LoggerFactory.getLogger(LoginInterceptor.class);
//超時時間設(shè)置為10秒
private static final int timeOut = 10000;
@Autowired
private StringRedisTemplate stringRedisTemplate;
@Autowired
private RedisLock redisLock;
/**
* 在請求處理之前進(jìn)行調(diào)用(Controller方法調(diào)用之前)
* 基于URL實現(xiàn)的攔截器
* @param request
* @param response
* @param handler
* @return
* @throws Exception
*/
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
String path = request.getServletPath();
if (path.matches(Constants.NO_INTERCEPTOR_PATH)) {
//不需要的攔截直接過
return true;
} else {
// 這寫你攔截需要干的事兒,比如取緩存,SESSION,權(quán)限判斷等
//判斷是否是重復(fù)提交的請求
if(!redisLock.lock(DigestUtils.md5Hex(request.getRequestURI()+value),String.valueOf(System.currentTimeMillis()+timeOut))){
logger.info("===========獲取鎖失敗,該請求為重復(fù)提交請求");
return false;
}
return true;
}
}
}
4.使用aop在后置通知中進(jìn)行解鎖。
/**
* @author wangbin
* @description 使用redis分布式鎖解決表單重復(fù)提交的問題
* @date 2019年09月20日
*/
@Aspect
@Component
public class RepeatedSubmit {
@Autowired
private RedisLock redisLock;
//定義切點
@Pointcut("execution(public * com.kunluntop.logistics.controller..*.*(..))")
public void pointcut(){
}
//在方法執(zhí)行完成后釋放鎖
@After("pointcut()")
public void after(){
ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
HttpServletRequest request = attributes.getRequest();
redisLock.unLock(DigestUtils.md5Hex(request.getRequestURI()+ CookieUtils.getCookie(request,"userkey")));
}
}
到此這篇關(guān)于redis分布式鎖解決表單重復(fù)提交的問題的文章就介紹到這了,更多相關(guān)redis 表單重復(fù)提交內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
淺談Java中Lambda表達(dá)式的相關(guān)操作
java8新特性,Lambda是一個匿名函數(shù),類似Python中的Lambda表達(dá)式、js中的箭頭函數(shù),目的簡化操作,文中有非常詳細(xì)的介紹及代碼示例,需要的朋友可以參考下2021-06-06
Netty分布式ByteBuf使用SocketChannel讀取數(shù)據(jù)過程剖析
這篇文章主要為大家介紹了Netty源碼分析ByteBuf使用SocketChannel讀取數(shù)據(jù)過程,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2022-03-03

