Java 春云门户修改通量响应库

Java 春云门户修改通量响应库,java,spring-cloud-gateway,Java,Spring Cloud Gateway,我在修改SpringCloudGateway中的流量响应时遇到了一些问题。我的设置简化如下: 我有2个域对象: @Data @AllArgsConstructor public class PersonV10 { @NotNull private String name; } @Data @AllArgsConstructor public class PersonV20 { @NotNull private String firstName; @N

我在修改SpringCloudGateway中的流量响应时遇到了一些问题。我的设置简化如下: 我有2个域对象:

@Data
@AllArgsConstructor
public class PersonV10 {
    @NotNull
    private String name;
}


@Data
@AllArgsConstructor
public class PersonV20 {
    @NotNull
    private String firstName;

    @NotNull
    private String lastName;
}
现在,我的服务返回PersonV20的实例,使用SpringCloudGateway,我想修改响应,使之成为PersonV10。在/person上我得到一个通量作为响应,在/person/{id}上我得到一个Mono作为响应

我的路线如下:

@Bean
public RouteLocator routes(RouteLocatorBuilder builder) {
    return builder.routes()
            .route("rewrite_response_v20", r -> r.path("/person/**")
                    .filters(f -> f.modifyResponseBody(PersonV20.class, PersonV10.class,
                            (exchange, p) -> Mono.just(new PersonV10(p.getFirstName() + " " + p.getLastName()))))
                    .uri("https://localhost:8444"))
                    .build();
}
当调用/person/{id}端点时,这种方法非常有效,我的响应被很好地修改为PersonV10。然而,当我现在调用/person端点时,我得到了一个流量,我在SpringCloudGateway中得到了这个异常:

org.springframework.core.codec.DecodingException: JSON decoding error: Cannot deserialize instance of `com.example.gateway.mutate_response_filter.PersonV20` out of START_ARRAY token; nested exception is com.fasterxml.jackson.databind.exc.MismatchedInputException: Cannot deserialize instance of `com.example.gateway.mutate_response_filter.PersonV20` out of START_ARRAY token
 at [Source: (io.netty.buffer.ByteBufInputStream); line: 1, column: 1]
    at org.springframework.http.codec.json.AbstractJackson2Decoder.processException(AbstractJackson2Decoder.java:215) ~[spring-web-5.2.6.RELEASE.jar:5.2.6.RELEASE]

现在我想这是有意义的,因为我得到了一个通量作为响应,在modifyResponseBody中我使用的是单声道。但在这种情况下如何使用助焊剂,我并不清楚。有人能给我指出正确的方向吗?谢谢

从SpringCloudGateway源代码中,您会发现一个TODO,上面写着“TODO:flux或mono”。这意味着目前只支持单一类型

// TODO: flux or mono
Mono modifiedBody = extractBody(exchange, clientResponse, inClass)
                    .flatMap(originalBody -> config.getRewriteFunction().apply(exchange,
                            originalBody))
                    .switchIfEmpty(Mono.defer(() -> (Mono) config.getRewriteFunction()
                            .apply(exchange, null)));
若你们想使用默认的ModifyResponseBodyFilter,也许你们可以把这个类放在一个包装类中,例如

class PersonV2ListWrapper{
    public List<PersonV2> persons;
}

否则,实现ModifyResponseBodyFilter功能和支持Flux的自过滤器也可能会有所帮助。

多亏@eric的提示,我才能够实现这一点。有几点需要注意:现在将集合包装在Mono中,当然我现在删除了反应流的一些好处,因此更好的方法当然是编写支持Flux的ModifyResponseBodyFilter的实现。另一个问题与原来的问题有点离题,所以我会在这篇文章的最后讨论这个问题

最终对我有效的解决方案是:

private static final String ROOT_PATTERN = "/person";
private static final String INDIVIDUAL_PATTERN = "/person/*";
private AntPathMatcher pathMatcher = new AntPathMatcher();

@Bean
public RouteLocator routes(RouteLocatorBuilder builder) {
    return builder.routes()
            .route("rewrite_response_v20_flux", r -> r.path(ROOT_PATTERN)
                    .filters(f -> f.modifyResponseBody(PersonV20[].class, PersonV10[].class,
                            (exchange, s) -> Mono.just(
                                    Stream.of(s)
                                            .map(v20 -> new PersonV10(v20.getId(), v20.getFirstName() + " " + v20.getLastName()))
                                            .toArray(PersonV10[]::new)
                            )))
                    .uri("https://localhost:8444")
                    .predicate(e -> pathMatcher.match(ROOT_PATTERN, e.getRequest().getPath().value()))
            )
            .route("rewrite_response_v20_mono", r -> r.path(INDIVIDUAL_PATTERN)
                    .filters(f -> f.rewritePath("/person/(?<ID>.*)", "/person/${ID}")
                            .modifyResponseBody(PersonV20.class, PersonV10.class,
                                    (exchange, s) -> Mono.just(new PersonV10(s.getId(), s.getFirstName() + " " + s.getLastName()))))
                    .uri("https://localhost:8444")
                    .predicate(e -> pathMatcher.match(INDIVIDUAL_PATTERN, e.getRequest().getPath().value()))
            )
            .build();
}
但是,即使在测试请求路径的末尾没有添加额外的谓词,这个测试也会成功,但它对我来说不起作用。如果我把重写-响应-v20-流量路由放在第一位,那么个人请求会失败,出现一个DecodingException异常,就像我原来的帖子一样,如果我把重写-响应-v20-流量路由放在第一位,那么个人请求的完整列表就会失败。在我看来,额外的谓词不应该是必要的,但是为了让它工作,我必须添加这些谓词

对于这个主题的任何其他评论/帮助都是非常受欢迎的,因为即使这样做有效,解决方案仍然不理想


希望这至少能帮助别人。

谢谢!这个建议非常有用。这不是开箱即用,但你确实让我走上了正轨。事实证明,如果我将这些类型设置为Person对象的数组,那么它实际上是有效的!我会在一个单独的答案中发布我的解决方案的更完整的答案。希望你的新帖子。似乎我误解了源代码的这一部分。我也将尝试你提到的这个。我已经发布了工作代码。对您的观点非常感兴趣,为什么在没有额外谓词的情况下无法正确选择路径。对于@jonckwanderkogel,您可以测试两种方法:r->r.pathROOT\u PATTERN.ordera大值。或r->r.pathROOT\u模式,错误
public class PathMatcherTests {
    private AntPathMatcher pathMatcher = new AntPathMatcher();

    private static final String ROOT_ENDPOINT = "/person";
    private static final String INDIVIDUAL_ENDPOINT = "/person/1";

    @Test
    public void shouldMatchRootButNotIndividual() {
        String rootMatchPattern = "/person";

        assertTrue(pathMatcher.match(rootMatchPattern, ROOT_ENDPOINT));
        assertFalse(pathMatcher.match(rootMatchPattern, INDIVIDUAL_ENDPOINT));
    }

    @Test
    public void shouldMatchIndividualButNotRoot() {
        String individualMatchPattern = "/person/*";

        assertFalse(pathMatcher.match(individualMatchPattern, ROOT_ENDPOINT));
        assertTrue(pathMatcher.match(individualMatchPattern, INDIVIDUAL_ENDPOINT));
    }
}