gateway添加限流类

This commit is contained in:
panxuejie 2026-01-08 10:33:23 +08:00
parent 27cbcd9f33
commit 2506d9265f
4 changed files with 141 additions and 0 deletions

View File

@ -5,11 +5,13 @@ import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient; import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
import org.springframework.cloud.openfeign.EnableFeignClients; import org.springframework.cloud.openfeign.EnableFeignClients;
import org.springframework.context.annotation.ComponentScan;
@SpringBootApplication @SpringBootApplication
@MapperScan("com.tacit.admin.mapper") @MapperScan("com.tacit.admin.mapper")
@EnableDiscoveryClient @EnableDiscoveryClient
@EnableFeignClients(basePackages = "com.tacit.common.feign") @EnableFeignClients(basePackages = "com.tacit.common.feign")
@ComponentScan(basePackages = {"com.tacit.admin", "com.tacit.common"})
public class AdminApplication { public class AdminApplication {
public static void main(String[] args) { public static void main(String[] args) {
SpringApplication.run(AdminApplication.class, args); SpringApplication.run(AdminApplication.class, args);

View File

@ -4,10 +4,12 @@ import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.SpringApplication; import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.openfeign.EnableFeignClients; import org.springframework.cloud.openfeign.EnableFeignClients;
import org.springframework.context.annotation.ComponentScan;
@SpringBootApplication @SpringBootApplication
@MapperScan("com.tacit.app.mapper") @MapperScan("com.tacit.app.mapper")
@EnableFeignClients(basePackages = "com.tacit.common.feign") @EnableFeignClients(basePackages = "com.tacit.common.feign")
@ComponentScan(basePackages = {"com.tacit.app", "com.tacit.common"})
public class AppApiApplication { public class AppApiApplication {
public static void main(String[] args) { public static void main(String[] args) {
SpringApplication.run(AppApiApplication.class, args); SpringApplication.run(AppApiApplication.class, args);

View File

@ -0,0 +1,68 @@
package com.tacit.gateway.config;
import org.springframework.cloud.gateway.filter.ratelimit.KeyResolver;
import org.springframework.cloud.gateway.support.ServerWebExchangeUtils;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;
/**
* 限流配置类
*/
@Configuration
public class RateLimiterConfig {
/**
* IP限流 - 根据请求IP地址进行限流
* 添加@Primary注解作为默认的KeyResolver
*/
@Bean
@Primary
public KeyResolver ipKeyResolver() {
return exchange -> {
// 处理可能的空指针异常
var remoteAddress = exchange.getRequest().getRemoteAddress();
if (remoteAddress != null && remoteAddress.getAddress() != null) {
return Mono.just(remoteAddress.getAddress().getHostAddress());
}
return Mono.just("unknown");
};
}
/**
* 接口限流 - 根据请求路径进行限流
*/
@Bean
public KeyResolver apiKeyResolver() {
return exchange -> Mono.just(exchange.getRequest().getPath().value());
}
/**
* 用户限流 - 根据请求头中的X-User-Id进行限流
*/
@Bean
public KeyResolver userKeyResolver() {
return exchange -> {
String userId = exchange.getRequest().getHeaders().getFirst("X-User-Id");
return Mono.just(userId != null ? userId : "anonymous");
};
}
/**
* 服务限流 - 根据路由ID进行限流
*/
@Bean
public KeyResolver serviceKeyResolver() {
return exchange -> {
// 从路由中获取服务名称使用正确的常量避免空指针
Object routeObj = exchange.getAttribute(ServerWebExchangeUtils.GATEWAY_ROUTE_ATTR);
if (routeObj != null) {
// 使用toString()方法获取路由信息避免类型转换错误
return Mono.just(routeObj.toString());
}
return Mono.just("unknown");
};
}
}

View File

@ -0,0 +1,69 @@
package com.tacit.gateway.filter;
import lombok.extern.slf4j.Slf4j;
import org.springframework.cloud.gateway.filter.GatewayFilter;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.OrderedGatewayFilter;
import org.springframework.cloud.gateway.filter.factory.AbstractGatewayFilterFactory;
import org.springframework.core.Ordered;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.http.server.reactive.ServerHttpResponse;
import org.springframework.stereotype.Component;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;
/**
* 自定义限流异常处理过滤器
*/
@Component
@Slf4j
public class CustomRateLimitExceptionHandler extends AbstractGatewayFilterFactory<CustomRateLimitExceptionHandler.Config> {
public CustomRateLimitExceptionHandler() {
super(Config.class);
}
@Override
public GatewayFilter apply(Config config) {
// 创建全局异常处理过滤器优先级最高
GatewayFilter filter = new GatewayFilter() {
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
return chain.filter(exchange)
.onErrorResume(throwable -> {
// 检查是否是限流异常
if (throwable.getMessage() != null && throwable.getMessage().contains("Rate limit exceeded")) {
log.warn("限流异常: {}, 请求路径: {}", throwable.getMessage(), exchange.getRequest().getPath());
return handleRateLimitException(exchange);
}
// 其他异常继续抛出
return Mono.error(throwable);
});
}
};
// 设置过滤器优先级确保在其他过滤器之前执行
return new OrderedGatewayFilter(filter, Ordered.HIGHEST_PRECEDENCE);
}
/**
* 处理限流异常返回自定义响应
*/
private Mono<Void> handleRateLimitException(ServerWebExchange exchange) {
ServerHttpResponse response = exchange.getResponse();
response.setStatusCode(HttpStatus.TOO_MANY_REQUESTS);
response.getHeaders().add(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE);
// 自定义限流响应内容
String responseBody = "{\"code\": 429, \"message\": \"请求过于频繁,请稍后再试\", \"data\": null}";
return response.writeWith(Mono.just(response.bufferFactory().wrap(responseBody.getBytes())));
}
public static class Config {
// 可以添加配置属性
}
}