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

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

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

相關文章