Skip to content

PrefixPath GatewayFilter Factory

概述

PrefixPath GatewayFilter Factory 是 Spring Cloud Gateway 中的一个路径过滤器工厂,它的主要作用是为匹配的请求路径添加一个前缀。这在微服务架构中非常有用,特别是当我们需要将客户端请求路由到不同版本的服务或者需要在路径中添加统一的 API 版本号时。

TIP

PrefixPath 过滤器常用于 API 版本管理、服务路径标准化以及微服务的路径重写场景。

核心功能

PrefixPath GatewayFilter Factory 接受一个 prefix 参数,会将指定的前缀添加到所有匹配请求的路径前面。

工作原理

配置方式

基础配置

yaml
spring:
  cloud:
    gateway:
      routes:
        - id: prefixpath_route
          uri: https://example.org
          predicates:
            - Path=/api/**
          filters:
            - PrefixPath=/mypath
yaml
spring:
  cloud:
    gateway:
      routes:
        - id: prefixpath_route
          uri: https://example.org
          filters:
            - PrefixPath=/mypath

编程式配置

kotlin
@Configuration
class GatewayConfig {

    @Bean
    fun customRouteLocator(builder: RouteLocatorBuilder): RouteLocator {
        return builder.routes()
            .route("prefixpath_route") { r ->
                r.path("/api/**")
                    .filters { f ->
                        f.prefixPath("/v1") // 为所有 /api/** 请求添加 /v1 前缀
                    }
                    .uri("http://localhost:8081")
            }
            .build()
    }
}

实际业务场景

场景 1:API 版本管理

在微服务架构中,我们经常需要管理不同版本的 API。使用 PrefixPath 可以优雅地处理版本路由:

kotlin
@Configuration
class ApiVersionConfig {

    @Bean
    fun apiRoutes(builder: RouteLocatorBuilder): RouteLocator {
        return builder.routes()
            // V1 API路由
            .route("api_v1") { r ->
                r.path("/user/**", "/order/**")
                    .and().header("API-Version", "v1")
                    .filters { f ->
                        f.prefixPath("/v1") // 客户端请求 /user/123 → 服务端接收 /v1/user/123
                    }
                    .uri("http://legacy-service:8080")
            }
            // V2 API路由
            .route("api_v2") { r ->
                r.path("/user/**", "/order/**")
                    .and().header("API-Version", "v2")
                    .filters { f ->
                        f.prefixPath("/v2") // 客户端请求 /user/123 → 服务端接收 /v2/user/123
                    }
                    .uri("http://new-service:8080")
            }
            .build()
    }
}

在 API 版本管理中,确保下游服务能够正确处理带有版本前缀的路径。

场景 2:多租户系统路径隔离

在多租户系统中,使用 PrefixPath 可以为不同租户的请求添加租户标识:

kotlin
@Configuration
class TenantRoutingConfig {

    @Bean
    fun tenantRoutes(builder: RouteLocatorBuilder): RouteLocator {
        return builder.routes()
            .route("tenant_a") { r ->
                r.header("Tenant-ID", "tenant-a")
                    .filters { f ->
                        f.prefixPath("/tenant-a") // 为租户A的请求添加前缀
                        f.addRequestHeader("X-Tenant", "tenant-a")
                    }
                    .uri("http://multi-tenant-service:8080")
            }
            .route("tenant_b") { r ->
                r.header("Tenant-ID", "tenant-b")
                    .filters { f ->
                        f.prefixPath("/tenant-b") // 为租户B的请求添加前缀
                        f.addRequestHeader("X-Tenant", "tenant-b")
                    }
                    .uri("http://multi-tenant-service:8080")
            }
            .build()
    }
}

场景 3:微服务命名空间管理

在复杂的微服务架构中,使用 PrefixPath 可以为不同的服务模块添加命名空间:

kotlin
@Configuration
class NamespaceRoutingConfig {

    @Bean
    fun namespaceRoutes(builder: RouteLocatorBuilder): RouteLocator {
        return builder.routes()
            // 用户服务命名空间
            .route("user_service") { r ->
                r.path("/user/**")
                    .filters { f ->
                        f.prefixPath("/services/user") // /user/profile → /services/user/user/profile
                        f.stripPrefix(1) // 去掉一层 /user → /services/user/profile
                    }
                    .uri("http://user-service:8080")
            }
            // 订单服务命名空间
            .route("order_service") { r ->
                r.path("/order/**")
                    .filters { f ->
                        f.prefixPath("/services/order") // /order/123 → /services/order/order/123
                        f.stripPrefix(1) // 去掉一层 /order → /services/order/123
                    }
                    .uri("http://order-service:8080")
            }
            .build()
    }
}

路径转换示例

原始请求路径PrefixPath 配置转换后路径说明
/hello/mypath/mypath/hello基础前缀添加
/api/users/v1/v1/api/usersAPI 版本前缀
//app/app/根路径处理
/user/123/services/user/services/user/user/123服务命名空间

高级配置

结合其他过滤器使用

kotlin
@Configuration
class AdvancedPrefixConfig {

    @Bean
    fun advancedRoutes(builder: RouteLocatorBuilder): RouteLocator {
        return builder.routes()
            .route("advanced_prefix") { r ->
                r.path("/api/**")
                    .filters { f ->
                        f.prefixPath("/v1") // 添加版本前缀
                        f.addRequestHeader("X-Gateway", "true") // 添加网关标识
                        f.retry { retryConfig ->
                            retryConfig.retries(3) // 重试配置
                                .backoff(Duration.ofSeconds(1), Duration.ofSeconds(10), 2, true)
                        }
                        f.circuitBreaker { config ->
                            config.name("api-circuit-breaker") // 熔断器配置
                                .fallbackUri("forward:/fallback")
                        }
                    }
                    .uri("http://api-service:8080")
            }
            .build()
    }
}

动态前缀配置

kotlin
@Component
class DynamicPrefixService {

    /**
     * 根据请求头动态生成前缀
     */
    fun generatePrefix(exchange: ServerWebExchange): String {
        val version = exchange.request.headers.getFirst("API-Version") ?: "v1"
        val region = exchange.request.headers.getFirst("Region") ?: "default"
        return "/$version/$region"
    }
}

