要知道,如今很多平台的接口都是可以同時被門戶網站,手機端,移動瀏覽器訪問,因為接口是通用的,而為了安全起見,有些接口都會設置一個門檻,那就是限制訪問次數,也就是在某一時間段內不能過多的訪問,比如登錄次數限制,在一些金融理財或者銀行的接口上比較常見,另外一些與用戶信息有關的接口都會有一個限制門檻
那么這個限制門檻怎么來做呢,其實有很多種方法,主流的做法可以用攔截器或者注解,那么今天咱們用注解來實現
首先需要定義一個注解,如下:
/** * * @Title: LimitIPRequest.java * @Package com.agood.bejavagod.component * @Description: 限制某個IP在某個時間段內請求某個方法的次數 * Copyright: Copyright (c) 2016 * Company:Nathan.Lee.Salvatore * * @author leechenxiang * @date 2016年12月14日 下午8:16:49 * @version V1.0 */ @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.METHOD) @Documented @Order(Ordered.HIGHEST_PRECEDENCE) // 設置順序為最高優先級 public @interface LimitIPRequest { /** * * @Description: 限制某時間段內可以訪問的次數,默認設置100 * @return * * @author leechenxiang * @date 2016年12月14日 下午8:22:29 */ int limitCounts() default 100; /** * * @Description: 限制訪問的某一個時間段,單位為秒,默認值1分鍾即可 * @return * * @author leechenxiang * @date 2016年12月14日 下午8:21:59 */ int timeSecond() default 60; }
然后再使用spring aop,攔截被你注解的那個controller的方法
@Aspect @Component public class LimitIPRequestDisplay { @Autowired private JedisClient jedis; @Pointcut("execution(* com.agood.bejavagod.controller.*.*(..)) && @annotation(com.agood.bejavagod.component.LimitIPRequest)") public void before(){ } @Before("before()") public void requestLimit(JoinPoint joinPoint) throws LimitIPRequestException { try { // 獲取HttpRequest ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes(); HttpServletRequest request = attributes.getRequest(); HttpServletResponse response = attributes.getResponse(); // 判斷request不能為空 if (request == null) { throw new LimitIPRequestException("HttpServletRequest有誤..."); } LimitIPRequest limit = this.getAnnotation(joinPoint); if(limit == null) { return; } String ip = request.getRemoteAddr(); String uri = request.getRequestURI().toString(); String redisKey = "limit-ip-request:" + uri + ":" + ip; // 設置在redis中的緩存,累加1 long count = jedis.incr(redisKey); // 如果該key不存在,則從0開始計算,並且當count為1的時候,設置過期時間 if (count == 1) { jedis.expire(redisKey, limit.timeSecond()); // redisTemplate.expire(redisKey, limit.time(), TimeUnit.MILLISECONDS); } // 如果redis中的count大於限制的次數,則報錯 if (count > limit.limitCounts()) { // logger.info("用戶IP[" + ip + "]訪問地址[" + url + "]超過了限定的次數[" + limit.count() + "]"); if (ShiroFilterUtils.isAjax(request)) { HttpServletResponse httpServletResponse = WebUtils.toHttp(response); httpServletResponse.sendError(ShiroFilterUtils.HTTP_STATUS_LIMIT_IP_REQUEST); } else { throw new LimitIPRequestException(); } } } catch (LimitIPRequestException e) { throw e; } catch (Exception e) { e.printStackTrace(); } } /** * * @Description: 獲得注解 * @param joinPoint * @return * @throws Exception * * @author leechenxiang * @date 2016年12月14日 下午9:55:32 */ private LimitIPRequest getAnnotation(JoinPoint joinPoint) throws Exception { Signature signature = joinPoint.getSignature(); MethodSignature methodSignature = (MethodSignature) signature; Method method = methodSignature.getMethod(); if (method != null) { return method.getAnnotation(LimitIPRequest.class); } return null; } }
這個類使用了redis緩存作為計數器,因為好用,當然你用靜態的map也行,但是考慮的分布式集群的話一般還是建議使用redis比較好。
大致的流程就是要獲取redis中的調用方法次數,使用incr函數,當key不存在的時候默認為0然后累加1,當累加1大於limit設置的限制次數時,則拋出異常,這個地方需要注意,如果是ajax調用的話需要判斷是否ajax,然后再返回錯誤信息

查看redis中key的剩余時間:
好,那么按照如上方法就能實現對接口訪問次數的限制
