Springboot2.x 结合 redis 实现ip请求次数限制

参考

  1. https://cloud.tencent.com/developer/article/1607647 Spring Boot整合Redis代码详解,四步搞定!
  2. https://blog.csdn.net/jinyangbest/article/details/98205802 springboot 拦截器@Autowired 注入失败
  3. https://www.cnblogs.com/harriets-zhang/p/14517312.html SpringBoot配置RedisTemplate
  4. https://blog.csdn.net/jinyangbest/article/details/98205802 springboot 拦截器@Autowired 注入失败
  5. https://redis.com.cn/redis-installation.html 在 windows 上安装 Redis
  6. https://www.cnblogs.com/xiaqiuchu/p/15106720.html Springboot2.3.5 实现JWT授权验证并针对不同用户实现多个拦截

环境

环境 版本 操作
windows 10
JDK 11
Springboot 2.3.7.RELEASE
spring-boot-starter-data-redis
redis x64-3.0.504 免安装版 下载

实现功能

根据用户ip,每个url接口每60秒只能请求2次。

遇到的问题

可能是好久没写java,忘记了springboot加载流程。导致 redis 拦截器注入失败

正文

1. windows 安装 redis

2. pom引入依赖

<dependencies>
	<dependency>
		<groupId>org.springframework.boot</groupId>
		<artifactId>spring-boot-starter-data-redis</artifactId>
	</dependency>
	<!--lombok 不是必须,只是本文章内代码包含了 lombok 的使用-->
	<dependency>
		<groupId>org.projectlombok</groupId>
		<artifactId>lombok</artifactId>
		<optional>true</optional>
	</dependency>
</dependencies>

3. application.properties 添加配置

# redis 配置
# https://zhuanlan.zhihu.com/p/139528556
#spring.redis.host=127.0.0.1
#spring.redis.port=6379
##spring.redis.password=
#spring.redis.database=0
#spring.redis.timeout=5000
#spring.redis.jedis.pool.max-active=100
#spring.redis.jedis.pool.max-idle=10
#spring.redis.jedis.pool.max-wait=100000

############################################################
# REDIS 配置 https://cloud.tencent.com/developer/article/1607647
############################################################
spring.redis.database=0
spring.redis.host=127.0.0.1
spring.redis.port=6379
spring.redis.password=
spring.redis.jedis.pool.max-active=8
spring.redis.jedis.pool.max-wait=-1
spring.redis.jedis.pool.max-idle=10
spring.redis.jedis.pool.min-idle=2
spring.redis.timeout=6000


# 请求ip限流限制
# 一分钟内只能请求 60 次
# 请求数量
request.limit.count=2
# 请求时间
request.limit.seconds=60

4. 创建 RedisConfig


import com.fasterxml.jackson.annotation.JsonAutoDetect;
import com.fasterxml.jackson.annotation.PropertyAccessor;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.jsontype.impl.LaissezFaireSubTypeValidator;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer;

/**
 * 解决redis 拦截器注入失败问题
 * https://blog.csdn.net/jinyangbest/article/details/98205802
 * redis配置
 * https://www.cnblogs.com/harriets-zhang/p/14517312.html
 */
@Configuration
public class RedisConfig {

    @Bean
    public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory) {
        RedisTemplate<String, Object> template = new RedisTemplate<>();

        template.setConnectionFactory(redisConnectionFactory);

        //配置序列化方式
        Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer(Object.class);

        ObjectMapper obm=new ObjectMapper();
        // 指定要序列化的域,field,get和set,以及修饰符范围,ANY是都有包括private和publi
        obm.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
        // 指定序列化输入的类型,类必须是非final修饰的,final修饰的类,比如String,Integer等会跑出异常
        obm.activateDefaultTyping(LaissezFaireSubTypeValidator.instance,ObjectMapper.DefaultTyping.NON_FINAL);

        jackson2JsonRedisSerializer.setObjectMapper(obm);

        StringRedisSerializer stringRedisSerializer = new StringRedisSerializer();
        //key 采用String的序列化方式
        template.setKeySerializer(stringRedisSerializer);
        //hash
        template.setHashKeySerializer(stringRedisSerializer);
        //value
        template.setValueSerializer(jackson2JsonRedisSerializer);
        template.afterPropertiesSet();
        return template;
    }

}

