You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Spring Webflux带依赖多顺序Web请求的实现优化与问题咨询

Spring Webflux响应式流调用外部API的优化解答

原始实现代码

public Mono<List<ParcelInfo>> parcelEventsForReference(String parcelId, Postcode postcode) {
    return connector1.getParcelReference(parcelId) // 1. Mono<ResolvedParcelIds> (Web请求1)
        .flatMapIterable(ResolvedParcelIds::getParcelIds) // 2. Flux<ResolvedParcelId>
        .flatMap(id -> parcelEventsFor(id) // 3. Mono<ParcelInfo> 
            .map(parcel -> redactIfNecessary(parcel, id, postcode))) // 4. Flux<ParcelInfo>
        .collectList() // 5. Mono<List<ParcelInfo>>
        .flatMapIterable(list -> list) // 6. Flux<ParcelInfo>
        .flatMap(this::getTrackingEvents)
        .collectList()
        .doOnError(throwable -> log.error(EXTERNAL_SERVICE_ERROR, throwable.getMessage()));
 }

private Mono<ParcelInfo> parcelEventsFor(ResolvedParcelId id) {
    return connector2.getParcelBy(id).zipWith(connector3.getAdditionalInstructions(uid)) // (Web请求2,3)
            .map(tuple -> combineParcelEvents(tuple.getT1(), tuple.getT2()));
 }

private Mono<ParcelInfo> getTrackingEvents(ParcelInfo parcel) {
    TrackingRequest request = mapToRequest(parcel);
    return connector4.getTrackingForParcel(request) // (Web请求4)
        .map(trackingResponse -> addTracking(parcel, trackingResponse));
}

private ParcelInfo addTracking(ParcelInfo parcel, TrackingResponse trackingResponse) {
    parcelProgress.setTracking(trackingResponse);
    return parcel;
 }

public class ParcelInfo { 
    private List<ParcelBase> parcel;
    private TrackingResponse trackingResponse;
    private String additionalInstructions;
}

public class ResolvedParcelIds {
    private List<ResolvedParcelId> parcelIds;
}

疑问解答

1. 是否应使用zipWhen运算符确保第4次请求仅在第1、2、3次请求完成后执行?若应使用,该如何实现?

你的当前实现已经保证了第4次请求(getTrackingEvents)是在第1、2、3次请求完成后才执行的——因为flatMap只会在上游流的元素产生后才触发后续操作,而上游的flatMap(id -> parcelEventsFor(id))已经涵盖了请求1、2、3的完整流程。

zipWhen并不适合这个场景:它的核心作用是将当前Mono的结果作为参数触发另一个Mono,再合并两者结果,更适合一对一的依赖场景。而你的需求是每个ParcelInfo都需要单独触发请求4,用flatMap才是更贴合的选择。

此外,当前代码里的两次collectList+flatMapIterable属于冗余操作,可以优化流程减少不必要的聚合与展开:

public Mono<List<ParcelInfo>> parcelEventsForReference(String parcelId, Postcode postcode) {
    return connector1.getParcelReference(parcelId) // 请求1
            .flatMapIterable(ResolvedParcelIds::getParcelIds)
            .flatMap(id -> parcelEventsFor(id) // 并行执行请求2、3
                    .map(parcel -> redactIfNecessary(parcel, id, postcode)))
            .flatMap(this::getTrackingEvents) // 每个ParcelInfo处理完成后触发请求4
            .collectList()
            .doOnError(throwable -> log.error(EXTERNAL_SERVICE_ERROR, throwable.getMessage()));
}

优化后的逻辑更简洁,同时依然严格保证请求4在1、2、3次请求完成后执行。

2. 若第1、2、3次请求未返回HTTP 200成功响应,如何阻止第4次请求执行?

首先要明确:doOnError只是错误的副作用处理(比如打日志),不会改变流的状态。要阻止请求4执行,需要让前序请求的错误正确传播,中断整个流:

  • 请求1失败:connector1.getParcelReference(parcelId)会返回错误Mono,整个流直接进入doOnError,后续所有操作(包括请求2、3、4)都不会触发。
  • 请求2或3失败:parcelEventsFor(id)会返回错误Mono,默认情况下flatMap会终止当前元素的处理,但继续处理其他ResolvedParcelId对应的请求。如果需要只要有一个请求2/3失败就终止整个流、完全阻止请求4执行,可以添加onErrorStop():
.flatMap(id -> parcelEventsFor(id)
        .map(parcel -> redactIfNecessary(parcel, id, postcode))
        .onErrorStop()) // 单个元素处理出错时,终止整个Flux

如果希望单个请求2/3失败不影响其他元素的处理,但出错的元素不触发请求4,可以用onErrorReturn返回默认值或空对象:

.flatMap(id -> parcelEventsFor(id)
        .map(parcel -> redactIfNecessary(parcel, id, postcode))
        .onErrorReturn(new ParcelInfo())) // 出错时返回默认值,跳过该元素的请求4

注意:如果你的连接器使用了onErrorResume之类的操作符将错误转为默认结果,流会继续执行,此时请求4依然会被触发——这种情况下需要根据业务需求调整连接器的错误处理逻辑,确保失败场景下错误能正确传播。

内容的提问来源于stack exchange,提问作者JsbDev

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.30 16:39:21