环境:Spring Boot2.7.12 + Spring Cloud2021.0.7
1 概念
通过某种机制实现对系统中的某些接口在规定的时间段内只能让某个具体的客户端访问指定次数,超出次数,就不让访问了。等待指定的时间到期后又能继续访问接口;这里需要注意的是是控制到每一个具体的接口上,所以必须确定两个要素:
客户端是谁
访问的接口
2 实现原理
可以通过2种方式实现:
通过网关
可以控制到所有的服务通过AOP
该方案只能针对具体的每一个服务,代码重复,如果通过
本篇文章我们通过网关实现,那接下来就是考虑上该如何去记录当前客户端访问的具体接口在指定的时间内已经访问了多少次了?通过两种方式:
JVM层
该种实现方式,你需要自己实现时效性的检查,实现麻烦通过Redis
Redis本身就可以对Key设置时效性,所以非常的方便。本文通过Redis实现。
通过 Redis 记录访问请求的次数,每次访问都进行递减,如果次数小于0就返回错误信息,当到了指定的时效则Redis会对过期的key进行自动删除。
3 代码实现
Redis配置
spring:redis:host: localhostport: 6379password: 123123database: 8lettuce:pool:maxActive: 8maxIdle: 100minIdle: 10maxWait: -1
定义全局过滤器
public class BrushProofFilter implements GlobalFilter, Ordered {private final ReactiveStringRedisTemplate reactiveStringRedisTemplate ;public BrushProofFilter(ReactiveStringRedisTemplate reactiveStringRedisTemplate) {this.reactiveStringRedisTemplate = reactiveStringRedisTemplate ;}public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {// 获取客户端的请求ipInetAddress address = exchange.getRequest().getRemoteAddress().getAddress();// 获取请求的URIString path = exchange.getRequest().getPath().toString() ;// 将其组合为Redis中的KeyString key = ("ratelimiter:" + address + ":" + path) ;// 通过抛出异常的方式终止序列,然后通过自定义的WebExceptionHandler处理异常信息return this.reactiveStringRedisTemplate.opsForValue()// 这里固定设置每30s,访问10次.setIfAbsent(key, "10", Duration.ofSeconds(30)).flatMap(exist -> {return this.reactiveStringRedisTemplate.opsForValue().decrement(key) ;}).doOnNext(num -> {if (num < 0) {throw new BrushProofException("你访问太快了") ;}}).then(chain.filter(exchange)) ;}public int getOrder() {return -2 ;}}
自定义异常
public class BrushProofException extends RuntimeException {private static final long serialVersionUID = 1L;public BrushProofException(String message) {super(message) ;}}
自定义异常处理句柄
public class RatelimiterWebExceptionHandler implements WebExceptionHandler {public Mono<Void> handle(ServerWebExchange exchange, Throwable ex) {if (ex instanceof RatelimiterException re) {ServerHttpResponse response = exchange.getResponse() ;response.setStatusCode(HttpStatus.INTERNAL_SERVER_ERROR) ;response.getHeaders().add("Content-Type", "text/html;charset=utf8") ;// 直接输出了,异常不进行传递return response.writeWith(Mono.just(response.bufferFactory().wrap(("你访问太快了").getBytes()))) ;}// 如果是其它异常,则将异常继续向下传递return Mono.error(ex) ;}}
访问测试
因为我这里没有这个接口,所以返回的是降级接口,也算是正常
当超过10次后:
Redis

以客户端请求ip + path作为key

超过10次后,限制访问
完毕!!!
关注+转发

