diff --git a/tacit-admin/src/main/java/com/tacit/admin/AdminApplication.java b/tacit-admin/src/main/java/com/tacit/admin/AdminApplication.java index 3cb289e..5af7df1 100644 --- a/tacit-admin/src/main/java/com/tacit/admin/AdminApplication.java +++ b/tacit-admin/src/main/java/com/tacit/admin/AdminApplication.java @@ -5,11 +5,13 @@ import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.cloud.client.discovery.EnableDiscoveryClient; import org.springframework.cloud.openfeign.EnableFeignClients; +import org.springframework.context.annotation.ComponentScan; @SpringBootApplication @MapperScan("com.tacit.admin.mapper") @EnableDiscoveryClient @EnableFeignClients(basePackages = "com.tacit.common.feign") +@ComponentScan(basePackages = {"com.tacit.admin", "com.tacit.common"}) public class AdminApplication { public static void main(String[] args) { SpringApplication.run(AdminApplication.class, args); diff --git a/tacit-app-api/src/main/java/com/tacit/app/AppApiApplication.java b/tacit-app-api/src/main/java/com/tacit/app/AppApiApplication.java index 65244a9..41c5bdd 100644 --- a/tacit-app-api/src/main/java/com/tacit/app/AppApiApplication.java +++ b/tacit-app-api/src/main/java/com/tacit/app/AppApiApplication.java @@ -4,10 +4,12 @@ import org.mybatis.spring.annotation.MapperScan; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.cloud.openfeign.EnableFeignClients; +import org.springframework.context.annotation.ComponentScan; @SpringBootApplication @MapperScan("com.tacit.app.mapper") @EnableFeignClients(basePackages = "com.tacit.common.feign") +@ComponentScan(basePackages = {"com.tacit.app", "com.tacit.common"}) public class AppApiApplication { public static void main(String[] args) { SpringApplication.run(AppApiApplication.class, args); diff --git a/tacit-gateway/src/main/java/com/tacit/gateway/config/RateLimiterConfig.java b/tacit-gateway/src/main/java/com/tacit/gateway/config/RateLimiterConfig.java new file mode 100644 index 0000000..c63d202 --- /dev/null +++ b/tacit-gateway/src/main/java/com/tacit/gateway/config/RateLimiterConfig.java @@ -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"); + }; + } +} \ No newline at end of file diff --git a/tacit-gateway/src/main/java/com/tacit/gateway/filter/CustomRateLimitExceptionHandler.java b/tacit-gateway/src/main/java/com/tacit/gateway/filter/CustomRateLimitExceptionHandler.java new file mode 100644 index 0000000..decbaa4 --- /dev/null +++ b/tacit-gateway/src/main/java/com/tacit/gateway/filter/CustomRateLimitExceptionHandler.java @@ -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 { + + public CustomRateLimitExceptionHandler() { + super(Config.class); + } + + @Override + public GatewayFilter apply(Config config) { + // 创建全局异常处理过滤器,优先级最高 + GatewayFilter filter = new GatewayFilter() { + @Override + public Mono 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 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 { + // 可以添加配置属性 + } +} \ No newline at end of file