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

本文涉及的产品
云数据库 Redis 版,标准版 2GB
推荐场景:
搭建游戏排行榜
云原生内存数据库 Tair,内存型 2GB
简介: 要知道,如今很多平台的接口都是可以同时被门户网站,手机端,移动浏览器访问,因为接口是通用的,而为了安全起见,有些接口都会设置一个门槛,那就是限制访问次数,也就是在某一时间段内不能过多的访问,比如登录次数限制,在一些金融理财或者银行的接口上比较常见,另外一些与用户信息有关的接口都会有一个限制门槛那么...

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

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

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

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

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

相关实践学习
基于Redis实现在线游戏积分排行榜
本场景将介绍如何基于Redis数据库实现在线游戏中的游戏玩家积分排行榜功能。
云数据库 Redis 版使用教程
云数据库Redis版是兼容Redis协议标准的、提供持久化的内存数据库服务,基于高可靠双机热备架构及可无缝扩展的集群架构,满足高读写性能场景及容量需弹性变配的业务需求。 产品详情:https://www.aliyun.com/product/kvstore     ------------------------------------------------------------------------- 阿里云数据库体验:数据库上云实战 开发者云会免费提供一台带自建MySQL的源数据库 ECS 实例和一台目标数据库 RDS实例。跟着指引,您可以一步步实现将ECS自建数据库迁移到目标数据库RDS。 点击下方链接,领取免费ECS&RDS资源,30分钟完成数据库上云实战!https://developer.aliyun.com/adc/scenario/51eefbd1894e42f6bb9acacadd3f9121?spm=a2c6h.13788135.J_3257954370.9.4ba85f24utseFl
相关文章
|
2月前
|
机器学习/深度学习 人工智能 缓存
函数计算产品使用问题之在第一次启动时请求外部接口总是超时,是什么导致的
函数计算产品作为一种事件驱动的全托管计算服务,让用户能够专注于业务逻辑的编写,而无需关心底层服务器的管理与运维。你可以有效地利用函数计算产品来支撑各类应用场景,从简单的数据处理到复杂的业务逻辑,实现快速、高效、低成本的云上部署与运维。以下是一些关于使用函数计算产品的合集和要点,帮助你更好地理解和应用这一服务。
|
4月前
|
弹性计算 运维 Shell
统计每个远程IP访问次数
【4月更文挑战第29天】
41 1
|
4月前
|
监控 JavaScript 应用服务中间件
匿名用户访问的接口或者无登录态场景下接口防刷的解决方案
匿名用户访问的接口或者无登录态场景下接口防刷的解决方案
88 0
|
存储 Java
报警系统QuickAlarm之频率统计及接口封装
前面将报警规则的制定加载解析,以及报警执行器的定义加载和扩展进行了讲解,基本上核心的内容已经完结,接下来剩下内容就比较简单了 1.报警频率的统计 2.报警线程池 3.对外封装统一可用的解耦
237 0
报警系统QuickAlarm之频率统计及接口封装
|
Web App开发 存储 弹性计算
日志服务之分析用户访问行为-4
日志服务之分析用户访问行为-4
93 0
|
监控 应用服务中间件 nginx
日志服务之分析用户访问行为-5
日志服务之分析用户访问行为-5
176 0
|
Web App开发 存储 监控
日志服务之分析用户访问行为-3
日志服务之分析用户访问行为-3
104 0
|
Web App开发 弹性计算 监控
日志服务之分析用户访问行为-2
日志服务之分析用户访问行为-2
102 0
|
数据采集 弹性计算 运维
日志服务之分析用户访问行为-1
日志服务之分析用户访问行为-1
175 0