@Configuration
class DynamicPrefixConfig {

    @Autowired
    private lateinit var dynamicPrefixService: DynamicPrefixService

    @Bean
    fun dynamicRoutes(builder: RouteLocatorBuilder): RouteLocator {
        return builder.routes()
            .route("dynamic_prefix") { r ->
                r.path("/api/**")
                    .filters { f ->
                        // 使用自定义过滤器实现动态前缀
                        f.filter { exchange, chain ->
                            val prefix = dynamicPrefixService.generatePrefix(exchange)
                            val newPath = prefix + exchange.request.path.value()
                            val newRequest = exchange.request.mutate()
                                .path(newPath)
                                .build()
                            chain.filter(exchange.mutate().request(newRequest).build())
                        }
                    }
                    .uri("http://dynamic-service:8080")
            }
            .build()
    }
}

注意事项与最佳实践

使用 `PrefixPath` 时需要注意以下几点:

  1. 路径重复问题:避免添加的前缀与下游服务的路径产生冲突
  2. 性能考虑:路径转换会带来轻微的性能开销
  3. 调试困难:路径转换可能会增加调试的复杂性

最佳实践建议:

  1. 统一前缀规范:在团队中建立统一的前缀命名规范
  2. 文档记录:详细记录路径转换规则,便于维护
  3. 监控日志:在网关层记录路径转换日志,便于问题排查
  4. 测试覆盖:确保所有路径转换场景都有对应的测试用例

监控与调试

启用网关日志

yaml
logging:
  level:
    org.springframework.cloud.gateway: DEBUG
    org.springframework.web.reactive: DEBUG

自定义路径转换日志

kotlin
@Component
class PathTransformationLogger : GlobalFilter, Ordered {

    private val logger = LoggerFactory.getLogger(PathTransformationLogger::class.java)

    override fun filter(exchange: ServerWebExchange, chain: GatewayFilterChain): Mono<Void> {
        val originalPath = exchange.request.path.value()

        return chain.filter(exchange).doOnSuccess {
            val transformedPath = exchange.getAttribute<String>("org.springframework.cloud.gateway.support.ServerWebExchangeUtils.gatewayRequestUrl")
            logger.info("Path transformation: {} -> {}", originalPath, transformedPath)
        }
    }

    override fun getOrder(): Int = -1
}

总结

PrefixPath GatewayFilter Factory 是 Spring Cloud Gateway 中一个简单但强大的路径处理工具。通过合理使用这个过滤器,我们可以实现:

  • ✅ API 版本管理
  • ✅ 多租户路径隔离
  • ✅ 微服务命名空间管理
  • ✅ 统一的路径标准化
Details

扩展阅读想了解更多关于 Spring Cloud Gateway 的过滤器,可以参考: