天天看點

防重複送出方案防重複送出方案

防重複送出方案

1.思路

因為服務端是使用Token來擷取使用者資訊,沒有對應的session管理機制,不能用sessionId加上使用者請求的URL作為唯一辨別。但是伺服器中的userId是用雪花算法進行計算,能確定唯一性,是以我們選擇用userId加上使用者請求的URL作為使用者請求的唯一辨別。當使用者請求POST、PUT、DELETE接口時,緩存使用者的userId加上請求的位址,設定逾時時間為2秒鐘,當這個使用者重複請求這個位址的時候,判斷是否在緩存中,如果在緩存中,則進行攔截,2秒後使用者才可以請求這個接口。

2.實作步驟

2.1編寫aop切面對所有請求到controller層的請求進行攔截

2.1在資料庫中擷取要攔截的路徑,為了防止多次請求資料庫導緻性能下降,但是又要定時的重新整理緩存,把查詢的資料加入緩存中,緩存的時間為1個小時。

2.2判斷使用者的請求是否在緩存中,如果在緩存中,則友好提示使用者不能頻繁操作。如果不在緩存中,則加入緩存,緩存失效時間為2秒鐘。

說明:這裡的緩存用的是Hazelcast,因為Hazelcast鎖和逾時時間不能在一行代碼中執行,如果并發請求實作不了防重複送出,建議替換成Redis。

/**
  * 切入controller層,對配置的位址進行判斷,如果2秒内重複送出多次則進行攔截
  * (攔截的位址自己進行編寫)
  */
@Before("within(com.*.*.web.controller.*)")
public void before(){
    // 擷取請求路徑
    HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
    String path = request.getServletPath();
    // 截取delete風格的servletPath
    if (path.contains(InsuranceConstant.APL_NO)){
        path = path.substring(0, path.lastIndexOf(InsuranceConstant.APL_NO));
    }
    String servletPath = path;

    // 查找防重複路徑(查找資料庫擷取過來的資料,緩存在Hazelcast中,逾時時間為1個小時)
    SysParameter sysParameter = findDuplicationPath();
    if (sysParameter == null){
        log.info("沒有配置防重複送出路徑,退出");
        return;
    }

    // 判斷請求位址是否是防重複位址
    String[] prmValue = sysParameter.getPrmValue().split(",");
    long count = Arrays.stream(prmValue).filter(value -> value.equals(servletPath)).count();
    if (count <= 0){
        log.info("{}不是要檢測路徑,退出",servletPath);
        return;
    }

    // 擷取登入的使用者Id
    Long userId = JwtTokenUtils.decodeToken(request.getHeader(CommonConstant.TOKEN));
    // 緩存Key
    String uniqueKey = servletPath + userId;
    // 檢查緩存中是否有對應的請求位址
    Object result = hazelcastUtils.get(InsuranceConstant.PREVENTING_DUPLICATION_ASPECT, uniqueKey);
    if (result == null){
        // 設定逾時時間[2秒逾時時間]
        hazelcastUtils.expire(InsuranceConstant.PREVENTING_DUPLICATION_ASPECT,uniqueKey,uniqueKey
                              ,preDuplicationMillisecond);
        log.info("請求路徑:{},進入防重複攔截,攔截時間:{}秒",servletPath,preDuplicationSecond);
    }else {
        log.info("請求路徑:{},{}秒内重複送出,友好提示",servletPath,preDuplicationSecond);
        throw new BusinessException(InsuranceErrorCodeEnum.REQUESTS_ARE_TOO_FREQUENT);
    }
}
           

繼續閱讀