简体   繁体   English

Spring webflux webclient 在评估第一次呼叫的响应时再次拨打电话

[英]Spring webflux webclient make another call while evaluating response from first call

I am calling an endpoint with webclient and want to map the response that I get to another object.我正在使用 webclient 调用端点,并希望 map 得到另一个 object 的响应。 While mapping that object I want to make additional calls for some parameters of the response.在映射 object 时,我想对响应的某些参数进行额外调用。

My first call returns the following object我的第一个电话返回以下 object

{
  "type": "Collection",
  "key": "some.key",
  "settings": [
    {
      "type": "Struct",
      "key": "steps",
      "value": [
        {
          "type": "Struct",
          "key": "1",
          "value": [
            {
              "type": "String",
              "key": "headline",
              "value": "someheadline"
            },
            {
              "type": "String",
              "key": "subheadline",
              "value": "somesubheadline"
            },
            {
              "type": "Link",
              "key": "link.to.another.object",
              "value": {
                "linkType": "Boilerplate",
                "key": "configurabletextkey"
              }
            }
          ]
        }
      ]
    },
    {
      "type": "Struct",
      "key": "commons",
      "value": [
        {
          "type": "String",
          "key": "mandatory.fields.text",
          "value": "Pflichtfelder"
        }
      ]
    }
  ]
}

I map that response like so:我的 map 响应如下:

webClient
        .get()
        .uri(
            uriBuilder ->
                uriBuilder
                    .path(headlessConfig.getEndpoint())
                    .pathSegment(contentId)
                    .build())
        .retrieve()
        .bodyToMono(Collection.class)
        .map(response -> {
                  return getCollectionContent(response);
                })

In the getCollectionContent method I iterate over the settings array and I extract data from the response and map it to my PageContent Object.在 getCollectionContent 方法中,我遍历设置数组并从响应中提取数据,并将 map 数据发送到我的 PageContent Object。

public class PageContent {

  private String pageId;

  private List<Message> messages;
}
public class Message {

  @NonNull private String key;

  @NonNull private String text;

  private Boolean containsHtml = false;
}

If the response contains the type "String" i'll just add the data to an Message Object and add it to the List of the PageContent.如果响应包含“字符串”类型,我只需将数据添加到消息 Object 并将其添加到 PageContent 列表中。

Now to the problem.现在来解决问题。 If the type is "Link" I want to make another call with webclient like above to the same endpoint to get the key and text for that object, create a Message Object out of it and add it to my existing List.如果类型是“链接”,我想像上面一样使用 webclient 对同一端点进行另一个调用,以获取该 object 的密钥和文本,创建一条消息 Object 并将其添加到我现有的列表中。

The corresponding code looks like so:对应的代码如下所示:

webClient
        .get()
        .uri(
            uriBuilder ->
                uriBuilder
                    .path(headlessConfig.getEndpoint())
                    .pathSegment(contentKey)
                    .build())
        .retrieve()
        .bodyToMono(ConfigurableText.class)
        .map(
                configurableTextResponse -> {
                  messages.add(
                      new Message(
                          prefix + configurableTextResponse.getKey(),
                          configurableTextResponse.getText(),
                          true));
                  return Mono.empty();
                })

Now when I try to do that nothing happens and I just receive the PageContent object without the message for the link.现在,当我尝试这样做时,什么也没有发生,我只收到没有链接消息的 PageContent object。

In a blocking way with resttemplate this logic should work but I would like to get it to work with webclient.以resttemplate的阻塞方式,这个逻辑应该可以工作,但我想让它与webclient一起工作。

Edit:编辑:

Code to iterate through the list and extract the message data:遍历列表并提取消息数据的代码:

private PageContent getCollectionContent(Collection response) {

    PageContent pageContent = new PageContent();
    pageContent.setPageId(response.getKey());

    List<Message> messages = new ArrayList<>();

    response
        .getSettings()
        .forEach(
            settingsItemsArray -> {
              var settingsItemList = (List<?>) settingsItemsArray.getValue();
              String prefix = settingsItemsArray.getKey() + ".";

              extractMessageText(prefix, (LinkedHashMap<?, ?>) settingsItemList.get(0), messages);
            });

    pageContent.setMessages(messages);

    return pageContent;
  }

Code to extract the MessageText, iterate further or get the missing text for a link type.用于提取 MessageText、进一步迭代或获取链接类型的缺失文本的代码。

