今天早上,新來的同事小王突然問我:“周哥,什麼是冪等性啊?”。然後我就跟他解釋了一番,冪等性就是說無論你執行幾次請求,其結果是一樣的。說到了冪等就不得不說重複提交了,你連續點選提交按鈕,理論上來說這是同一條資料,資料庫應該只能存入一條,而實際上存放了多條,這就違反了冪等性。因此我們就需要做一些處理,來保證連續點選提交按鈕後,資料庫只能存入一條資料。
防止重複提交的方式很多,這裡我就說一下我認為比較好用的一種。
自定義註解+Aop實現
我們通過獲取使用者ip及訪問的介面來判斷他是否重複提交,假如這個ip在一段時間內容多次訪問這個介面,我們則認為是重複提交,我們將重複提交的請求直接處理即可,不讓訪問目標介面。
自定義註解
@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface NoRepeatSubmit {
/**
* 預設1s鍾以內算重複提交
* @return
*/
long timeout() default 1;
}
Aop處理邏輯
我們將ip+介面地址作為key,隨機生成UUID作為value,存入redis。每次請求進來,根據key查詢redis,如果存在則說明是重複提交,丟擲異常,如果不存在,則是正常提交,將key存入redis。
@Aspect
@Component
public class NoRepeatSubmitAop {
@Autowired
private RedisService redisUtils;
/**
* 定義切入點
*/
@Pointcut("@annotation(NoRepeatSubmit)")
public void noRepeat() {}
/**
* 前置通知:在連線點之前執行的通知
* @param point
* @throws Throwable
*/
@Before("noRepeat()")
public void before(JoinPoint point) throws Exception{
// 接收到請求,記錄請求內容
ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
HttpServletRequest request = attributes.getRequest();
Assert.notNull(request, "request can not null");
// 此處可以用token或者JSessionId
String token = IpUtils.getIpAddr(request);
String path = request.getServletPath();
String key = getKey(token, path);
String clientId = getClientId();
List<Object> lGet = redisUtils.lGet(key, 0, -1);
// 獲取註解
MethodSignature signature = (MethodSignature) point.getSignature();
Method method = signature.getMethod();
NoRepeatSubmit annotation = method.getAnnotation(NoRepeatSubmit.class);
long timeout = annotation.timeout();
boolean isSuccess = false;
if (lGet.size()==0 || lGet == null) {
isSuccess = redisUtils.lSet(key, clientId, timeout);
}
if (!isSuccess) {
// 獲取鎖失敗,認為是重複提交的請求
redisUtils.lSet(key, clientId, timeout);
throw new Exception("不可以重複提交");
}
}
private String getKey(String token, String path) {
return token + path;
}
private String getClientId() {
return UUID.randomUUID().toString();
}
}
提供介面用來測試
在介面上新增上我們自定義的註解@NoRepeatSubmit
@RequestMapping("/test")
@NoRepeatSubmit
public String tt(HttpServletRequest request) {
return "1";
}
測試
我們在瀏覽器中連續請求兩次介面。發現第一次介面響應正常內容:1,第二次介面響應了不可重複提交的異常資訊。1s之後再點選介面,發現又響應了正常內容。
至此,這種防止重複提交的方式就介紹完了,這樣我們就完美防止了介面重複提交。