Springboot使用redis實(shí)現(xiàn)接口Api限流的示例代碼
前言

該篇介紹的內(nèi)容如題,就是利用redis實(shí)現(xiàn)接口的限流( 某時(shí)間范圍內(nèi) 最大的訪問(wèn)次數(shù) ) 。
正文
慣例,先看下我們的實(shí)戰(zhàn)目錄結(jié)構(gòu):

首先是pom.xml 核心依賴:
<!--用于redis數(shù)據(jù)庫(kù)連接-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<!--用于redis lettuce 連接池pool使用-->
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-pool2</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>然后是application.yml里面的redis接入配置:
spring:
redis:
lettuce:
pool:
#連接池最大連接數(shù) 使用負(fù)值代表無(wú)限制 默認(rèn)為8
max-active: 10
#最大空閑連接 默認(rèn)8
max-idle: 10
#最小空閑連接 默認(rèn)0
min-idle: 1
host: 127.0.0.1
password: 123456
port: 6379
database: 0
timeout: 2000ms
server:
port: 8710redis的配置類, RedisConfig.java:
import com.fasterxml.jackson.annotation.JsonAutoDetect;
import com.fasterxml.jackson.annotation.PropertyAccessor;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.cache.CacheManager;
import org.springframework.cache.annotation.EnableCaching;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.cache.RedisCacheConfiguration;
import org.springframework.data.redis.cache.RedisCacheManager;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.RedisSerializationContext;
import org.springframework.data.redis.serializer.StringRedisSerializer;
import static org.springframework.data.redis.cache.RedisCacheConfiguration.defaultCacheConfig;
/**
* @Author: JCccc
* @CreateTime: 2018-09-11
* @Description:
*/
@Configuration
@EnableCaching
public class RedisConfig {
@Bean
public CacheManager cacheManager(RedisConnectionFactory connectionFactory) {
RedisCacheConfiguration cacheConfiguration =
defaultCacheConfig()
.disableCachingNullValues()
.serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(new Jackson2JsonRedisSerializer(Object.class)));
return RedisCacheManager.builder(connectionFactory).cacheDefaults(cacheConfiguration).build();
}
@Bean
public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory factory) {
RedisTemplate<String, Object> redisTemplate = new RedisTemplate<>();
redisTemplate.setConnectionFactory(factory);
Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer(Object.class);
ObjectMapper om = new ObjectMapper();
om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
om.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
jackson2JsonRedisSerializer.setObjectMapper(om);
//序列化設(shè)置 ,這樣為了存儲(chǔ)操作對(duì)象時(shí)正常顯示的數(shù)據(jù),也能正常存儲(chǔ)和獲取
redisTemplate.setKeySerializer(new StringRedisSerializer());
redisTemplate.setValueSerializer(jackson2JsonRedisSerializer);
redisTemplate.setHashKeySerializer(new StringRedisSerializer());
redisTemplate.setHashValueSerializer(jackson2JsonRedisSerializer);
return redisTemplate;
}
@Bean
public StringRedisTemplate stringRedisTemplate(RedisConnectionFactory factory) {
StringRedisTemplate stringRedisTemplate = new StringRedisTemplate();
stringRedisTemplate.setConnectionFactory(factory);
return stringRedisTemplate;
}
}自定義注解:
import java.lang.annotation.*;
/**
* @Author JCccc
* @Description
* @Date 2021/7/23 11:46
*/
@Inherited
@Documented
@Target({ElementType.FIELD, ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface RequestLimit {
/**
* 時(shí)間內(nèi) 秒為單位
*/
int second() default 10;
/**
* 允許訪問(wèn)次數(shù)
*/
int maxCount() default 5;
//默認(rèn)效果 : 10秒內(nèi) 對(duì)于使用該注解的接口,只能總請(qǐng)求訪問(wèn)數(shù) 不能大于 5次
}接下來(lái)是攔截器 RequestLimitInterceptor.java:
攔截接口的方式 是通過(guò) ip地址+接口url ,做時(shí)間內(nèi)的訪問(wèn)計(jì)數(shù)
import com.elegant.testdemo.annotation.RequestLimit;
import com.elegant.testdemo.utils.IpUtil;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Component;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.HandlerInterceptor;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.concurrent.TimeUnit;
/**
* @Author JCccc
* @Description
* @Date 2021/7/23 11:49
*/
@Component
public class RequestLimitInterceptor implements HandlerInterceptor {
private final Logger log = LoggerFactory.getLogger(this.getClass());
@Autowired
private RedisTemplate<String, Object> redisTemplate;
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
try {
if (handler instanceof HandlerMethod) {
HandlerMethod handlerMethod = (HandlerMethod) handler;
// 獲取RequestLimit注解
RequestLimit requestLimit = handlerMethod.getMethodAnnotation(RequestLimit.class);
if (null==requestLimit) {
return true;
}
//限制的時(shí)間范圍
int seconds = requestLimit.second();
//時(shí)間內(nèi)的 最大次數(shù)
int maxCount = requestLimit.maxCount();
String ipAddr = IpUtil.getIpAddr(request);
// 存儲(chǔ)key
String key = ipAddr+":"+request.getContextPath() + ":" + request.getServletPath();
// 已經(jīng)訪問(wèn)的次數(shù)
Integer count = (Integer) redisTemplate.opsForValue().get(key);
log.info("檢測(cè)到目前ip對(duì)接口={}已經(jīng)訪問(wèn)的次數(shù)", request.getServletPath() , count);
if (null == count || -1 == count) {
redisTemplate.opsForValue().set(key, 1, seconds, TimeUnit.SECONDS);
return true;
}
if (count < maxCount) {
redisTemplate.opsForValue().increment(key);
return true;
}
log.warn("請(qǐng)求過(guò)于頻繁請(qǐng)稍后再試");
returnData(response);
return false;
}
return true;
} catch (Exception e) {
log.warn("請(qǐng)求過(guò)于頻繁請(qǐng)稍后再試");
e.printStackTrace();
}
return true;
}
public void returnData(HttpServletResponse response) throws IOException {
response.setCharacterEncoding("UTF-8");
response.setContentType("application/json; charset=utf-8");
ObjectMapper objectMapper = new ObjectMapper();
//這里傳提示語(yǔ)可以改成自己項(xiàng)目的返回?cái)?shù)據(jù)封裝的類
response.getWriter().println(objectMapper.writeValueAsString("請(qǐng)求過(guò)于頻繁請(qǐng)稍后再試"));
return;
}
}接下來(lái)是 攔截器的配置 WebConfig.java:
import com.elegant.testdemo.interceptor.RequestLimitInterceptor;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
/**
* @Author JCccc
* @Description
* @Date 2021/7/23 11:52
*/
@Configuration
public class WebConfig implements WebMvcConfigurer {
@Autowired
private RequestLimitInterceptor requestLimitInterceptor;
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(requestLimitInterceptor)
//攔截所有請(qǐng)求路徑
.addPathPatterns("/**")
//再設(shè)置 放開哪些路徑
.excludePathPatterns("/static/**","/auth/login");
}
}最后還有兩個(gè)工具類
import javax.servlet.http.HttpServletRequest;
import java.net.InetAddress;
import java.net.UnknownHostException;
/**
* @Author : JCccc
* @CreateTime : 2018-11-23
* @Description :
* @Point: Keep a good mood
**/
public class IpUtil {
public static String getIpAddr(HttpServletRequest request) {
String ipAddress = null;
try {
ipAddress = request.getHeader("x-forwarded-for");
if (ipAddress == null || ipAddress.length() == 0 || "unknown".equalsIgnoreCase(ipAddress)) {
ipAddress = request.getHeader("Proxy-Client-IP");
}
if (ipAddress == null || ipAddress.length() == 0 || "unknown".equalsIgnoreCase(ipAddress)) {
ipAddress = request.getHeader("WL-Proxy-Client-IP");
}
if (ipAddress == null || ipAddress.length() == 0 || "unknown".equalsIgnoreCase(ipAddress)) {
ipAddress = request.getRemoteAddr();
if (ipAddress.equals("127.0.0.1")) {
// 根據(jù)網(wǎng)卡取本機(jī)配置的IP
InetAddress inet = null;
try {
inet = InetAddress.getLocalHost();
} catch (UnknownHostException e) {
e.printStackTrace();
}
ipAddress = inet.getHostAddress();
}
}
// 對(duì)于通過(guò)多個(gè)代理的情況,第一個(gè)IP為客戶端真實(shí)IP,多個(gè)IP按照','分割
if (ipAddress != null && ipAddress.length() > 15) { // "***.***.***.***".length()
// = 15
if (ipAddress.indexOf(",") > 0) {
ipAddress = ipAddress.substring(0, ipAddress.indexOf(","));
}
}
} catch (Exception e) {
ipAddress="";
}
// ipAddress = this.getRequest().getRemoteAddr();
return ipAddress;
}
}RedisUtil :
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.*;
import org.springframework.stereotype.Component;
import java.io.Serializable;
import java.util.List;
import java.util.Set;
@Component
public class RedisUtils {
@Autowired
private RedisTemplate redisTemplate;
/**
* 寫入String型 [ 鍵,值]
*
* @param key
* @param value
* @return
*/
public boolean set(final String key, Object value) {
boolean result = false;
try {
ValueOperations<Serializable, Object> operations = redisTemplate.opsForValue();
operations.set(key, value);
result = true;
} catch (Exception e) {
e.printStackTrace();
}
return result;
}
/**
* 寫入String型,順便帶有過(guò)期時(shí)間 [ 鍵,值]
*
* @param key
* @param value
* @return
*/
public boolean setWithTime(final String key, Object value,int seconds) {
boolean result = false;
try {
ValueOperations<Serializable, Object> operations = redisTemplate.opsForValue();
operations.set(key, value,seconds, TimeUnit.SECONDS);
result = true;
} catch (Exception e) {
e.printStackTrace();
}
return result;
}
/**
* 批量刪除對(duì)應(yīng)的value
*
* @param keys
*/
public void remove(final String... keys) {
for (String key : keys) {
remove(key);
}
}
/**
* 批量刪除key
*
* @param pattern
*/
public void removePattern(final String pattern) {
Set<Serializable> keys = redisTemplate.keys(pattern);
if (keys.size() > 0)
redisTemplate.delete(keys);
}
/**
* 刪除對(duì)應(yīng)的value
*
* @param key
*/
public void remove(final String key) {
if (exists(key)) {
redisTemplate.delete(key);
}
}
/**
* 判斷緩存中是否有對(duì)應(yīng)的value
*
* @param key
* @return
*/
public boolean exists(final String key) {
return redisTemplate.hasKey(key);
}
/**
* 讀取緩存
*
* @param key
* @return
*/
public Object get(final String key) {
Object result = null;
ValueOperations<Serializable, Object> operations = redisTemplate.opsForValue();
result = operations.get(key);
return result;
}
/**
* 哈希 添加
* hash 一個(gè)鍵值(key->value)對(duì)集合
*
* @param key
* @param hashKey
* @param value
*/
public void hmSet(String key, Object hashKey, Object value) {
HashOperations<String, Object, Object> hash = redisTemplate.opsForHash();
hash.put(key, hashKey, value);
}
/**
* Hash獲取數(shù)據(jù)
*
* @param key
* @param hashKey
* @return
*/
public Object hmGet(String key, Object hashKey) {
HashOperations<String, Object, Object> hash = redisTemplate.opsForHash();
return hash.get(key, hashKey);
}
/**
* 列表添加
* list:lpush key value1
*
* @param k
* @param v
*/
public void lPush(String k, Object v) {
ListOperations<String, Object> list = redisTemplate.opsForList();
list.rightPush(k, v);
}
/**
* 列表List獲取
* lrange: key 0 10 (讀取的個(gè)數(shù) 從0開始 讀取到下標(biāo)為10 的數(shù)據(jù))
*
* @param k
* @param l
* @param l1
* @return
*/
public List<Object> lRange(String k, long l, long l1) {
ListOperations<String, Object> list = redisTemplate.opsForList();
return list.range(k, l, l1);
}
/**
* Set集合添加
*
* @param key
* @param value
*/
public void add(String key, Object value) {
SetOperations<String, Object> set = redisTemplate.opsForSet();
set.add(key, value);
}
/**
* Set 集合獲取
*
* @param key
* @return
*/
public Set<Object> setMembers(String key) {
SetOperations<String, Object> set = redisTemplate.opsForSet();
return set.members(key);
}
/**
* Sorted set :有序集合添加
*
* @param key
* @param value
* @param scoure
*/
public void zAdd(String key, Object value, double scoure) {
ZSetOperations<String, Object> zset = redisTemplate.opsForZSet();
zset.add(key, value, scoure);
}
/**
* Sorted set:有序集合獲取
*
* @param key
* @param scoure
* @param scoure1
* @return
*/
public Set<Object> rangeByScore(String key, double scoure, double scoure1) {
ZSetOperations<String, Object> zset = redisTemplate.opsForZSet();
return zset.rangeByScore(key, scoure, scoure1);
}
/**
* 根據(jù)key獲取Set中的所有值
*
* @param key 鍵
* @return
*/
public Set<Integer> sGet(String key) {
try {
return redisTemplate.opsForSet().members(key);
} catch (Exception e) {
e.printStackTrace();
return null;
}
}
/**
* 根據(jù)value從一個(gè)set中查詢,是否存在
*
* @param key 鍵
* @param value 值
* @return true 存在 false不存在
*/
public boolean sHasKey(String key, Object value) {
try {
return redisTemplate.opsForSet().isMember(key, value);
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
}最后寫個(gè)測(cè)試接口
TestController.java
import com.elegant.testdemo.annotation.RequestLimit;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
/**
* @Author JCccc
* @Description
* @Date 2021/7/23 11:55
*/
@RestController
public class TestController {
@GetMapping("/test")
@RequestLimit(maxCount = 3,second = 60)
public String test() {
return "你好,如果對(duì)你有幫助,請(qǐng)點(diǎn)贊加關(guān)注。";
}
}這個(gè)/test接口的注解,我們?cè)O(shè)置的是 60秒內(nèi) 最大訪問(wèn)次數(shù)為 3次 (實(shí)際應(yīng)用應(yīng)該是根據(jù)具體接口做相關(guān)的次數(shù)限制。)
然后使用postman測(cè)試一下接口:
前面三次都是請(qǐng)求通過(guò)的:


第四次:

ok,就先到這。
到此這篇關(guān)于Springboot使用redis實(shí)現(xiàn)接口Api限流的示例代碼的文章就介紹到這了,更多相關(guān)Springboot redis接口限流內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
- springboot+redis 實(shí)現(xiàn)分布式限流令牌桶的示例代碼
- 使用SpringBoot?+?Redis?實(shí)現(xiàn)接口限流的方式
- SpringBoot整合Redis并且用Redis實(shí)現(xiàn)限流的方法 附Redis解壓包
- SpringBoot中使用Redis對(duì)接口進(jìn)行限流的實(shí)現(xiàn)
- 基于SpringBoot+Redis實(shí)現(xiàn)一個(gè)簡(jiǎn)單的限流器
- SpringBoot使用Redis對(duì)用戶IP進(jìn)行接口限流的示例詳解
- Springboot使用redis實(shí)現(xiàn)接口Api限流的實(shí)例
- SpringBoot使用Redis對(duì)用戶IP進(jìn)行接口限流的項(xiàng)目實(shí)踐
- Springboot+Redis實(shí)現(xiàn)API接口限流的示例代碼
- SpringBoot使用Redis進(jìn)行限流功能實(shí)現(xiàn)
相關(guān)文章
springboot filter實(shí)現(xiàn)請(qǐng)求響應(yīng)全鏈路攔截
這篇文章主要為大家詳細(xì)介紹了SpringBoot如何結(jié)合Filter同時(shí)攔截請(qǐng)求和響應(yīng),從而實(shí)現(xiàn)??日志采集自動(dòng)化,感興趣的小伙伴可以跟隨小編一起學(xué)習(xí)一下2025-04-04
SpringBoot中服務(wù)消費(fèi)的實(shí)現(xiàn)
本文主要介紹了SpringBoot中服務(wù)消費(fèi)的實(shí)現(xiàn),文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2023-07-07
java中多態(tài)概念、實(shí)現(xiàn)原理詳解
JAVA中多態(tài)性是對(duì)象多種表現(xiàn)形式的體現(xiàn)。在面向?qū)ο笾?最常見的多態(tài)發(fā)生在使用父類的引用來(lái)引用子類的對(duì)象。下面這篇文章主要給大家介紹一下,需要的朋友可以參考下2017-04-04
基于bufferedreader的read()與readline()讀取出錯(cuò)原因及解決
這篇文章主要介紹了bufferedreader的read()與readline()讀取出錯(cuò)原因及解決,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2021-12-12
基于java實(shí)現(xiàn)斗地主代碼實(shí)例解析
這篇文章主要介紹了基于java實(shí)現(xiàn)斗地主代碼實(shí)例解析,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2020-07-07
SpringBoot+SpringSecurity處理Ajax登錄請(qǐng)求問(wèn)題(推薦)
這篇文章主要介紹了SpringBoot+SpringSecurity處理Ajax登錄請(qǐng)求問(wèn)題,本文給大家介紹的非常不錯(cuò),具有參考借鑒價(jià)值,需要的朋友可以參考下2017-12-12
SpringBoot 自動(dòng)掃描第三方包及spring.factories失效的問(wèn)題解決
這篇文章主要介紹了SpringBoot 自動(dòng)掃描第三方包及spring.factories失效的問(wèn)題,本文給大家分享最新解決方法,需要的朋友可以參考下2023-05-05
java搜索無(wú)向圖中兩點(diǎn)之間所有路徑的算法
這篇文章主要介紹了java搜索無(wú)向圖中兩點(diǎn)之間所有路徑的算法2019-01-01