private void extractMessageText(
      String prefix, LinkedHashMap<?, ?> settingsItem, List<Message> messages) {

    String itemKey = (String) settingsItem.get(KEY);
    String itemType = (String) settingsItem.get(TYPE);

    switch (itemType) {
      case "String":
        messages.add(new Message(prefix + itemKey, (String) settingsItem.get(VALUE)));
        break;
      case "Struct":
        ((List<?>) settingsItem.get(VALUE))
            .forEach(
                structItems ->
                    extractMessageText(
                        prefix + settingsItem.get(KEY) + ".",
                        (LinkedHashMap<?, ?>) structItems,
                        messages));
        break;
      case "Link":
        webClient
        .get()
        .uri(
            uriBuilder ->
                uriBuilder
                    .path(headlessConfig.getEndpoint())
                    .pathSegment(contentKey)
                    .build())
        .retrieve()
        .bodyToMono(ConfigurableText.class)
        .map(
                configurableTextResponse -> {
                  messages.add(
                      new Message(
                          prefix + configurableTextResponse.getKey(),
                          configurableTextResponse.getText(),
                          true));
                  return Mono.empty();
                })
        break;
      default:
        break;
    }
  }

I have changed some of your code to make it more compatible with the reactor pattern.我已经更改了您的一些代码,以使其与反应器模式更兼容。 I have changed the recursion into expandDeep and also used Jackson to parse the JSON.我已将递归更改为expandDeep并使用 Jackson 来解析 JSON。 I hope this will give you some ideas how to solve your issue.我希望这会给你一些想法来解决你的问题。

List<Message> messages = Flux
                .fromIterable(jsonNode.get("settings"))
                //expand the graph into a stream of flat data and track the address of the node with 'prefix'
                //expand/exapndDeep operators are alternatives of recursion in project reactor
                .expandDeep(parent -> {
                    String parentPrefix = Optional.ofNullable(parent.get("prefix")).map(JsonNode::asText)
                            .orElse(parent.get("key").asText());
                    String type = parent.get("type").asText();
                    if (type.equals("Struct")) {
                        return Flux.fromIterable(parent.get("value"))
                                .cast(ObjectNode.class)
                                .map(child -> child.put("prefix", parentPrefix + ":" + child.get("key").asText()));
                    }
                    return Mono.empty();
                })
                //we have to choose only leaf nodes aka String and Link nodes
                .filter(node -> Arrays.asList("String", "Link").contains(node.get("type").asText()))
                //now process expanded leaf nodes
                .flatMap(leaf -> {
                    if ("String".equals(leaf.get("type").asText())) {
                        return Mono.just(new Message(leaf.get("prefix").asText(), leaf.get("value").asText(), true));
                    }
                    if ("Link".equals(leaf.get("type").asText())) {
                        return webClient
                                .get()
                                .uri(
                                        uriBuilder ->
                                                uriBuilder
                                                        .pathSegment(leaf.get("key").asText())
                                                        .build())
                                .retrieve()
                                .bodyToMono(JsonNode.class)
                                .map(configurableTextResponse -> new Message(
                                        leaf.get("prefix") + configurableTextResponse.get("key").asText(),
                                        configurableTextResponse.get("text").asText(),
                                        true));
                    }
                    return Mono.empty();
                })
                // at this point we are getting stream of the Message objects from the Link/String nodes
                //collect them into a list
                .collectList()
                //we have to subscribe()/block() the mono to actually invoke the pipline.
                .block();

The main reason your code did nothing was that you were not subscribing to your WebClient pipeline.您的代码什么也没做的主要原因是您没有订阅 WebClient 管道。

EDIT:编辑:

change改变

  .map(response -> {
                  return getCollectionContent(response);
                })

to

.flatMap(response -> {
                      return getCollectionContent(response);
                    })

and return from getCollectionContent(response) Mono<PageContent> page并从getCollectionContent(response) Mono<PageContent> page返回

something like:就像是:

     // at this point we are getting stream of the Message objects from the Link/String nodes
                    //collect them into a list
                    .collectList()
                    .map(messages -> {
                        PageContent pageContent = new PageContent();
                        pageContent.setPageId(response.get("pageId").asText());
pageContent.setMessages(messages);
                        return pageContent;
                    });

after these changes, your getCollectionContent() will return a publisher Mono<PageContent> which will be subscribed from the flatMap operator.在这些更改之后,您的getCollectionContent()将返回一个发布者Mono<PageContent> ,该发布者将从 flatMap 运算符订阅。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM