如何限制用户在某一时间段多次访问接口

要知道,如今很多平台的接口都是可以同时被门户网站,手机端,移动浏览器访问,因为接口是通用的,而为了安全起见,有些接口都会设置一个门槛,那就是限制访问次数,也就是在某一时间段内不能过多的访问,比如登录次数限制,在一些金融理财或者银行的接口上比较常见,另外一些与用户信息有关的接口都会有一个限制门槛

那么这个限制门槛怎么来做呢,其实有很多种方法,主流的做法可以用拦截器或者注解,那么今天咱们用注解来实现

首先需要定义一个注解,如下:

/**
 * 
 * @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的剩余时间:

好,那么按照如上方法就能实现对接口访问次数的限制

posted @ 2016-12-14 22:50  风间影月  阅读(21405)  评论(2编辑  收藏  举报