Java 从分页网络调用序列生成Spring流量

Java 从分页网络调用序列生成Spring流量,java,spring-webflux,reactive,Java,Spring Webflux,Reactive,我正在使用Spring反应式WebFlux客户端调用API API.magicthegathering.io/v1/cards。响应是一个包含100张卡片的页面,以及包含“下一页”和“最后一页”链接的标题,例如,“最后一页”是api.magicthegating.io/v1/cards?page=426(“下一页”只是n+1)。我想生成一个Flux,用一个入口点分别输出每个卡片,例如Flux getAllCards() 我目前有一个CardsClient组件,它返回一个Mono。CardPage

我正在使用Spring反应式WebFlux客户端调用API API.magicthegathering.io/v1/cards。响应是一个包含100张卡片的页面,以及包含“下一页”和“最后一页”链接的标题,例如,“最后一页”是
api.magicthegating.io/v1/cards?page=426
(“下一页”只是n+1)。我想生成一个
Flux
,用一个入口点分别输出每个卡片,例如
Flux getAllCards()

我目前有一个
CardsClient
组件,它返回一个
Mono
CardPage
上有一个
cards()
方法,返回其中的所有卡片(这是API响应模型的1:1表示)。除此之外,我还有一个
CardCatalog
组件,上面有
getAllCards()
方法

我尝试过使用
Flux::expand
Flux::generate
,这在某种程度上是可行的,但这些实现都有缺陷

下面是我当前迭代的
CardCatalog::getAllCards()
的一个片段。问题在于
expand
的递归性质导致了对
client::getNextPage
的冗余调用;显然,我没有使用正确的方法

  @Override
  public Flux<Card> getAllCards() {
    return client.getFirstPage().flux().expand(client::getNextPage)
        .map(Page::cards)
        .flatMap(Flux::fromIterable)
        .map(mapper::convert)
        .cache();
  }
完整代码如下:

使用
expand
,我向
client::getNextPage()
添加了一个打印。如您所见,图中显示的是创建冗余调用

Getting https://api.magicthegathering.io/v1/cards?page=1
Getting https://api.magicthegathering.io/v1/cards?page=7
Getting https://api.magicthegathering.io/v1/cards?page=2
Getting https://api.magicthegathering.io/v1/cards?page=8
Getting https://api.magicthegathering.io/v1/cards?page=3
Getting https://api.magicthegathering.io/v1/cards?page=9
Getting https://api.magicthegathering.io/v1/cards?page=4
Getting https://api.magicthegathering.io/v1/cards?page=10
Getting https://api.magicthegathering.io/v1/cards?page=5
Getting https://api.magicthegathering.io/v1/cards?page=11
Getting https://api.magicthegathering.io/v1/cards?page=6
Getting https://api.magicthegathering.io/v1/cards?page=12
Getting https://api.magicthegathering.io/v1/cards?page=7
我想要更像这样的东西:

Getting https://api.magicthegathering.io/v1/cards?page=1
Getting https://api.magicthegathering.io/v1/cards?page=2
Getting https://api.magicthegathering.io/v1/cards?page=3

(最后一点注意:并行化并直接调用URI肯定会更快,但绕过下一个/最后一个机制并硬编码URI感觉有点傻。我可能会这样做,但仍然想解决这个问题。)

好吧,我想出了一些可行的方法。我决定使用页面计数方法来尝试并行化,尽管由于网络IO仍然是瓶颈,因此并行化速度不快。我可能会回到标题链接爬网并使用缓存。大致上,神奇的数字和所有,这是它看起来的样子:

  @Override
  public Flux<Card> getAllCards() {
    return client.getPageCount().flatMapMany(pageCount ->
        Flux.concat(
            range(1, pageCount)
                .parallel(pageCount / 6).runOn(Schedulers.parallel())
                .map(client::getPage)
        ).map(Page::cards).flatMap(Flux::fromIterable).map(mapper::convert)
    );
  }
@覆盖
公共流量getAllCards(){
返回client.getPageCount().flatMapMany(pageCount->
海螺(
范围(1,页数)
.parallel(pageCount/6).runOn(Schedulers.parallel())
.map(客户端::getPage)
).map(Page::cards).flatMap(Flux::fromIterable).map(mapper::convert)
);
}

我认为这是一种连续的非阻塞方式:

公共流量getAllCards(){
PaginationParams PaginationParams=新的PaginationParams();
最终通量页面通量=单声道
.defer(()->client.getPage(分页参数))
.doOnNext(第->{
if(page.next().isPresent()){
paginationParams.setPage(page.next().get());
}否则{
paginationParams.setPage(空);
}
})
.repeat(()->paginationParams.getPage()!=null);
返回pageFlux.flatMapIterable(Page::cards).map(mapper::convert);
}
  @Override
  public Flux<Card> getAllCards() {
    return client.getPageCount().flatMapMany(pageCount ->
        Flux.concat(
            range(1, pageCount)
                .parallel(pageCount / 6).runOn(Schedulers.parallel())
                .map(client::getPage)
        ).map(Page::cards).flatMap(Flux::fromIterable).map(mapper::convert)
    );
  }