springboot接口防刷
服务器通过redis 记录下请求的次数,如果次数超过限制就限制访问。 在redis 保存的key 是有时效性的,过期就会删除。
@RequestLimit
注解
import java.lang.annotation.*; /** * 请求限制的自定义注解 * * @Target 注解可修饰的对象范围,ElementType.METHOD 作用于方法,ElementType.TYPE 作用于类 * (ElementType)取值有: * 1.CONSTRUCTOR:用于描述构造器 * 2.FIELD:用于描述域 * 3.LOCAL_VARIABLE:用于描述局部变量 * 4.METHOD:用于描述方法 * 5.PACKAGE:用于描述包 * 6.PARAMETER:用于描述参数 * 7.TYPE:用于描述类、接口(包括注解类型) 或enum声明 * @Retention定义了该Annotation被保留的时间长短:某些Annotation仅出现在源代码中,而被编译器丢弃; * 而另一些却被编译在class文件中;编译在class文件中的Annotation可能会被虚拟机忽略, * 而另一些在class被装载时将被读取(请注意并不影响class的执行,因为Annotation与class在使用上是被分离的)。 * 使用这个meta-Annotation可以对 Annotation的“生命周期”限制。 * (RetentionPoicy)取值有: * 1.SOURCE:在源文件中有效(即源文件保留) * 2.CLASS:在class文件中有效(即class保留) * 3.RUNTIME:在运行时有效(即运行时保留) * * @Inherited * 元注解是一个标记注解,@Inherited阐述了某个被标注的类型是被继承的。 * 如果一个使用了@Inherited修饰的annotation类型被用于一个class,则这个annotation将被用于该class的子类。 */ @Documented @Inherited @Target({ElementType.METHOD,ElementType.TYPE}) @Retention(RetentionPolicy.RUNTIME) public @interface RequestLimit { // 在 second 秒内,最大只能请求 maxCount 次 int second() default 1; int maxCount() default 1; }
RequestLimitIntercept
拦截器
HandlerInterceptor是一个拦截器,其中有可以自定义很多内容,可以学习一下,包括在方法调用前进入拦截器做相应的操作
自定义一个拦截器,请求之前,进行请求次数校验
package com.springcache.Filter; import com.alibaba.fastjson.JSONObject; import com.springcache.Entity.MyApi.RequestLimit; import com.springcache.utils.Result; import lombok.extern.slf4j.Slf4j; 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.lang.reflect.Method; import java.util.concurrent.TimeUnit; @Slf4j @Component public class RequestLimitIntercept implements HandlerInterceptor { @Autowired private RedisTemplate<String,Object> redisTemplate; @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { /** * isAssignableFrom() 判定此 Class 对象所表示的类或接口与指定的 Class 参数所表示的类或接口是否相同,或是否是其超类或超接口 * isAssignableFrom()方法是判断是否为某个类的父类 * instanceof关键字是判断是否某个类的子类 */ if(handler.getClass().isAssignableFrom(HandlerMethod.class)){ //HandlerMethod 封装方法定义相关的信息,如类,方法,参数等 HandlerMethod handlerMethod = (HandlerMethod) handler; Method method = handlerMethod.getMethod(); // 获取方法中是否包含注解 RequestLimit methodAnnotation = method.getAnnotation(RequestLimit.class); //获取 类中是否包含注解,也就是controller 是否有注解 RequestLimit classAnnotation = method.getDeclaringClass().getAnnotation(RequestLimit.class); // 如果 方法上有注解就优先选择方法上的参数,否则类上的参数 RequestLimit requestLimit = methodAnnotation != null?methodAnnotation:classAnnotation; if(requestLimit != null){ if(isLimit(request,requestLimit)){ responseOut(response, Result.error(403,"请求过快")); return false; } } } return HandlerInterceptor.super.preHandle(request, response, handler); } //判断请求是否受限 public boolean isLimit(HttpServletRequest request,RequestLimit requestLimit){ // 受限的redis 缓存key ,因为这里用浏览器做测试,我就用sessionid 来做唯一key,如果是app ,可以使用 用户ID 之类的唯一标识。 String limitKey = request.getServletPath()+request.getSession().getId(); // 从缓存中获取,当前这个请求访问了几次 Integer redisCount = (Integer) redisTemplate.opsForValue().get(limitKey); if(redisCount == null){ //初始 次数 redisTemplate.opsForValue().set(limitKey,1,requestLimit.second(), TimeUnit.SECONDS); System.out.println("写入redis --"); }else{ System.out.println("intValue-->"+redisCount.intValue()); if(redisCount.intValue() >= requestLimit.maxCount()){ return true; } // 次数自增 redisTemplate.opsForValue().increment(limitKey); } return false; } /** * 回写给客户端 * @param response * @param result * @throws IOException */ private void responseOut(HttpServletResponse response, Result result) throws IOException { response.setCharacterEncoding("UTF-8"); response.setContentType("application/json; charset=utf-8"); PrintWriter out = null ; String json = JSONObject.toJSON(result).toString(); out = response.getWriter(); out.append(json); } }
拦截器写好了,但是还得添加注册
WebMvcConfig 配置类
@Slf4j @Component public class WebMvcConfig implements WebMvcConfigurer { @Autowired private RequestLimitIntercept requestLimitIntercept; @Override public void addInterceptors(InterceptorRegistry registry) { log.info("添加拦截"); registry.addInterceptor(requestLimitIntercept); } //这一段代码,如果集成了swagger必须要有,否则swagger无法打开,会被拦截 //这段代码内实际是配置路由的 @Override public void addResourceHandlers(ResourceHandlerRegistry registry) { registry.addResourceHandler("/**").addResourceLocations( "classpath:/static/"); registry.addResourceHandler("swagger-ui.html").addResourceLocations( "classpath:/META-INF/resources/"); registry.addResourceHandler("/webjars/**").addResourceLocations( "classpath:/META-INF/resources/webjars/"); // addResourceHandlers(registry); } }
做完后在启动类上添加 @EnableWebMvc注解,否则启动会失败
Controller
控制层测试接口,
使用方式:
第一种:直接在类上使用注解@RequestLimit(maxCount = 5,second = 1)
第二种:在方法上使用注解@RequestLimit(maxCount = 5,second = 1)
maxCount 最大的请求数、second 代表时间,单位是秒
默认1秒内,每个接口只能请求一次
@Controller @RequestMapping("/user") @Api(tags = "测试:接口防刷") @ResponseBody public class UserController { @Autowired private UserService userService; @ApiOperation(value = "查询用户",notes = "接口防刷测试") @GetMapping("/getNo") @RequestLimit(maxCount = 5,second = 2) public User queryNo(Integer userId) { return userService.queryNo(userId); } @GetMapping("/get") @RequestLimit(second = 10,maxCount = 2) public String query() { return "aaaaa"; } }
吾乃代码搬运工,侵联删
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 全程不用写代码,我用AI程序员写了一个飞机大战
· MongoDB 8.0这个新功能碉堡了,比商业数据库还牛
· 记一次.NET内存居高不下排查解决与启示
· 白话解读 Dapr 1.15:你的「微服务管家」又秀新绝活了
· DeepSeek 开源周回顾「GitHub 热点速览」