簡單介紹redis分散式鎖解決表單重複提交的問題

大雄45發表於2021-12-04
導讀 在系統中,有些介面如果重複提交,可能會造成髒資料或者其他的嚴重的問題,所以我們一般會對與資料庫有互動的介面進行重複處理。本文就詳細的介紹一下redis分散式鎖解決表單重複提交,感興趣的可以瞭解一下

假如使用者的網速慢,使用者點選提交按鈕,卻因為網速慢,而沒有跳轉到新的頁面,這時的使用者會再次點選提交按鈕,舉個例子:使用者點選訂單頁面,當點選提交按鈕的時候,也許因為網速的原因,沒有跳轉到新的頁面,這時的使用者會再次點選提交按鈕,如果沒有經過處理的話,這時使用者就會生成兩份訂單,類似於這種場景都叫重複提交。

使用redis的setnx和getset 解決表單重複提交的問題。

1.引入redis依賴和aop依賴

    org.springframework.bootspring-boot-starter-redis1.3.8.RELEASEorg.springframework.bootspring-boot-starter-aop

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 進行加鎖的操作(該方法是單執行緒執行的)
     * @date 2019年09月20日
     * @param key 某個方法請求url加上cookie中的使用者身份使用md5加密生成
     * @param value 當前時間+過期時間(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()){
            //獲取上一個鎖的時間,並且重新設定鎖
            String oldValue = redisTemplate.opsForValue().getAndSet(key, value);
            if(StringUtils.isNotEmpty(oldValue) && oldValue.equals(currentValue)){
                //設定成功,重新設定鎖是保證了單執行緒的執行
                return true;
            }
        }
        return false;
    }
 
    /**
     * @author wangbin
     * @description 進行解鎖的操作
     * @date 2019年09月20日
     * @param key 某個方法請求url使用md5加密生成
     * @param value 當前時間+過期時間
     * @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 進行解鎖的操作
     * @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.使用攔截器在請求之前進行加鎖的判斷。

@Configuration
public class LoginInterceptor extends HandlerInterceptorAdapter {
    private final Logger logger = LoggerFactory.getLogger(LoginInterceptor.class);
    //超時時間設定為10秒
    private static final int timeOut = 10000;
    @Autowired
    private StringRedisTemplate stringRedisTemplate;
    @Autowired
    private RedisLock redisLock;
    /**
     * 在請求處理之前進行呼叫(Controller方法呼叫之前)
     * 基於URL實現的攔截器
     * @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,許可權判斷等
            //判斷是否是重複提交的請求
            if(!redisLock.lock(DigestUtils.md5Hex(request.getRequestURI()+value),String.valueOf(System.currentTimeMillis()+timeOut))){
                        logger.info("===========獲取鎖失敗,該請求為重複提交請求");
                        return false;
             }
            return true;
        }
    }
}

4.使用aop在後置通知中進行解鎖。

/**
 * @author wangbin
 * @description 使用redis分散式鎖解決表單重複提交的問題
 * @date 2019年09月20日
 */
@Aspect
@Component
public class RepeatedSubmit {
 
    @Autowired
    private RedisLock redisLock;
 
    //定義切點
    @Pointcut("execution(public * com.kunluntop.logistics.controller..*.*(..))")
    public void pointcut(){
 
    }
 
    //在方法執行完成後釋放鎖
    @After("pointcut()")
    public void after(){
        ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
        HttpServletRequest request = attributes.getRequest();
        redisLock.unLock(DigestUtils.md5Hex(request.getRequestURI()+ CookieUtils.getCookie(request,"userkey")));
    }
}

到此這篇關於redis分散式鎖解決表單重複提交的問題的文章就介紹到這了。

原文來自:

來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/69955379/viewspace-2845751/,如需轉載,請註明出處,否則將追究法律責任。

相關文章