Redis实现限流

1、引入依赖

1
2
3
4
5
6
7
8
9
10
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>

<!-- redis依赖commons-pool 这个依赖一定要添加 -->
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-pool2</artifactId>
</dependency>

2、application.yml配置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
server:
port: 8181

spring:
redis:
host: 127.0.0.1
port: 6379
timeout: 10s
lettuce:
pool:
# 连接池中的最小空闲连接 默认0
min-idle: 0
# 连接池中的最大空闲连接 默认8
max-idle: 8
# 连接池最大连接数 默认8 ,负数表示没有限制
max-active: 8
# 连接池最大阻塞等待时间(使用负值表示没有限制) 默认-1
max-wait: -1ms
#选择哪个库存储,默认是0
database: 0
password: 123456

3、创建redisConfig,引入redisTemplate

1
2
3
4
5
6
7
8
9
10
11
12
13
@Configuration
public class RedisConfig {
@Bean
public RedisTemplate<String, Object> redisTemplate(LettuceConnectionFactory redisConnectionFactory) {
RedisTemplate<String, Object> redisTemplate = new RedisTemplate<String, Object>();
redisTemplate.setKeySerializer(new StringRedisSerializer());
redisTemplate.setValueSerializer(new GenericJackson2JsonRedisSerializer());
redisTemplate.setHashKeySerializer(new StringRedisSerializer());
redisTemplate.setHashValueSerializer(new GenericJackson2JsonRedisSerializer());
redisTemplate.setConnectionFactory(redisConnectionFactory);
return redisTemplate;
}
}

4、写自定义注解和拦截器

1、自定义注解

1
2
3
4
5
6
7
8
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
@Documented
public @interface AccessLimit {
int seconds(); //秒数
int maxCount(); //最大访问次数
boolean needLogin()default true;//是否需要登录
}

2、创建拦截器

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
@Component
public class FangshuaInterceptor extends HandlerInterceptorAdapter {

@Autowired
private RedisTemplate redisTemplate;

@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
//判断请求是否属于方法的请求
if(handler instanceof HandlerMethod){
HandlerMethod hm = (HandlerMethod) handler;
//获取方法中的注解,看是否有该注解
AccessLimit accessLimit = hm.getMethodAnnotation(AccessLimit.class);
if(accessLimit == null){
return true;
}
int seconds = accessLimit.seconds();
int maxCount = accessLimit.maxCount();
boolean login = accessLimit.needLogin();
String key = request.getRequestURI();
//如果需要登录
if(login){
//获取登录的session进行判断,此处只是例子,不写具体的业务
//.....
key+=""+"1"; //这里假设用户是1,项目中是动态获取的userId
}

//从redis中获取用户访问的次数
Integer count;
if(Objects.isNull(redisTemplate.opsForValue().get(key))){
count = 0;
}else{
count = (Integer) redisTemplate.opsForValue().get(key);
}
if(count == 0){
redisTemplate.opsForValue().set(key,1,seconds, TimeUnit.SECONDS);
}else if(count<maxCount){
//key的值加1
redisTemplate.opsForValue().increment(key);
}else{
//超出访问次数
Map<String,Object> errMap=new HashMap<>();
errMap.put("code",400);
errMap.put("msg","请求超时,请稍后再试");
render(response,errMap); //这里的CodeMsg是一个返回参数
return false;
}
}
return true;
}


private void render(HttpServletResponse response, Map<String,Object> errMap) throws Exception {
response.setContentType("application/json;charset=UTF-8");
OutputStream out = response.getOutputStream();
String str = JSON.toJSONString(errMap);
out.write(str.getBytes("UTF-8"));
out.flush();
out.close();
}
}

3、将自定义拦截器加入到拦截器列表中

1
2
3
4
5
6
7
8
9
10
11
@Configuration
public class WebConfig extends WebMvcConfigurerAdapter {

@Autowired
private FangshuaInterceptor interceptor;

@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(interceptor);
}
}

4、测试

1
2
3
4
5
6
7
8
9
10
11
@RestController
@RequestMapping("test")
public class TestController {

//每三十秒最多可以请求三次,不需要登录
@AccessLimit(seconds=30, maxCount=3, needLogin=false)
@PostMapping("/fangshua")
public String fangshua(){
return "成功";
}
}