java接口防重提交如何處理


1.什么是接口防重?

在一定的時間內請求同一接口,同一參數。由於請求是健康請求,會執行正常的業務邏輯,從而產生大量的廢數據。

2.處理方法

第一種:前台在請求接口的時候,傳遞一個唯一值,然后在對應接口判斷該唯一值,在一定的時間內是否被消費過
第二種:采用Spring AOP理念,實現請求的切割,在請求執行到某個方法或某層時候,開始攔截進行,獲取該請求的參數,用戶信息,請求地址,存入redis中並放置過期時間,進行防重(推薦使用)

3.談談以上兩種處理方法的利弊

第一種:局限性太高,前台必須傳遞一個唯一值,就算請求到達指定后台服務,寫一個攔截器,需要配置太多不需要攔截的方法,也許你會說,可以攔截有規則的請求地址,這樣真的好嗎?
第二種:作為一名JAVA后台開發,Spring應該是熟悉的不能再熟悉了,Spring核心AOP又用了多少,針對以上請求,只需要寫一個注解類,然后切面到該注解上,在需要防重的方法上只需添加注解即可

4.具體代碼(采用第二種)

注解類


import java.lang.annotation.*;

/**
 * 防重
 * @author haodongdong
 * @date 2020/8/12
 * @return
 */
//標識該注解用於方法上
@Target({ElementType.METHOD})
//申明該注解為運行時注解,編譯后改注解不會被遺棄
@Retention(RetentionPolicy.RUNTIME)
//javadoc工具記錄
@Documented
public @interface PreventSubmit 
{
}

切面類

import com.qianxian.common.exception.AppException;
import com.qianxian.common.util.TokenUtil;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestAttributes;
import org.springframework.web.context.request.RequestContextHolder;
import javax.servlet.http.HttpServletRequest;
import java.util.Arrays;
import java.util.concurrent.TimeUnit;

/**
 * 防重復提交
 * @author haodongdong
 * @date 2020/8/12
 * @return
 */
@Component
@Aspect
@Slf4j
public class PreventSubmitAspect {

    /**
     * 放重redis前綴
     */
    private static String API_PREVENT_SUBMIT = "api:preventSubmit:";

    /**
     * 放重分布式鎖前綴
     */
    private static String API_LOCK_PREVENT_SUBMIT = "api:preventSubmit:lock:";

    /**
     * 失效時間
     */
    private static Integer INVALID_NUMBER = 3;

    /**
     * redis
     */
    @Autowired
    private StringRedisTemplate stringRedisTemplate;

    /**
     * 分布式鎖
     */
    @Autowired
    private RedissonClient redissonClient;


    /**
     * 防重
     * @author haodongdong
     * @date 2020/8/12
     * @return
     */
    @Around("@annotation(com.qianxian.user.annotation.PreventSubmit)")
    public Object preventSubmitAspect(ProceedingJoinPoint joinPoint) throws Throwable {

        RLock lock = null;

        try {

            //獲取目標方法的參數
            Object[] args = joinPoint.getArgs();

            //獲取當前request請求
            RequestAttributes requestAttributes = RequestContextHolder.getRequestAttributes();
            HttpServletRequest request = (HttpServletRequest) requestAttributes.resolveReference(RequestAttributes.REFERENCE_REQUEST);

            //獲取請求地址
            String requestUri = request.getRequestURI();

            //獲取用戶ID
            Long userId = null;
            try {
                userId = TokenUtil.getUserId(request);
            }catch (Exception e){}

            //拼接鎖前綴,采用同一方法,同一用戶,同一接口
            String temp = requestUri.concat(Arrays.asList(args).toString()) + (userId != null ? userId : "");
            temp = temp.replaceAll("/","");

            //拼接rediskey
            String lockPrefix = API_LOCK_PREVENT_SUBMIT.concat(temp);
            String redisPrefix = API_PREVENT_SUBMIT.concat(temp);

            /**
             * 對同一方法同一用戶同一參數加鎖,即使獲取不到用戶ID,每個用戶請求數據也會不一致,不會造成接口堵塞
             */
            lock = this.redissonClient.getLock(lockPrefix);
            lock.lock();

            String flag = this.stringRedisTemplate.opsForValue().get(redisPrefix);
            if(StringUtils.isNotEmpty(flag)){
                throw new AppException("您當前的操作太頻繁了,請稍后再試!");
            }

            //存入redis,設置失效時間
            this.stringRedisTemplate.opsForValue().set(redisPrefix,redisPrefix,INVALID_NUMBER, TimeUnit.SECONDS);

            //執行目標方法
            Object result = joinPoint.proceed(args);
            return result;

        }finally {
            if(lock != null){
                lock.unlock();
            }
        }

    }

}

 

作者:一枚路過的程序猿
鏈接:https://www.jianshu.com/p/609fedde1234
來源:簡書
著作權歸作者所有。商業轉載請聯系作者獲得授權,非商業轉載請注明出處。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM