You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Spring Cloud Gateway如何基于请求体JSON内容路由至对应服务?

问题描述

在Spring Cloud Gateway中,我已在yaml文件配置了两个路径相同的路由,分别指向http://localhost:8081/和http://localhost:8082/,对应两个同URI的POST接口。现需根据请求JSON体中codiceRicevitoria字段的值(为1时调用8081服务,否则调用8082服务)实现路由,请问该如何实现?当前自定义GatewayFilter的思路是否正确,还是使用自定义Predicate更合适?

我的配置与代码示例如下:
yaml配置:

spring:
  cloud:
    gateway:
      routes:
        - id: springcloud
          uri: http://localhost:8081/
          predicates:
            - Path=/springcloud/**
          filters:
            - name: InterceptRouteGatewayFilter


        - id: springcloud2
          uri: http://localhost:8082/
          predicates:
          - Path=/springcloud/**
          filters:
          - name: InterceptRouteGatewayFilter

Java代码:

@Component
public class InterceptRouteGatewayFilter extends AbstractGatewayFilterFactory<Object> {

    @Override
    public GatewayFilter apply(Object config) {
        return (exchange, chain) -> {
            ServerHttpRequest request = exchange.getRequest();
            Flux<DataBuffer> body = request.getBody();

            return body.collectList().flatMap(dataBuffers -> {
                byte[] contentBytes = dataBuffers.stream()
                        .map(buffer -> {
                            byte[] bytes = new byte[buffer.readableByteCount()];
                            buffer.read(bytes);
                            return bytes;
                        })
                        .reduce(new byte[0], (a, b) -> {
                            byte[] result = new byte[a.length + b.length];
                            System.arraycopy(a, 0, result, 0, a.length);
                            System.arraycopy(b, 0, result, a.length, b.length);
                            return result;
                        });

                String bodyJson = new String(contentBytes, StandardCharsets.UTF_8);
                String result= extractCodiceRicevitoria(bodyJson);
           
                if (result.equals("1")) {
                    // CALL http://localhost:8081/springcloud/microservice
                } else { 
                    // CALL http://localhost:8082/springcloud/microservice 
                }
                
                return chain.filter(exchange);
            });
        };
    }

    private String extractCodiceRicevitoria(String requestBody) {
        JsonObject jsonObject = new JsonParser().parse(requestBody).getAsJsonObject();
        return jsonObject.get("codiceRicevitoria").getAsString();
    }
}
解决方案分析

思路选择:自定义Predicate更合适

Spring Cloud Gateway中,Predicate的职责是判断请求是否匹配当前路由,而Filter主要负责请求/响应的修改逻辑。你的需求是根据请求体字段选择路由,用自定义RoutePredicateFactory更贴合Gateway的设计理念,路由规则更清晰。

而你当前的Filter思路存在问题:两个路由都匹配Path=/springcloud/**,会同时进入匹配逻辑,Filter无法直接拦截路由选择,只能修改请求URI,反而会让配置冗余。

下面提供两种可行方案:


方案一:自定义RoutePredicateFactory(推荐)

通过自定义Predicate,让每个路由根据请求体的codiceRicevitoria值决定是否匹配,完全符合Gateway的路由匹配逻辑。

1. 实现自定义Predicate类

@Component
public class CodiceRicevitoriaRoutePredicateFactory extends AbstractRoutePredicateFactory<CodiceRicevitoriaRoutePredicateFactory.Config> {

    public CodiceRicevitoriaRoutePredicateFactory() {
        super(Config.class);
    }

    @Override
    public Predicate<ServerWebExchange> apply(Config config) {
        return exchange -> {
            ServerHttpRequest request = exchange.getRequest();
            // 读取并缓存请求体(请求体只能读取一次)
            return DataBufferUtils.join(request.getBody())
                    .map(dataBuffer -> {
                        byte[] bytes = new byte[dataBuffer.readableByteCount()];
                        dataBuffer.read(bytes);
                        DataBufferUtils.release(dataBuffer);
                        // 解析JSON字段
                        String body = new String(bytes, StandardCharsets.UTF_8);
                        JsonObject jsonObject = new JsonParser().parse(body).getAsJsonObject();
                        String codice = jsonObject.get("codiceRicevitoria").getAsString();
                        // 缓存请求体供后续使用
                        exchange.getAttributes().put("cachedRequestBody", bytes);
                        // 判断是否匹配当前路由的目标值
                        return codice.equals(config.getTargetValue());
                    })
                    .defaultIfEmpty(false)
                    .block();
        };
    }

    // 接收yaml配置参数的内部类
    public static class Config {
        private String targetValue;

        public String getTargetValue() {
            return targetValue;
        }

        public void setTargetValue(String targetValue) {
            this.targetValue = targetValue;
        }
    }

    @Override
    public List<String> shortcutFieldOrder() {
        return Collections.singletonList("targetValue");
    }
}

2. 修改yaml配置

给每个路由添加自定义Predicate,同时通过ModifyRequestBody过滤器恢复缓存的请求体(否则后续服务无法读取):

spring:
  cloud:
    gateway:
      routes:
        - id: springcloud-8081
          uri: http://localhost:8081/
          predicates:
            - Path=/springcloud/**
            - CodiceRicevitoria=1
          filters:
            - name: ModifyRequestBody
              args:
                inClass: java.lang.String
                outClass: java.lang.String
                rewriteFunction: |
                  (exchange, body) -> {
                      byte[] cachedBody = exchange.getAttribute("cachedRequestBody");
                      return cachedBody != null ? new String(cachedBody, java.nio.charset.StandardCharsets.UTF_8) : body;
                  }
        - id: springcloud-8082
          uri: http://localhost:8082/
          predicates:
            - Path=/springcloud/**
            - CodiceRicevitoria!=1 # 匹配非1的所有情况
          filters:
            - name: ModifyRequestBody
              args:
                inClass: java.lang.String
                outClass: java.lang.String
                rewriteFunction: |
                  (exchange, body) -> {
                      byte[] cachedBody = exchange.getAttribute("cachedRequestBody");
                      return cachedBody != null ? new String(cachedBody, java.nio.charset.StandardCharsets.UTF_8) : body;
                  }

方案二:自定义GatewayFilter(单路由动态修改URI)

如果不想配置多个路由,可以用单路由+Filter动态修改目标URI的方式,适合简单场景。

1. 修改yaml配置

spring:
  cloud:
    gateway:
      routes:
        - id: springcloud
          uri: http://localhost:8081/ # 默认指向8081
          predicates:
            - Path=/springcloud/**
          filters:
            - name: DynamicUriGatewayFilter

2. 实现自定义Filter类

@Component
public class DynamicUriGatewayFilter extends AbstractGatewayFilterFactory<Object> {

    @Override
    public GatewayFilter apply(Object config) {
        return (exchange, chain) -> {
            ServerHttpRequest request = exchange.getRequest();
            return DataBufferUtils.join(request.getBody())
                    .flatMap(dataBuffer -> {
                        byte[] bytes = new byte[dataBuffer.readableByteCount()];
                        dataBuffer.read(bytes);
                        DataBufferUtils.release(dataBuffer);
                        // 解析请求体字段
                        String bodyJson = new String(bytes, StandardCharsets.UTF_8);
                        JsonObject jsonObject = new JsonParser().parse(bodyJson).getAsJsonObject();
                        String codice = jsonObject.get("codiceRicevitoria").getAsString();

                        // 动态生成目标URI
                        URI newUri = "1".equals(codice)
                                ? URI.create("http://localhost:8081" + request.getPath())
                                : URI.create("http://localhost:8082" + request.getPath());

                        // 重新包装请求,恢复请求体(避免后续无法读取)
                        ServerHttpRequestDecorator decoratedRequest = new ServerHttpRequestDecorator(request.mutate().uri(newUri).build()) {
                            @Override
                            public Flux<DataBuffer> getBody() {
                                return Flux.just(exchange.getResponse().bufferFactory().wrap(bytes));
                            }
                        };

                        return chain.filter(exchange.mutate().request(decoratedRequest).build());
                    });
        };
    }
}

关键注意事项
  1. 请求体只能读取一次:无论哪种方案,都必须缓存并恢复请求体,否则后续服务或过滤器无法获取请求内容。
  2. 异常处理:要添加JSON解析失败、字段缺失等异常捕获逻辑,避免网关崩溃。
  3. 性能考量:高并发场景下,缓存请求体可能占用内存,建议限制请求体大小,或使用流式处理优化。

内容的提问来源于stack exchange,提问作者R.S.

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.25 23:42:50