前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >专栏 >通过redis和注解实现ip访问频次限制

通过redis和注解实现ip访问频次限制

作者头像
烤红薯
发布于 2021-12-23 07:05:46
发布于 2021-12-23 07:05:46
2.2K00
代码可运行
举报
运行总次数:0
代码可运行

浙江的八月下旬还是有点热

实现目标:在接口上面加一个注解。限制单个ip在指定时间范围内可以访问的次数。

实现的逻辑是,将访问的ip和要访问的url作为key存放在reids中。

设定其数据类型为list,value的值为每次访问的时间戳。

redis中的数据如图:

验证方法:

当list的长度达到了设定的访问最大次数,

就和用当前的时间戳和最早存放的时间戳做对比。

若相差时间小于设定的时间范围,则说明此ip访问此接口达到了上限。

开始实现

新建自定义注解用在controller中需要限制的接口上面

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
import java.lang.annotation.*;
/**
 * ip 最大 访问次数
 * time 时间范围
 * (@interface 注解类)
 */
@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface IpMax {

    /**
     * 允许访问的最大次数
     */
    int count() default Integer.MAX_VALUE;

    /**
     * 时间段,单位为秒,默认值三十秒
     */
    int time() default 30;


}

使用如下, 直接在原有接口上面添加刚刚定义好的注解。其中 count 为最大访问次数,time为时间范围(本处时间单位采用的是秒)

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
@IpMax(count = 3, time = 10)
@ApiOperation("查询用户的数量")
@PostMapping("/getUserCount")
public long getUserCount() {
    return userService.getUserCount();
}

在拦截器中添加ip是否超频的验证逻辑

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
import com.batata.continuing.config.changeable.OpenOrClose;
import com.batata.continuing.config.count.ip.IpCount;
import com.batata.continuing.config.count.ip.IpMax;
import com.batata.continuing.config.moreNote.token.NotNeedToken;
import com.batata.continuing.utils.JwtUtil;
import org.nutz.lang.Lang;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.lang.reflect.Method;

public class JwtInterceptor implements HandlerInterceptor {

    @Autowired
    private OpenOrClose openOrClose;

    /**
     * 用来验证ip访问次数的逻辑层
     */
    @Autowired
    private IpCount ipCount;


    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object object) {

        // 从 http 请求头中取出 token
        String token = request.getHeader("token");
        // 如果不是映射到方法直接通过
        if (!(object instanceof HandlerMethod)) {
            return true;
        }
        HandlerMethod handlerMethod = (HandlerMethod) object;
        Method method = handlerMethod.getMethod();


        // ip超频访问方面的验证   start
        if (method.isAnnotationPresent(IpMax.class)) { // 判断访问的接口是否有此注解
            String requestURI = request.getRequestURI(); // 请求的url
            String ip = Lang.getIP(request); // 获得请求者的ip(可根据自己的方法,我这边用的是nutz的工具类,引入请参考历史文章)
            IpMax ipMax = method.getAnnotation(IpMax.class); // 获得注解中的内容
            int count = ipMax.count(); // 访问次数
            long time = ipMax.time(); //  时间范围
            // 通过封装的方法,判断ip是否可以通过验证
            boolean ipIsOk = ipCount.ipIsOk(requestURI, ip, count, time);
            if (!ipIsOk) { // 超过访问次数
                // 时间范围内超出最大访问次数   注:超过访问次数的处理方式可自行根据具体需求
                throw new RuntimeException("本接口" + time + "秒内可以请求" + count + "次,您已超出最大访问次数!!!");
            }
        }
        // ip超频访问方面的验证   end

     

        return true;
    }

    @Override
    public void postHandle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object o, ModelAndView modelAndView) throws Exception {

    }

    @Override
    public void afterCompletion(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object o, Exception e) throws Exception {

    }


}

ip验证的逻辑处理

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
import com.batata.continuing.config.RedisKeyConfig;
import com.batata.continuing.service.common.RedisService;
import com.batata.continuing.utils.DateUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Component;

import java.util.ArrayList;
import java.util.List;


/**
 * ip 相关的统计
 */
@Component
public class IpCount {


    /**
     * 封装的redis方法
     */
    @Autowired
    private RedisService redisService;


    @Autowired
    public RedisTemplate redisTemplate;


