重複提交,你是如何處理的?

Java旅途發表於2020-06-24

今天早上,新來的同事小王突然問我:“周哥,什麼是冪等性啊?”。然後我就跟他解釋了一番,冪等性就是說無論你執行幾次請求,其結果是一樣的。說到了冪等就不得不說重複提交了,你連續點選提交按鈕,理論上來說這是同一條資料,資料庫應該只能存入一條,而實際上存放了多條,這就違反了冪等性。因此我們就需要做一些處理,來保證連續點選提交按鈕後,資料庫只能存入一條資料。

防止重複提交的方式很多,這裡我就說一下我認為比較好用的一種。

自定義註解+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之後再點選介面,發現又響應了正常內容。

至此,這種防止重複提交的方式就介紹完了,這樣我們就完美防止了介面重複提交。

相關文章