redis分布式鎖解決表單重復(fù)提交的問(wèn)題
假如用戶的網(wǎng)速慢,用戶點(diǎn)擊提交按鈕,卻因?yàn)榫W(wǎng)速慢,而沒(méi)有跳轉(zhuǎn)到新的頁(yè)面,這時(shí)的用戶會(huì)再次點(diǎn)擊提交按鈕,舉個(gè)例子:用戶點(diǎn)擊訂單頁(yè)面,當(dāng)點(diǎn)擊提交按鈕的時(shí)候,也許因?yàn)榫W(wǎng)速的原因,沒(méi)有跳轉(zhuǎn)到新的頁(yè)面,這時(shí)的用戶會(huì)再次點(diǎn)擊提交按鈕,如果沒(méi)有經(jīng)過(guò)處理的話,這時(shí)用戶就會(huì)生成兩份訂單,類(lèi)似于這種場(chǎng)景都叫重復(fù)提交。
使用redis的setnx和getset命令解決表單重復(fù)提交的問(wèn)題。
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.編寫(xiě)加鎖和解鎖的方法。
/** * @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)行加鎖的操作(該方法是單線程運(yùn)行的) * @date 2019年09月20日 * @param key 某個(gè)方法請(qǐng)求url加上cookie中的用戶身份使用md5加密生成 * @param value 當(dāng)前時(shí)間+過(guò)期時(shí)間(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()){ //獲取上一個(gè)鎖的時(shí)間,并且重新設(shè)置鎖 String oldValue = redisTemplate.opsForValue().getAndSet(key, value); if(StringUtils.isNotEmpty(oldValue) && oldValue.equals(currentValue)){ //設(shè)置成功,重新設(shè)置鎖是保證了單線程的運(yùn)行 return true; } } return false; } /** * @author wangbin * @description 進(jìn)行解鎖的操作 * @date 2019年09月20日 * @param key 某個(gè)方法請(qǐng)求url使用md5加密生成 * @param value 當(dāng)前時(shí)間+過(guò)期時(shí)間 * @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 某個(gè)方法請(qǐng)求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.使用攔截器在請(qǐng)求之前進(jìn)行加鎖的判斷。
@Configuration public class LoginInterceptor extends HandlerInterceptorAdapter { private final Logger logger = LoggerFactory.getLogger(LoginInterceptor.class); //超時(shí)時(shí)間設(shè)置為10秒 private static final int timeOut = 10000; @Autowired private StringRedisTemplate stringRedisTemplate; @Autowired private RedisLock redisLock; /** * 在請(qǐng)求處理之前進(jìn)行調(diào)用(Controller方法調(diào)用之前) * 基于URL實(shí)現(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)) { //不需要的攔截直接過(guò) return true; } else { // 這寫(xiě)你攔截需要干的事兒,比如取緩存,SESSION,權(quán)限判斷等 //判斷是否是重復(fù)提交的請(qǐng)求 if(!redisLock.lock(DigestUtils.md5Hex(request.getRequestURI()+value),String.valueOf(System.currentTimeMillis()+timeOut))){ logger.info("===========獲取鎖失敗,該請(qǐng)求為重復(fù)提交請(qǐng)求"); return false; } return true; } } }
4.使用aop在后置通知中進(jìn)行解鎖。
/** * @author wangbin * @description 使用redis分布式鎖解決表單重復(fù)提交的問(wèn)題 * @date 2019年09月20日 */ @Aspect @Component public class RepeatedSubmit { @Autowired private RedisLock redisLock; //定義切點(diǎn) @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ù)提交的問(wèn)題的文章就介紹到這了,更多相關(guān)redis 表單重復(fù)提交內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
SpringAOP中的切點(diǎn)表達(dá)式Pointcut詳解
這篇文章主要介紹了SpringAOP中的切點(diǎn)表達(dá)式Pointcut詳解,Spring?的?AOP?中的一個(gè)核心概念是切點(diǎn)(Pointcut),切點(diǎn)表達(dá)式定義通知(Advice)執(zhí)行的范圍,需要的朋友可以參考下2023-08-08Java關(guān)鍵字this(動(dòng)力節(jié)點(diǎn)Java學(xué)院整理)
java中的this隨處可見(jiàn),用法也多。通常情況下理解this關(guān)鍵字還是很容易的,但是在我初學(xué)的時(shí)候,有一個(gè)疑問(wèn)卻一直不能很清晰的理解,現(xiàn)在慢慢的理解了,下面通過(guò)本文給大家記錄下,有需要的朋友參考下2017-03-03java獲取和設(shè)置系統(tǒng)變量問(wèn)題(環(huán)境變量)
這篇文章主要介紹了java獲取和設(shè)置系統(tǒng)變量問(wèn)題(環(huán)境變量),具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2023-01-01Spring使用RestTemplate模擬form提交示例
本篇文章主要介紹了Spring使用RestTemplate模擬form提交示例,小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧2018-03-03MyBatis-Generator的配置說(shuō)明和使用
本文主要介紹了MyBatis-Generator的配置說(shuō)明和使用的相關(guān)知識(shí)。具有很好的參考價(jià)值,下面跟著小編一起來(lái)看下吧2017-02-02idea實(shí)現(xiàn)類(lèi)快捷生成接口方法示例
這篇文章主要介紹了idea實(shí)現(xiàn)類(lèi)快捷生成接口方法示例,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2020-08-08Java解決xss轉(zhuǎn)義導(dǎo)致轉(zhuǎn)碼的問(wèn)題
跨站腳本攻擊XSS是最普遍的Web應(yīng)用安全漏洞,本文主要介紹了Java解決xss轉(zhuǎn)義導(dǎo)致轉(zhuǎn)碼的問(wèn)題,具有一定的參考價(jià)值,感興趣的可以了解一下2023-08-08快速搭建springboot項(xiàng)目(新手入門(mén))
本文主要介紹了快速搭建springboot項(xiàng)目(新手入門(mén)),文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2023-07-07Apache Commons Math3探索之多項(xiàng)式曲線擬合實(shí)現(xiàn)代碼
這篇文章主要介紹了Apache Commons Math3探索之多項(xiàng)式曲線擬合實(shí)現(xiàn)代碼,小編覺(jué)得挺不錯(cuò)的,這里分享給大家,供需要的朋友參考。2017-10-10