SpringBoot-(二十五)SpringBoot自定义注解实现接口防刷

本文最后更新于:September 11, 2022 pm

SpringBoot框架中有两个非常重要的策略:开箱即用和约定优于配置。其设计目的是用来简化新Spring应用的初始搭建以及开发过程。该框架使用了特定的方式来进行配置,从而使开发人员不再需要定义样板化的配置。

目录

自定义注解

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
package com.tothefor.demo.limitcount;

import java.lang.annotation.*;

/**
* @Author DragonOne
* @Date 2022/9/7 07:57
* @Title
* @Description
*/
@Documented
@Inherited
@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface RequestLimit {
// 在 second 秒内,最大只能请求 maxCount 次
int second() default 7;

int maxCount() default 5;
}

编写过滤器

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
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
package com.tothefor.demo.limitcount;

import com.alibaba.fastjson.JSONObject;
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.io.PrintWriter;
import java.util.concurrent.TimeUnit;

/**
* @Author DragonOne
* @Date 2022/9/7 08:23
* @Title
* @Description
*/
@Component
public class LimitInterceptor implements HandlerInterceptor {

private static final Logger logger = LoggerFactory.getLogger(LimitInterceptor.class);

@Autowired
private RedisTemplate<String, Object> redisTemplate;

@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
//判断请求是否属于方法的请求
RequestLimit requestLimit = null;
if (handler instanceof HandlerMethod) {

HandlerMethod hm = (HandlerMethod) handler;

//获取方法中的注解,看是否有该注解
requestLimit = hm.getMethodAnnotation(RequestLimit.class);
// 该请求不是方法请求
if (requestLimit == null) {
return true;
}
// 注解中的时间,也可以根据数据库中动态获取
int seconds = requestLimit.second();
// 注解中的最多请求次数,也可以根据数据库中动态获取
int maxCount = requestLimit.maxCount();
// 获取请求url
String key = request.getRequestURI();
//拼接。项目中是动态获取的userId,从token中获取
key += "" + "userID";


//从redis中获取用户访问的次数
Integer userCount = (Integer) redisTemplate.opsForValue().get(key);

if (userCount == null) {
//第一次访问
logger.info("首次进入");
redisTemplate.opsForValue().set(key, 1L, seconds, TimeUnit.SECONDS);
} else if (userCount < maxCount) {
//加1
logger.info("有效访问");
redisTemplate.opsForValue().increment(key);
} else {
//超出访问次数
logger.info("无效访问");
// 这里测试只是传入了一个string,可以根据需求传入一个统一返回结果类
render(response, "访问次数过多");
return false;
}
}

return true;

}

/**
* @Author DragonOne
* @Date 2022/9/7 09:09
* @Title 封装返回信息,可传入自定义统一返回类型进行封装
* @Description
*/
private void render(HttpServletResponse response, String str) throws IOException {
response.setCharacterEncoding("UTF-8");
response.setContentType("application/json; charset=utf-8");
String json = JSONObject.toJSON(str).toString();
PrintWriter out = response.getWriter();
out.append(json);
}
}

添加过滤器

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
package com.tothefor.demo.limitcount;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

/**
* @Author DragonOne
* @Date 2022/9/7 08:56
* @Title
* @Description
*/
@Component
public class WebMvcConfig implements WebMvcConfigurer {

@Autowired
private LimitInterceptor limitInterceptor;

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

测试

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
package com.tothefor.demo.limitcount;

import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

/**
* @Author DragonOne
* @Date 2022/9/7 08:53
* @Title
* @Description
*/
@RestController
public class LimitController {

@RequestLimit
@RequestMapping("/checkLimit")
public String gets(){
return "ok";
}

}

总结

简单说明步骤:编写过滤器,过滤请求。

  • 扩展:可将过滤器中的是否为注解的判断去掉,从而达到对所有接口请求的限流控制。而判断的次数逻辑根据数据库中的数据进行判断。
  • 另外至于为什么要封装一个返回信息的方法(针对做了全局Controller结果的统一处理来说):因为这是在过滤器中,而此时还并没有请求Controller接口,所以这里需要单独进行处理。
  • 另外,可以借鉴方法,自定义注解实现接口的其他限制访问,如:等级限制、VIP限制等。