zoukankan      html  css  js  c++  java
  • 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

  • 相关阅读:
    leetcode_138复制带随机指针的链表
    minSTL
    LLVM
    STL基础_迭代器
    mysql数据库表清空后id如何从1开始自增
    explain用法和结果分析
    MySQL多表查询与子查询
    数据结构与算法笔记
    MySQL数据库的SQL语言与视图
    mysql忘记密码解决方案
  • 原文地址:https://www.cnblogs.com/devise/p/10959885.html
Copyright © 2011-2022 走看看