    /**
     * ip 是否可以继续访问
     *
     * @param url      访问的url
     * @param ip       访问的ip
     * @param maxValue 最大的访问次数
     * @param time     时间范围 单位为秒
     * @return true 可以继续访问  false 超出限制,不可以继续访问
     */
    public boolean ipIsOk(String url, String ip, int maxValue, long time) {
        boolean ipIsOk = true;

        String key = RedisKeyConfig.ipCount + ip + url; // 根据指定规则拼接生成此次访问的key
        List<Long> timeList = redisService.getCacheList(key); // 查询redis中已有的数据


        if (
                timeList.size() == 0 || timeList.size() < maxValue
        ) { // 没有记录或者没有达到最大访问次数不做超时验证
            addNewTime(key, time); // 添加当前的时间到list中
            return ipIsOk;

        } // 若不满足此条件,则证明list中的值达到了最大数量(即访问的次数)

        // 判断达到规定的访问次数的用时是否小于规定的时间
        // (当前时间戳-最旧记录的时间戳)< 限定的时间转毫秒
        if (
                (DateUtils.getNowTimeLong() - timeList.get(0)) < (time * 1000)
        ) {
            // 未达到,证明指定范围时间内访问数量超过的定义数量
            ipIsOk = false;
        }

        // 删除第一个值(就是时间最旧的那个值,我这边是下标为0的,手动在redis客户端测试的为row最大的值。这个根据自己的具体情况)
        redisTemplate.opsForList().remove(key, 1, timeList.get(0));
        addNewTime(key, time); // 添加当前的时间到list中

        return ipIsOk;
    }

    /**
     * 往redis中添加新的数据,注:新增的值row在后
     *
     * @param key  key
     * @param time 有效时间,单位为秒
     */
    public void addNewTime(String key, long time) {
        List<Long> nowTime = new ArrayList<>();
        nowTime.add(DateUtils.getNowTimeLong()); // 当前时间戳
        redisService.setCacheList(key, nowTime); // 追加值或新缓存值
        redisService.expire(key, time + 1); // 设置有效时间 
    }


}

// 用到的工具类 start

redis操作封装的service (仅含使用到的方法)

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.BoundSetOperations;
import org.springframework.data.redis.core.HashOperations;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.ValueOperations;
import org.springframework.stereotype.Component;

import java.util.*;
import java.util.concurrent.TimeUnit;

@SuppressWarnings(value = {"unchecked", "rawtypes"})
@Component
public class RedisService {


    @Autowired
    public RedisTemplate redisTemplate;

    /**
     * 设置有效时间
     *
     * @param key     Redis键
     * @param timeout 超时时间  注:此处时间单位为秒
     * @return true=设置成功;false=设置失败
     */
    public boolean expire(final String key, final long timeout) {
        return expire(key, timeout, TimeUnit.SECONDS);
    }

    
    /**
     * 缓存List数据
     *
     * @param key      缓存的键值
     * @param dataList 待缓存的List数据
     * @return 缓存的对象
     */
    public <T> long setCacheList(final String key, final List<T> dataList) {
        Long count = redisTemplate.opsForList().rightPushAll(key, dataList);
        return count == null ? 0 : count;
    }

    /**
     * 获得缓存的list对象
     *
     * @param key 缓存的键值
     * @return 缓存键值对应的数据
     */
    public <T> List<T> getCacheList(final String key) {
        return redisTemplate.opsForList().range(key, 0, -1);
    }

}

时间util(仅含使用到的方法)

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
import java.text.DecimalFormat;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Calendar;
import java.util.Date;

import org.apache.commons.lang3.time.DateFormatUtils;

public class DateUtils {
      
    /**
     * 获得当前时间的时间戳
     * @return 当前时间戳
     */
    public static long getNowTimeLong() {
        return System.currentTimeMillis();
    }


}

// 用到的工具类 end

启动项目访问加了注解的接口测试

第四次访问出现请求不通过,(此处的处理方式为抛出了一个异常)配置成功

查看redis中的数据为

