思路:
- 自定义注解定义限制周期和限制次数
- 拦截器拦截所有请求,如果发现使用了限流注解的API,查看redis里是否记录了该接口的请求次数,如果有,就判断没有超过自定义注解的最大阈值,达到阈值就拦截该请求,否则请求次数加1并放行
代码实现:
这段代码是一个拦截器(Interceptor)的实现,它用于限制用户访问某些接口的频率。具体来说,它会根据用户的 IP 地址、请求方法和请求 URI 等信息生成一个 Redis 的 Key,然后使用 Redis 的 incr() 方法对该 Key 的值进行自增操作,以统计用户访问该接口的次数。如果用户访问该接口的次数超过了设定的最大值,就会返回一个错误信息,并拒绝该用户的访问。
其中,AccessLimit 是一个自定义的注解,用于在方法上标注该方法的访问控制信息,包括访问时间间隔(seconds)、最大访问次数(maxCount)和错误提示信息(msg)。在 preHandle() 方法中,会通过 handler 参数获取当前请求对应的 HandlerMethod 实例,然后从该实例中获取方法上的 AccessLimit 注解,以获取访问控制信息。如果方法上没有 AccessLimit 注解,就表示该方法不需要进行访问控制,直接通过拦截器。
在具体实现中,使用了 Objects.nonNull() 方法来判断 count 变量是否为空,避免了在 count 为 null 时出现空指针异常。同时,在使用 Redis 的 incr() 方法时,也需要注意可能会发生 Redis 连接失败的情况,因此需要对该异常进行捕获和处理。
/**
* Redis拦截器
* API请求限流
* @author DarkClouds
* @date 2023/05/10
*/
@Slf4j
@Component
public class AccessLimitInterceptor implements HandlerInterceptor {
@Autowired
private RedisService redisService;
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) {
boolean result = true;
// Handler 是否为 HandlerMethod 实例
if (handler instanceof HandlerMethod) {
HandlerMethod handlerMethod = (HandlerMethod) handler;
AccessLimit accessLimit = handlerMethod.getMethodAnnotation(AccessLimit.class);
//方法上没有访问控制的注解,直接通过
if (accessLimit != null) {
int seconds = accessLimit.seconds();
int maxCount = accessLimit.maxCount();
String ip = IpUtils.getIpAddress(request);
String method = request.getMethod();
String requestUri = request.getRequestURI();
String redisKey = ip + ":" + method + ":" + requestUri;
try {
Long count = redisService.incr(redisKey, 1L);
// 第一次访问
if (Objects.nonNull(count) && count == 1) {
redisService.setExpire(redisKey, seconds, TimeUnit.SECONDS);
} else if (count > maxCount) {
//触发限制时的消息提示返回给前端
WebUtils.renderString(response, JSON.toJSONString(Result.fail(accessLimit.msg())));
log.warn(redisKey + "请求次数超过每" + seconds + "秒" + maxCount + "次");
result = false;
}
} catch (RedisConnectionFailureException e) {
log.error("redis错误: " + e.getMessage());
result = false;
}
}
}
return result;
}
}
/**
* Redis限流注解
*
* @author DarkClouds
* @date 2023/05/10
*/
@Documented
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface AccessLimit {
/**
* 限制周期(秒)
*/
int seconds();
/**
* 规定周期内限制次数
*/
int maxCount();
/**
* 触发限制时的消息提示
*/
String msg() default "操作过于频繁请稍后再试";
}
使用演示
@AccessLimit(seconds = 60, maxCount = 3)
public void test() {
//当60秒内连续请求该方法超过3次,将被限流,无法访问
}