使用Kotlin中的反应器对Gitlab API进行分页

使用Kotlin中的反应器对Gitlab API进行分页,kotlin,reactive-programming,project-reactor,gitlab-api,Kotlin,Reactive Programming,Project Reactor,Gitlab Api,我正在编写一段Kotlin代码,它使用reactor框架实现对Gitlab提交API的调用。 提交API已分页。我正在使用的函数在两个指定的提交散列之间检索提交 只要函数能够实际检索任何提交,它就可以正常工作,但如果找不到结果,它就会失败。然后它失败了 java.lang.RuntimeException:已到达提交日志的末尾 我尝试用.switchIfEmpty(Flux.error(RuntimeException(“到达提交日志的末尾”))替换行.switchIfEmpty(Flux.em

我正在编写一段Kotlin代码,它使用reactor框架实现对Gitlab提交API的调用。 提交API已分页。我正在使用的函数在两个指定的提交散列之间检索提交

只要函数能够实际检索任何提交,它就可以正常工作,但如果找不到结果,它就会失败。然后它失败了
java.lang.RuntimeException:已到达提交日志的末尾

我尝试用
.switchIfEmpty(Flux.error(RuntimeException(“到达提交日志的末尾”))
替换行
.switchIfEmpty(Flux.empty())
,但这会生成一个无休止的循环

我不太了解多个通量的嵌套,这使我很难调试。我非常感谢任何关于如何解决这个问题的提示

fun getCommits(fromCommit: String, toCommit: String): Iterable<Commit> {
    val commits = Flux.concat(Flux.generate<Flux<GitLabCommit>, Int>({ 1 }) { state, sink ->
        val page = client.get()
                .uri("/projects/{name}/repository/commits?page=$state&per_page=100")
                .accept(MediaType.APPLICATION_JSON)
                .retrieve()
                .bodyToFlux<GitLabCommit>()
                .doOnError({
                    LOGGER.warn("Could not retrieve commits for project '$name': ${it.message}")
                    sink.next(Flux.just(GitLabCommit("xxxxx", "Could not retrieve all commits for project '$name'")))
                    sink.complete()
                })
                .onErrorReturn(GitLabCommit("xxxxx", "Could not retrieve all commits for project '$name'"))
                .switchIfEmpty(Flux.error(RuntimeException("Reached end of commit log.")))

        sink.next(page)
        return@generate state + 1
    })


    return commits
            // The Gitlab API returns commits from newest to oldest
            .skipWhile { !it.id.startsWith(toCommit) } //inclusive
            .takeWhile { !it.id.startsWith(fromCommit) } //exclusive
            .map { Commit(it.title, listOf(it.id), name) }
            .toIterable()
}

客户端
org.springframework.web.reactive.function.client.WebClient.Builder
的一个正确初始化的实例,它有助于令牌处理和URL编码。

异常来源:外部使用的
提交
没有
onErrorResume
子句。 无止境循环的来源:在内部
生成
中没有错误时,它将继续循环,增加
状态
并连接空结果

我会采取一种不同于生成的方法:range+concatMap+takeWhile

大概是这样的:

fun getCommits(fromCommit: String, toCommit: String): Iterable<String> =
    Flux.range(1, 1000) //tune the second parameter
            .concatMap { page -> client
                .get()
                .uri("/projects/{name}/repository/commits?page=$page&per_page=100")
                .accept(MediaType.APPLICATION_JSON)
                .retrieve()
                .bodyToFlux<GitLabCommit>()
                .doOnError({ LOGGER.warn("Could not retrieve commits for project '$name': ${it.message}")})
                .onErrorReturn(GitLabCommit("xxxxx", "Could not retrieve all commits for project '$name'"))
                .defaultIfEmpty(GitLabCommit("xxxxx", "Reached end of commit log."))
                // ^ we now have marker commits in case of error or end of log
            }
            //use the marker commits to short-circuit the range
            .takeWhile { !it.id.equals("xxxxx") } //end condition to cancel the range loop, doesn't include the xxxxx commits
            // The Gitlab API returns commits from newest to oldest
            .skipWhile { !it.id.startsWith(toCommit) } //inclusive
            .takeWhile { !it.id.startsWith(fromCommit) } //exclusive
            .map { Commit(it.title, listOf(it.id), name) }
            .toIterable()
fun getCommits(fromCommit:String,toCommit:String):Iterable=
Flux.range(11000)//调整第二个参数
.concatMap{page->client
.get()
.uri(“/projects/{name}/repository/commits?page=$page&per_page=100”)
.accept(MediaType.APPLICATION_JSON)
.retrieve()
.bodyToFlux()
.doError({LOGGER.warn(“无法检索项目“$name”:${it.message}”)的提交)
.OneErrorReturn(GitLabCommit(“xxxxx”,“无法检索项目“$name”的所有提交))
.defaultIfEmpty(GitLabCommit(“xxxxx”,“已到达提交日志的末尾”))
//^如果出现错误或日志结束,我们现在有标记提交
}
//使用标记器使范围短路
.takeWhile{!it.id.equals(“xxxxx”)}//取消范围循环的结束条件,不包括xxxxx提交
//Gitlab API返回从最新到最旧的提交
.skipWhile{!it.id.startsWith(toCommit)}//包含在内
.takeWhile{!it.id.startsWith(fromCommit)}//独占
.map{Commit(it.title,listOf(it.id),name)}
.toIterable()
我们从多达1000页的范围开始,对每一页都提出请求

这可能会导致额外的请求(例如,如果到第100页时我们已经收到一个空响应,那么就不会有更多的页面)。但您几乎解决了此问题:

我们可以使用
onerrorrurn
defaultIfEmpty
创建标记提交,然后在
takeWhile
中的
concatMap
之后使用该标记提交

一旦附加的
takeWhile
看到第一个
xxxxx
提交,它将触发一个取消,该取消将传播到concatMap/range,导致range停止发送页码,从而停止发出无关请求


然后您就有了您的业务逻辑
skipWhile
takeWhile
map

非常感谢您提供的详细答案!你最初的建议解决了这个问题。我也会看看你的选择,评论它,然后接受你的答案!我试着实现你的其他建议,但变量范围和大括号不匹配存在问题。我自己无法解决这个问题,您能再看一眼吗?已编辑,如果您在函数之外的作用域中有一个
client
变量(如您的原始代码片段中),现在应该会更好
fun getCommits(fromCommit: String, toCommit: String): Iterable<String> =
    Flux.range(1, 1000) //tune the second parameter
            .concatMap { page -> client
                .get()
                .uri("/projects/{name}/repository/commits?page=$page&per_page=100")
                .accept(MediaType.APPLICATION_JSON)
                .retrieve()
                .bodyToFlux<GitLabCommit>()
                .doOnError({ LOGGER.warn("Could not retrieve commits for project '$name': ${it.message}")})
                .onErrorReturn(GitLabCommit("xxxxx", "Could not retrieve all commits for project '$name'"))
                .defaultIfEmpty(GitLabCommit("xxxxx", "Reached end of commit log."))
                // ^ we now have marker commits in case of error or end of log
            }
            //use the marker commits to short-circuit the range
            .takeWhile { !it.id.equals("xxxxx") } //end condition to cancel the range loop, doesn't include the xxxxx commits
            // The Gitlab API returns commits from newest to oldest
            .skipWhile { !it.id.startsWith(toCommit) } //inclusive
            .takeWhile { !it.id.startsWith(fromCommit) } //exclusive
            .map { Commit(it.title, listOf(it.id), name) }
            .toIterable()