另注:也可以配合超频做一个黑名单的机制

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2021-08-28,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 jackHarbor 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
暂无评论
推荐阅读
编辑精选文章
换一批
使用redis计数来控制单位时间内对某接口的访问量,防止刷验证码接口之类的
使用自定义注解的方式,在需要被限制访问频率的方法上加注解即可控制。 看实现方式,基于springboot,aop,redis。 新建Springboot工程,引入redis,aop。 创建注解 package com.tianyalei.annotation; import org.springframework.core.Ordered; import org.springframework.core.annotation.Order; import java.lang.annotation.*;
天涯泪小武
2019/01/17
1.8K0
太优雅了!用Redis高效实现限流功能!
在高并发场景下,接口限流能够防止系统过载,确保服务的可用性和稳定性。限流策略的选择和实现方式,直接影响到用户体验和系统的负载能力。而Redis作为强大的内存数据库,以其卓越的性能和原子操作特性,成为了实现接口限流的理想选择。它不仅可以快速响应请求,还能通过其丰富的数据结构,如字符串、列表、有序集合等,来辅助实现多样化的限流逻辑。
程序员小义
2024/04/25
4.3K0
太优雅了!用Redis高效实现限流功能!
用 SpringBoot+Redis 解决海量重复提交问题
在实际的开发项目中,一个对外暴露的接口往往会面临很多次请求,我们来解释一下幂等的概念:任意多次执行所产生的影响均与一次执行的影响相同。按照这个含义,最终的含义就是 对数据库的影响只能是一次性的,不能重复处理。如何保证其幂等性,通常有以下手段:
南风
2020/04/22
3.2K0
用 SpringBoot+Redis 解决海量重复提交问题
美团面试:接口被恶意狂刷,怎么办?
首先,我们创建一个简单的controller,然后,在方法上加上我们自定义的注解AccessLimit,就可以实现接口防刷了。
麦洛
2021/08/23
7250
美团面试:接口被恶意狂刷,怎么办?
SpringBoot整合Redis
本篇博客是我github上our-task:一个完整的清单管理系统的配套教程文档,这是SpringBoot+Vue开发的前后端分离清单管理工具,仿滴答清单。目前已部署在阿里云ECS上,可进行在线预览,随意使用(附详细教程),大家感兴趣的话,欢迎给个star!
呆小鱼LQ
2020/12/29
7891
SpringBoot整合Redis
Springboot中使用redis进行api防刷限流
首先我们编写注解类AccessLimit,使用注解方式在方法上限流更优雅更方便!三个参数分别代表有效时间、最大访问次数、是否需要登录,可以理解为 seconds 内最多访问 maxCount 次。
海向
2019/12/12
1.3K0
点赞模块设计:Redis缓存 + 定时写入数据库实现高性能点赞功能
本文基于 SpringCloud, 用户发起点赞、取消点赞后先存入 Redis 中,再每隔两小时从 Redis 读取点赞数据写入数据库中做持久化存储。
田维常
2019/11/12
6.9K1
点赞模块设计:Redis缓存 + 定时写入数据库实现高性能点赞功能
Spring Boot 项目的 API 接口防刷
来源:blog.csdn.net/weixin_42533856/article/details/82593123
芋道源码
2019/11/04
1.1K0
SpringBoot Redis 解决重复提交问题
在实际的开发项目中,一个对外暴露的接口往往会面临很多次请求,我们来解释一下幂等的概念:任意多次执行所产生的影响均与一次执行的影响相同。按照这个含义,最终的含义就是 对数据库的影响只能是一次性的,不能重复处理。如何保证其幂等性,通常有以下手段:
FHAdmin
2021/12/20
9920
SpringBoot实现接口限流(Redis版本)
随着前后端分离架构的流⾏,前端页面与后端接口的调⽤关系越来越复杂,后端服务的稳定性越来越重要。在遇到突发的请求量激增,恶意的⽤户访问,亦或请求频率过⾼给下游服务带来较⼤压⼒时,我们常常需要通过缓存、限流、负载均衡等多种⽅式保证后端服务的稳定性。其中,限流是不可或缺的⼀环。
水煮麥楽雞
2022/11/20
9290
SpringBoot入门建站全系列(十四)集成Redis缓存
本地缓存,就是使用应用内使用本地内存将数据暂缓存储,一般数据库的查询如果不怎么改动,可以用本地缓存暂存。
品茗IT
2019/08/16
5460
Spring和Redis整合详解
Redis是一个开源(BSD许可)的内存数据结构存储,用作数据库,缓存和消息代理。
品茗IT
2019/09/12
2K0
用Redis实现短链接点击统计
事情发生在9月8号晚淘宝促销活动,短链接应用突然数据库连接飙升,监控中发现有SQL在疯狂地更新,其中有一条就是更新短链接的点击数。查看了该接口功能其实非常简单:判断ip是否合法,然后短链接的点击数+1,更新到数据库表。
用户3467126
2020/12/01
1.2K0
用Redis实现短链接点击统计
Springboot项目的接口防刷
说明:使用了注解的方式进行对接口防刷的功能,非常高大上,本文章仅供参考 一,技术要点:springboot的基本知识,redis基本操作, 首先是写一个注解类: import java.lang.annotation.Retention;import java.lang.annotation.Target; import static java.lang.annotation.ElementType.METHOD;import static java.lang.annotation.RetentionPol
JAVA葵花宝典
2019/05/24
7500
spring AOP _Redis 防止接口重复
1.自定义注解 package com.ptdot.portal.aop; import java.lang.annotation.*; @Target({ElementType.METHOD}) @Retention(RetentionPolicy.RUNTIME) @Documented public @interface NoRepeatSubmit { /** * 默认1s钟以内算重复提交 * @return */ long timeout() defa
派大星在吗
2021/12/17
2430
SpringBoot整合拦截器之接口加密
以上就是springBoot整合拦截器接口加密 感觉是不是超简单! 有什么问题可以联系我哈。
猿码优创
2019/11/28
7950
SpringBoot整合拦截器之接口加密
SpringBoot集成Redis和配置Redis做缓存[通俗易懂]
版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。
全栈程序员站长
2022/11/08
1.4K0
SpringBoot防止大量请求攻击
发布者:全栈程序员栈长,转载请注明出处:https://javaforall.cn/162102.html原文链接:https://javaforall.cn
全栈程序员站长
2022/09/13
1.2K0
SpringBoot防止大量请求攻击
相关推荐
使用redis计数来控制单位时间内对某接口的访问量,防止刷验证码接口之类的
更多 >
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档