![cover](https://img-blog.csdnimg.cn/b8db4aed5de04ec2beffcb36224f5fa2.png)
SpringBoot基于Redis和注解实现接口访问频率限制
使用场景1、开放接口每个IP访问次数限制(防止黑客恶意攻击)2、账号登录,规定时间内允许登录次数限制(防止账号恶意被盗)3、银行取钱,提现密码24小时之内只能输入5次限制(防止密码暴力破解)4、博客或自媒体评论,每个用户或IP在3分钟内只能发表1次评论(防止恶意言论刷屏)以上场景是企业级项目开发中比较常见的,回到今天的主题,我以第一种场景为例,实现开放接口限制每个IP的访问频率.
·
使用场景
1、开放接口每个IP访问次数限制(防止黑客恶意攻击)
2、账号登录,规定时间内允许登录次数限制(防止账号恶意被盗)
3、银行取钱,提现密码24小时之内只能输入5次限制(防止密码暴力破解)
4、博客或自媒体评论,每个用户或IP在3分钟内只能发表1次评论(防止恶意言论刷屏)
......
以上场景是企业级项目开发中比较常见的,回到今天的主题,我以第一种场景为例,实现开放接口限制每个IP的访问频率,废话不多说,直接上代码。
搭建 SpringBoot 项目
- Maven 引入 Redis 依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
- application.yml 配置 Redis 连接信息
spring:
redis:
host: 1xx.2x.1xx.xx3 # Redis服务器地址
database: 0 # Redis数据库索引(默认为0)
port: 6379 # Redis服务器连接端口
password: 1xxxxxxxxxx5 # Redis服务器连接密码(默认为空)
timeout: 3000 # 连接超时时间(毫秒)
lettuce:
pool:
max-active: 1000 # 连接池最大连接数(使用负值表示没有限制)
max-wait: -1ms # 连接池最大阻塞等待时间(使用负值表示没有限制)
max-idle: 10 # 连接池中的最大空闲连接
min-idle: 5 # 连接池中的最小空闲连接
新建 AccessLimit 注解类
/**
* Created by LPB on 2022-05-01.
*/
@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
public @interface AccessLimit {
/**
* 指定时间 单位:秒
*
* @return
*/
int seconds() default 10;
/**
* 指定时间内API请求次数
*
* @return
*/
int maxCount() default 5;
}
新建 RequestInterceptor 自定义拦截器
/**
* Created by LPB on 2022-05-01.
*/
@Slf4j
@Component
public class RequestInterceptor implements HandlerInterceptor {
@Autowired
StringRedisTemplate redisTemplate;
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
try {
if (handler instanceof HandlerMethod) {
HandlerMethod handlerMethod = (HandlerMethod) handler;
AccessLimit accessLimit = handlerMethod.getMethodAnnotation(AccessLimit.class);
if (null == accessLimit) {
return true;
}
int seconds = accessLimit.seconds();
int maxCount = accessLimit.maxCount();
String ip = IPUtils.getClientIpAddress(request);
String servletPath = request.getServletPath();
String key = ip + ":" + request.getContextPath() + ":" + servletPath;
// 已经访问的次数
String count = redisTemplate.opsForValue().get(key);
if (null != count) {
log.info("(ip限流请求次数) ip:{} 接口名:{} 访问次数:{}", ip, servletPath, count);
}
if (null == count || -1 == Integer.parseInt(count)) {
redisTemplate.opsForValue().set(key, "1", seconds, TimeUnit.SECONDS);
return true;
}
if (Integer.parseInt(count) < maxCount) {
redisTemplate.opsForValue().increment(key, 1);
return true;
}
_response(response);
return false;
}
} catch (Exception e) {
log.error("(ip限流请求次数) 请求异常 ex:{}", e.getMessage());
}
return true;
}
/**
* 拦截器异常响应
*
* @param response
* @throws IOException
*/
public void _response(HttpServletResponse response) throws IOException {
response.setCharacterEncoding("UTF-8");
response.setContentType("application/json; charset=utf-8");
ObjectMapper objectMapper = new ObjectMapper();
// R.fail 是接口响应统一封装返回
response.getWriter().println(objectMapper.writeValueAsString(R.fail("请求过于频繁请稍后再试")));
return;
}
}
注册 RequestInterceptor 自定义拦截器
/**
* Created by LPB on 2022-05-01.
*/
@Configuration
public class WebMvcConfig extends WebMvcConfigurationSupport {
@Autowired
RequestInterceptor requestInterceptor;
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(requestInterceptor).addPathPatterns("/**");
}
}
新建 TestController 测试接口
/**
* Created by LPB on 2022-05-01.
*/
@RestController
public class TestController {
/**
* 接口请求频率限制
* 设置10秒内最多请求3次
*
* @return
*/
@AccessLimit(seconds = 10, maxCount = 3)
@GetMapping("/test")
public R test() {
return R.success("接口请求成功");
}
}
PostMan 接口测试
Redis 记录某个IP请求某接口次数
注:本地 localhost 对应的 IP 是 Redis 中这个IP
更多推荐
所有评论(0)