Spring 复合 API 调用的云网关?

Spring Cloud Gateway for composite API calls?

我开始构建一个微服务 API 网关,我正在考虑 Spring 云来帮助我进行路由。但是对网关 API 的一些调用将需要对不同服务的多个请求。

假设我有 2 项服务:订单详细信息服务送货服务。我想要一个网关端点 GET /orders/{orderId} 调用 Order Details 服务 然后 送货服务 并将两者合并为 return 带送货的完整订单详细信息。这可能通过 Spring 云的路由实现,还是我应该使用 RestTemplate 之类的东西来手动制作这些调用?

在 GitHub 到 have routes support multiple URIs. So far, there aren't any plans to implement this yet, at least, not according to one of the contributors 上发布了增强建议。

如 Spring Cloud Gateway Github 问题 中所述,在库为此开发过滤器之前,我使用自己的 ModifyResponseBodyGatewayFilterFactory 解决了它自定义过滤器。

以防万一它对其他人有用,我在这里提供基本实现(它可能需要一些返工,但它应该足以说明问题)。

简而言之,我有一个“基本”服务检索类似这样的内容:

[
  {
    "targetEntryId": "624a448cbc728123b47d08c4",
    "sections": [
      {
        "title": "sadasa",
        "description": "asda"
      }
    ],
    "id": "624a448c45459c4d757869f1"
  },
  {
    "targetEntryId": "624a44e5bc728123b47d08c5",
    "sections": [
      {
        "title": "asda",
        "description": null
      }
    ],
    "id": "624a44e645459c4d757869f2"
  }
]

我想用实际的 targetEntry 数据(当然,由 targetEntryId 标识)丰富这些条目。

所以,我基于 ModifyResponseBody 创建了我的过滤器:

/**
 * <p>
 *   Filter to compose a response body with associated data from a second API.
 * </p>
 *
 * @author rozagerardo
 */
@Component
public class ComposeFieldApiGatewayFilterFactory extends
    AbstractGatewayFilterFactory<ComposeFieldApiGatewayFilterFactory.Config> {

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

  @Autowired
  ModifyResponseBodyGatewayFilterFactory modifyResponseBodyFilter;

  ParameterizedTypeReference<List<Map<String, Object>>> jsonType =
      new ParameterizedTypeReference<List<Map<String, Object>>>() {
      };

  @Value("${server.port:9080}")
  int aPort;

  @Override
  public GatewayFilter apply(final Config config) {
    return modifyResponseBodyFilter.apply((c) -> {
      c.setRewriteFunction(List.class, List.class, (filterExchange, input) -> {
        List<Map<String, Object>> castedInput = (List<Map<String, Object>>) input;
        //  extract base field values (usually ids) and join them in a "," separated string
        String baseFieldValues = castedInput.stream()
            .map(bodyMap -> (String) bodyMap.get(config.getOriginBaseField()))
            .collect(Collectors.joining(","));

        // Request to a path managed by the Gateway
        WebClient client = WebClient.create();
        return client.get()
            .uri(UriComponentsBuilder.fromUriString("http://localhost").port(aPort)
                .path(config.getTargetGatewayPath())
                .queryParam(config.getTargetQueryParam(), baseFieldValues).build().toUri())
            .exchangeToMono(response -> response.bodyToMono(jsonType)
                .map(targetEntries -> {
                  // create a Map using the base field values as keys fo easy access
                  Map<String, Map> targetEntriesMap = targetEntries.stream().collect(
                      Collectors.toMap(pr -> (String) pr.get("id"), pr -> pr));
                  // compose the origin body using the requested target entries
                  return castedInput.stream().map(originEntries -> {
                    originEntries.put(config.getComposeField(),
                        targetEntriesMap.get(originEntries.get(config.getOriginBaseField())));
                    return originEntries;
                  }).collect(Collectors.toList());
                })
            );
      });
    });
  }

  ;

  @Override
  public List<String> shortcutFieldOrder() {
    return Arrays.asList("originBaseField", "targetGatewayPath", "targetQueryParam",
        "composeField");
  }

  /**
   * <p>
   * Config class to use for AbstractGatewayFilterFactory.
   * </p>
   */
  public static class Config {

    private String originBaseField;
    private String targetGatewayPath;
    private String targetQueryParam;
    private String composeField;

    public Config() {
    }

    // Getters and Setters...

  }
}

为了完整起见,这是使用我的过滤器的相应路由设置:

spring:
  cloud:
    gateway:
      routes:
        # TARGET ENTRIES ROUTES
        - id: targetentries_route
          uri: ${configs.api.tagetentries.baseURL}
          predicates:
            - Path=/api/target/entries
            - Method=GET
          filters:
            - RewritePath=/api/target/entries(?<segment>.*), /target-entries-service$\{segment}
        # ORIGIN ENTRIES
        - id: originentries_route
          uri: ${configs.api.originentries.baseURL}
          predicates:
            - Path=/api/origin/entries**
          filters:
            - RewritePath=/api/origin/entries(?<segment>.*), /origin-entries-service$\{segment}
            - ComposeFieldApi=targetEntryId,/api/target/entries,ids,targetEntry

有了这个,我得到的响应如下所示:

[
  {
    "targetEntryId": "624a448cbc728123b47d08c4",
    "sections": [
      {
        "title": "sadasa",
        "description": "asda"
      }
    ],
    "id": "624a448c45459c4d757869f1",
    "targetEntry": {
      "id": "624a448cbc728123b47d08c4",
      "targetEntityField": "whatever"
    }
  },
  {
    "targetEntryId": "624a44e5bc728123b47d08c5",
    "sections": [
      {
        "title": "asda",
        "description": null
      }
    ],
    "id": "624a44e645459c4d757869f2",
    "targetEntry": {
      "id": "624a44e5bc728123b47d08c5",
      "targetEntityField": "somethingelse"
    }
  }
]