环境:Spring Boot2.7.12 + Spring Cloud2021.0.7
通过某种机制实现对系统中的现接某些接口在规定的时间段内只能让某个具体的客户端访问指定次数,超出次数,口防就不让访问了。通过等待指定的全局器实时间到期后又能继续访问接口;这里需要注意的是是控制到每一个具体的接口上,所以必须确定两个要素:
可以通过2种方式实现:
本篇文章我们通过网关实现,口防那接下来就是通过考虑上该如何去记录当前客户端访问的具体接口在指定的时间内已经访问了多少次了?通过两种方式:
通过 Redis 记录访问请求的次数,每次访问都进行递减,如果次数小于0就返回错误信息,当到了指定的时效则Redis会对过期的key进行自动删除。
Redis配置
spring: redis: host: localhost port: 6379 password: 123123 database: 8 lettuce: pool: maxActive: 8 maxIdle: 100 minIdle: 10 maxWait: -1
定义全局过滤器
@Componentpublic class BrushProofFilter implements GlobalFilter, Ordered { private final ReactiveStringRedisTemplate reactiveStringRedisTemplate ; public BrushProofFilter(ReactiveStringRedisTemplate reactiveStringRedisTemplate) { this.reactiveStringRedisTemplate = reactiveStringRedisTemplate ; } @Override public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) { // 获取客户端的请求ip InetAddress address = exchange.getRequest().getRemoteAddress().getAddress(); // 获取请求的URI String path = exchange.getRequest().getPath().toString() ; // 将其组合为Redis中的Key String 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)) ; } @Override public int getOrder() { return -2 ; }}
自定义异常
public class BrushProofException extends RuntimeException { private static final long serialVersionUID = 1L; public BrushProofException(String message) { super(message) ; } }
自定义异常处理句柄
@Componentpublic class RatelimiterWebExceptionHandler implements WebExceptionHandler { @Override 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
图片
(责任编辑:焦点)
四川阿坝州提高孤儿基本生活最低养育标准 2022年1月起执行
江丰电子(300666.SZ)实现精密零部件在PVD等半导体核心工艺环节的应用
银保监会:前10个月房地产合理贷款需求得到满足 信贷结构持续优化
新筑股份(002480.SZ):拟开展融资性售后回租业务 租赁期限3年
获赔8000万,华为胜诉三星的专利侵权诉讼案究竟是怎么回事?