spring中實現基於注解實現動態的接口限流防刷


本文將介紹在spring項目中自定義注解,借助redis實現接口的限流

自定義注解類


import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/**
 * 基於注解的請求限制
 */
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface AccessLimit {
    /**
     * 請求限制數
     * @return
     */
    int limit();


    /**
     * 時間范圍
     * @return
     */
    int timeScope();

}


使用注解

我們在需要進行接口防刷的類或者方法上加上該注解即可,


    /**
     *  得到秒殺地址
     *    由於秒殺地址較為重要和敏感,為了防止惡意用戶刷接口,
     *    我們將秒殺接口作為動態的
     * @param user
     * @param goodsId
     * @param tryCode
     * @return
     */
    @GetMapping("/path")
    @ResponseBody
    @AccessLimit(limit = 5, timeScope = 5) // 限制5秒內只能請求5次
    public Result<String> getMiaoshaPath(HttpServletRequest request, User user, long goodsId, String tryCode) {
        // 驗證碼校驗
        Boolean verifyPass = kaptchaService.imgVerifyCode(user, goodsId, tryCode);
        if (!verifyPass) {
            log.info("【執行秒殺】-- 驗證碼錯誤");
            throw new FlashSaleException(KAPTCHA_VERIFY_FAIL);
        }
        String path = miaoshaService.createPath(user, goodsId);
        return Result.success(path);
    };

使用攔截器,在攔截方法時拿到注解上的屬性

 @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {

        // 從redis中取到值
        Cookie cookie = CookieUtil.get(request, Constants.Cookie.TOKEN);
        if (cookie == null) {
            throw new FlashSaleException(AuthFailEnum.COOKIE_HAVE_NO_TOKEN);
        }
        StringRedisTemplate redisTemplate = ApplicationContextHolder.get().getBean("stringRedisTemplate", StringRedisTemplate.class);
        String userStr = redisTemplate.opsForValue().get(cookie.getValue());
        if (StringUtils.isEmpty(userStr)) {
            throw new FlashSaleException(AuthFailEnum.REDIS_HAVE_NOT_TOKEN);
        }
        User user = JSON.parseObject(userStr, User.class);
        UserContextHolder.set(user);
        if (handler instanceof HandlerMethod) {
            HandlerMethod hm = (HandlerMethod) handler;
            // 拿到注解的內容
            AccessLimit accessLimit = hm.getMethodAnnotation(AccessLimit.class);
            if (accessLimit == null) {
                // 不需要限流驗證
                return true;
            } else {
                // 需要限流驗證
                int limit = accessLimit.limit();
                int timeScope = accessLimit.timeScope();
                // 次數校驗(借助redis實現基於用戶的限流驗證)
                String requestURI = request.getRequestURI();
                final String redisKey = Constants.Cache.PATH_COUNT_PREFIX + user.getId() + ":" + requestURI;
                String currentCount = redisTemplate.opsForValue().get(redisKey);
                if (!StringUtils.isEmpty(currentCount)) {
                    int count = Integer.valueOf(currentCount);
                    if (count < limit) {
                        redisTemplate.opsForValue().increment(redisKey, 1);
                    } else {
                        // 訪問過於頻繁
                        throw new FlashSaleException(PATH_LIMIT_REACHED);
                    }
                } else {
                    redisTemplate.opsForValue().set(redisKey, "1", timeScope, TimeUnit.SECONDS);
                }
            }
        }
        UserContextHolder.set(user);
        renewExpiretime(response, cookie, userStr);
        return true;
    }


總結

 在實現了上述代碼后,當我們訪問到帶有AccessLimit注解的方法或類時,只要攔截器攔截了該請求,就能通過getMethodAnnotation()拿到注解上的limit和timeScope屬性,然后借助redis實現限流;比如某些接口我們可能想要2秒只能訪問1次,那么就把limit=1 timeScope=2,某些接口我們想要限制1分鍾訪問10次,就把limit=10, timeScope=60


免責聲明!

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



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