歡迎訪問我的個人博客:www.ifueen.com
防重複提交的重要性?
在業務開發中,爲什麼我們要去想辦法解決重複提交這一問題發生?網上的概念很多:導致表單重複提交,造成數據重複,增加服務器負載,嚴重甚至會造成服務器宕機,那麼爲什麼會造成這種現象?前臺操作的抖動,快速操作,網絡通信或者後端響應慢,都會增加後端重複處理的概率,就拿我親身經歷來說,因爲業務邏輯,需要進行一個"關注"操作,但是寫好業務之後在測試時連續點擊幾下,重複地進行關注和取消關注操作,因爲操作過於頻繁,而服務器走過來的響應速度沒有那麼快地進行處理,導致重複數據插入地情況,最後導致在查詢關注的時候服務器報錯,這個時候,放重複提交就顯得很重要了
如何防重複提交?
其實實現地方法有很多,但是原理大概都是相通的,我選擇的是通過使用AOP+redis來進行處理,前端發起請求的時候需要在請求頭裏將token給我,然後我這邊通過token+ip再加上請求的路徑作爲一個key存到redis裏面去,設置一個合適的過期時間,下一次再從redis中取出和當前時間進行一個判斷,如果大於我們設定的一個超時時間,那麼就進行攔截,不讓它進行下面的業務代碼,並給出提示“操作頻繁,請稍後重試”
代碼實現
前提環境準備:SpringAOP的支持,Redis的支持
其實用文字敘述出來感覺有一點點繞,但是代碼實現起來其實不難
首先我們需要自定義一個註解:
package com.ifueen.anntion;
import org.springframework.stereotype.Component;
import java.lang.annotation.*;
/**
* @author fueen
* @date 2020/7/4
* 自定義防重複提交註解
*/
@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Component
public @interface NoRepeatSubmit {
/**
* 默認時間 默認1秒鐘
* @return
*/
int lockTime() default 1000;
}
然後寫一個AOP進行攔截處理
package com.ifueen.aspect;
import com.ifueen.anntion.NoRepeatSubmit;
import com.ifueen.utils.CommonResult;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import javax.servlet.http.HttpServletRequest;
import java.util.Objects;
import java.util.concurrent.TimeUnit;
/**
* @author fueen
* @date 2020/7/4 15:07
*/
@Aspect
@Component
@Slf4j
@SuppressWarnings("all")
public class RepeatSubmitAspect {
public static final String KEYPREX="noRpeat:user:";
@Autowired
private RedisTemplate redisTemplate;
/**
* 進行接口防重複操作處理
* @param pjp
* @param noRepeatSubmit
* @return
*/
@Around("execution(* com.ifueen.controller.*.*(..)) && @annotation(noRepeatSubmit)")
public Object around(ProceedingJoinPoint pjp, NoRepeatSubmit noRepeatSubmit) throws Throwable {
try {
//獲取request
HttpServletRequest request = ((ServletRequestAttributes) Objects.requireNonNull(RequestContextHolder.getRequestAttributes())).getRequest();
//拿到token和請求路徑
StringBuilder sb = new StringBuilder();
sb.append(KEYPREX).append(request.getHeader("token").toString()).append(request.getRequestURI().toString());
//獲取現在時間
long now = System.currentTimeMillis();
if (redisTemplate.hasKey(sb.toString())){
//上次請求時間
long lastTime= Long.valueOf(redisTemplate.opsForValue().get(sb.toString()).toString()) ;
// 如果現在距離上次提交時間小於設置的默認時間 則 判斷爲重複提交 否則 正常提交 -> 進入業務處理
if ((now - lastTime)>noRepeatSubmit.lockTime()){
//重新記錄時間 10分鐘過期時間
redisTemplate.opsForValue().set(sb.toString(),String.valueOf(now),10, TimeUnit.MINUTES);
//處理業務
Object result = pjp.proceed();
return result;
}else {
return CommonResult.getFaiInstance("-1","點擊的太快了,請慢一點!");
}
}else {
//第一次操作
redisTemplate.opsForValue().set(sb.toString(),String.valueOf(now),10, TimeUnit.MINUTES);
Object result = pjp.proceed();
return result;
}
}catch (Throwable e){
log.error("校驗表單重複提交時異常: {}", e.getMessage());
return CommonResult.getFaiInstance("-1","校驗重複提交時異常");
}
}
}
AOP和註解都寫好了,現在只需要在要用到的請求上面打上這個註解就可以了
其他請求需要進行防重複提交,也只需要打上這個註解即可