中文字幕av专区_日韩电影在线播放_精品国产精品久久一区免费式_av在线免费观看网站

溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點擊 登錄注冊 即表示同意《億速云用戶服務條款》

SpringBoot怎么結合Aop+Redis防止接口重復提交

發布時間:2022-03-29 13:45:25 來源:億速云 閱讀:400 作者:iii 欄目:大數據

這篇“SpringBoot怎么結合Aop+Redis防止接口重復提交”文章的知識點大部分人都不太理解,所以小編給大家總結了以下內容,內容詳細,步驟清晰,具有一定的借鑒價值,希望大家閱讀完這篇文章能有所收獲,下面我們一起來看看這篇“SpringBoot怎么結合Aop+Redis防止接口重復提交”文章吧。

在實際的開發項目中,一個對外暴露的接口往往會面臨很多次請求,我們來解釋一下冪等的概念:任意多次執行所產生的影響均與一次執行的影響相同。按照這個含義,最終的含義就是 對數據庫的影響只能是一次性的,不能重復處理。如何保證其冪等性,通常有以下手段:

1、數據庫建立唯一性索引,可以保證最終插入數據庫的只有一條數據。

2、token機制,每次接口請求前先獲取一個token,然后再下次請求的時候在請求的header體中加上這個token,后臺進行驗證,如果驗證通過刪除token,下次請求再次判斷token。

3、悲觀鎖或者樂觀鎖,悲觀鎖可以保證每次for update的時候其他sql無法update數據(在數據庫引擎是innodb的時候,select的條件必須是唯一索引,防止鎖全表)

4、先查詢后判斷,首先通過查詢數據庫是否存在數據,如果存在證明已經請求過了,直接拒絕該請求,如果沒有存在,就證明是第一次進來,直接放行。

為什么要防止接口重復提交?
對于有些敏感操作接口,比如新增數據接口、付款接口,要是用戶操作不當多次點擊提交按鈕,這些接口就會被多次請求,最后可能導致系統異常。

前端可以如何控制?
前端可以通過js進行控制,當用戶點擊提交按鈕,
1.按鈕設置多少秒內不可點擊狀態
2.按鈕點擊后彈出loading提示框,避免再次點擊,直到接口請求返回后
3.按鈕點擊后跳轉到新的頁面

但是,請記住,永遠不要相信用戶的行為,因為你不知道用戶會做哪些奇葩的操作,所以,最重要的還是要在后端處理。

使用aop+redis進行攔截處理
一.創建切面類RepeatSubmitAspect
實現過程:接口請求后,token+請求路徑作為key值去redis中讀取數據,若能找到這個key,則證明是重復提交的,反之不是。若不是重復提交,則直接放行,并將這個key寫入redis中,并設置一定時間過期(我這里是設置的5s過期)


在傳統的web項目中,為了防止重復提交,通常做法是:后端生成唯一的提交令牌(uuid),存儲在服務端,頁面在發起請求時,攜帶次令牌,后端驗證請求后刪除令牌,保證請求的唯一性。
但是,上訴的做法是需要前后端都需要進行改動,如果在項目初期,是可以實現的,但是,在項目的后期,很多功能都實現好了,不可能大范圍的去改動。

思路
1.自定義注解@NoRepeatSubmit 標記所有Controller中提交的請求
2.通過AOP對所有標記了@NoRepeatSubmit 的方法進行攔截
3.在業務方法執行前,獲取當前用戶的token或者JSessionId+當前請求地址,作為一個唯一的key,去獲取redis分布式鎖,如果此時并發獲取,只有一個線程能獲取到。
4.業務執行后,釋放鎖

關于Redis分布式鎖
使用Redis是為了在負載均衡部署,如果是單機的項目可以使用一個本地線程安全的Cache替代Redis

代碼
自定義注解
 

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/**
 * @ClassName NoRepeatSubmit
 * @Description 這里描述
 * @Author admin
 * @Date 2021/3/2 16:16
 */
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface NoRepeatSubmit {

    /**
     * 設置請求鎖定時間
     *
     * @return
     */
    int lockTime() default 10;

}

AOP

package com.hongkun.aop;

/**
 * @ClassName RepeatSubmitAspect
 * @Description 這里描述
 * @Author admin
 * @Date 2021/3/2 16:15
 */

import com.hongkun.until.ApiResult;
import com.hongkun.until.Result;
import com.hongkun.until.RedisLock;
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.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.util.Assert;
import org.springframework.web.context.request.RequestAttributes;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import javax.servlet.http.HttpServletRequest;
import java.util.UUID;
import java.util.concurrent.TimeUnit;

/**
 * @author liucheng
 * @since 2020/01/15
 * 防止接口重復提交
 */
@Aspect
@Component
public class RepeatSubmitAspect {
    private static final Logger LOGGER = LoggerFactory.getLogger(RepeatSubmitAspect.class);

    @Autowired
    private RedisLock redisLock;

    @Pointcut("@annotation(noRepeatSubmit)")
    public void pointCut(NoRepeatSubmit noRepeatSubmit) {
    }

    @Around("pointCut(noRepeatSubmit)")
    public Object around(ProceedingJoinPoint pjp, NoRepeatSubmit noRepeatSubmit) throws Throwable {

        int lockSeconds = noRepeatSubmit.lockTime();

        RequestAttributes ra = RequestContextHolder.getRequestAttributes();
        ServletRequestAttributes sra = (ServletRequestAttributes) ra;
        HttpServletRequest request = sra.getRequest();

        Assert.notNull(request, "request can not null");

        // 此處可以用token或者JSessionId
        String token = request.getHeader("token");
        String path = request.getServletPath();
        String key = getKey(token, path);
        String clientId = getClientId();

        boolean isSuccess = redisLock.lock(key, clientId, lockSeconds,TimeUnit.SECONDS);
        LOGGER.info("tryLock key = [{}], clientId = [{}]", key, clientId);

        if (isSuccess) {
            LOGGER.info("tryLock success, key = [{}], clientId = [{}]", key, clientId);
            // 獲取鎖成功
            Object result;
            try {
                // 執行進程
                result = pjp.proceed();
            } finally {
                // 解鎖
                redisLock.unlock(key, clientId);
                LOGGER.info("releaseLock success, key = [{}], clientId = [{}]", key, clientId);
            }

            return result;

        } else {
            // 獲取鎖失敗,認為是重復提交的請求
            LOGGER.info("tryLock fail, key = [{}]", key);
            return ApiResult.success(200, "重復請求,請稍后再試", null);
        }

    }

    private String getKey(String token, String path) {
        return "00000"+":"+token + path;
    }

    private String getClientId() {
        return UUID.randomUUID().toString();
    }


}

以上就是關于“SpringBoot怎么結合Aop+Redis防止接口重復提交”這篇文章的內容,相信大家都有了一定的了解,希望小編分享的內容對大家有幫助,若想了解更多相關的知識內容,請關注億速云行業資訊頻道。

向AI問一下細節

免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。

AI

富蕴县| 甘孜县| 商南县| 东阳市| 顺昌县| 辽源市| 阿克陶县| 重庆市| 丽江市| 景德镇市| 游戏| 湖口县| 新宾| 鹤壁市| 雅江县| 乌审旗| 洪雅县| 大埔区| 宝鸡市| 静宁县| 晋州市| 新沂市| 乃东县| 油尖旺区| 老河口市| 湘潭县| 浦县| 驻马店市| 四川省| 蓝山县| 原平市| 诸城市| 泰宁县| 郎溪县| 太保市| 南木林县| 五莲县| 拜泉县| 乐都县| 西华县| 抚宁县|