本文将介绍在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);};
使用拦截器,在拦截方法时拿到注解上的属性
@Overridepublic 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
