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

十年的華陰網站建設經驗,針對設計、前端、開發、售后、文案、推廣等六對一服務,響應快,48小時及時工作處理。成都營銷網站建設的優勢是能夠根據用戶設備顯示端的尺寸不同,自動調整華陰建站的顯示方式,使網站能夠適用不同顯示終端,在瀏覽器中調整網站的寬度,無論在任何一種瀏覽器上瀏覽網站,都能展現優雅布局與設計,從而大程度地提升瀏覽體驗。創新互聯從事“華陰網站設計”,“華陰網站推廣”以來,每個客戶項目都認真落實執行。
在實際的開發項目中,一個對外暴露的接口往往會面臨很多次請求,我們來解釋一下冪等的概念:任意多次執行所產生的影響均與一次執行的影響相同。按照這個含義,最終的含義就是 對數據庫的影響只能是一次性的,不能重復處理。如何保證其冪等性,通常有以下手段:
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;
}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防止接口重復提交”這篇文章的內容,相信大家都有了一定的了解,希望小編分享的內容對大家有幫助,若想了解更多相關的知識內容,請關注創新互聯行業資訊頻道。
標題名稱:SpringBoot怎么結合Aop+Redis防止接口重復提交
文章分享:http://www.yijiale78.com/article48/jooihp.html
成都網站建設公司_創新互聯,為您提供網站改版、移動網站建設、企業網站制作、Google、App設計、定制網站
聲明:本網站發布的內容(圖片、視頻和文字)以用戶投稿、用戶轉載內容為主,如果涉及侵權請盡快告知,我們將會在第一時間刪除。文章觀點不代表本網站立場,如需處理請聯系客服。電話:028-86922220;郵箱:631063699@qq.com。內容未經允許不得轉載,或轉載時需注明來源: 創新互聯