5. 自定义拦截器异常

import lombok.Data;
import org.springframework.http.HttpStatus;

/**
 * @author xiaqiuchu
 */
@Data
public class BaseException extends RuntimeException{
    public HttpStatus httpCode = 404;
    BaseException(String msg){
        super(msg);
    }
}

/**
 * 请求超出次数限制
 */
public class RequestLimitException extends BaseException{
    /**
     * 请求超出次数限制
     */
    public RequestLimitException(){
        super("请求超出次数限制");
    }
}

6. 请求限流拦截器处理类


import cn.hutool.extra.servlet.ServletUtil;
import com.xxx.xxx.exception.RequestLimitException;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.web.servlet.handler.HandlerInterceptorAdapter;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.concurrent.TimeUnit;

/**
 * 请求限制拦截器
 * 结合 redis https://cloud.tencent.com/developer/article/1607647
 */
// 注意,这里不需要加 @Component 注解,在IntercaptorConfig手动注册bean
@Slf4j
public class RequestLimitInterceptor extends HandlerInterceptorAdapter {

    @Autowired
    private RedisTemplate<String, Object> redisTemplate;

    /**
     * 次数
     */
    @Value("${request.limit.count}")
    private Integer requestLimitCount;

    /**
     * 秒
     */
    @Value("${request.limit.seconds}")
    private Integer requestLimitSeconds;

    @Override
    public  boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        log.info("进入RequestLimitInterceptor拦截器了");
        this.updateCurrentIpRequestNum(request);
        return true;

    }

    /**
     * 更新当前用户的请求数
     *
     * @param request
     * @return
     */
    private void updateCurrentIpRequestNum(HttpServletRequest request){
        String userIP = ServletUtil.getClientIP(request, null);
        String currentUserCacheKey = request.getRequestURL() + userIP;
        /**
         * 判断指定缓存key是否存在,不存在并且过期 就创建新的
         */
        if(!redisTemplate.hasKey(currentUserCacheKey) || redisTemplate.getExpire(currentUserCacheKey) <= 0){
            redisTemplate.opsForValue().set(currentUserCacheKey, 1, requestLimitSeconds, TimeUnit.SECONDS);
        }else{
            redisTemplate.opsForValue().increment(currentUserCacheKey, 1);
        }
        //
        Integer newNum = (Integer) redisTemplate.opsForValue().get(currentUserCacheKey);
        log.info("访问IP:{},请求URL:{},请求次数:{},请求限制:{}次,请求限制:{}秒",userIP, request.getRequestURL(), newNum, requestLimitCount, requestLimitSeconds);
        if(newNum > requestLimitCount){
            throw new RequestLimitException();
        }
    }
}


7. 拦截器配置类(注册拦截器)

// 引入拦截器逻辑处理类
import com.xxx.xxx.interceptor.RequestLimitInterceptor;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

/**
 * 拦截器配置类
 * https://www.cnblogs.com/xiaqiuchu/p/15106720.html
 */
@Configuration
public class IntercaptorConfig  implements WebMvcConfigurer {

    /**
     * 解决拦截器注入问题,注入为null
     * https://blog.csdn.net/jinyangbest/article/details/98205802
     * @return
     */
    @Bean
    public RequestLimitInterceptor requestLimitInterceptor(){
        return new RequestLimitInterceptor();
    }

    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        /**
         * 请求限制拦截器
         */
        registry.addInterceptor(requestLimitInterceptor())
                /**
                 * 拦截的路径
                 */
                .addPathPatterns("/**");
    }

posted @   夏秋初  阅读(483)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· 阿里巴巴 QwQ-32B真的超越了 DeepSeek R-1吗?
· 【译】Visual Studio 中新的强大生产力特性
· 【设计模式】告别冗长if-else语句:使用策略模式优化代码结构
· 10年+ .NET Coder 心语 ── 封装的思维:从隐藏、稳定开始理解其本质意义
点击右上角即可分享
微信分享提示