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

如何实现WebClient POST重试时动态修改请求头的逻辑

问题根因

你遇到的retry/retryWhen无法携带新token的问题,本质不是重试API不支持修改请求,而是请求组装逻辑把authToken的值提前固定死了:
.header("authToken", authenticationRepository.getUserkey()) 这行在你调用send()方法组装Mono的瞬间就会执行取值,第一次调用取到null,后续不管重试多少次,请求头用的都是组装时拿到的固定值,不会重新读取repository里更新后的token。

实现方案

核心是延迟请求的构造时机,让每次实际发起请求(包括首次请求、重试请求)时才实时读取最新token,同时增加单飞机制避免并发场景下重复调用登录接口,不需要递归,也不需要手动在重试逻辑里修改请求内容。

完整实现代码:

// 缓存正在执行/有效的登录请求Mono,避免并发场景重复触发登录
private final AtomicReference<Mono<String>> cachedLoginMono = new AtomicReference<>();

private <T extends APIResponse<?>> Mono<T> send(RequestData postRequest, Class<T> responseType) {
    // 用Mono.defer包裹整个请求链,每次订阅(含重试)都会重新构造请求、读取最新token
    return Mono.defer(() -> webClient
            .post()
            .uri(url)
            // 这里的header取值会在每次发请求时实时执行,不会固定为初始值
            .header("authToken", authenticationRepository.getUserkey())
            .retrieve()
            .bodyToMono(responseType)
            // 直接在flatMap里做未授权校验,比doOnSuccess抛异常更符合响应式流语义
            .flatMap(resp -> {
                if (isUnauthorized(resp)) { // 替换成你原来的未授权判断逻辑
                    return Mono.error(new RequestUnauthorized());
                }
                return Mono.just(resp);
            })
        )
        // 最多重试1次,避免登录异常时无限循环
        .onErrorResume(RequestUnauthorized.class, ex -> refreshToken().then(send(postRequest, responseType)))
        .checkpoint("api request with auth");
}

// 单飞实现的token刷新:并发场景下多个请求同时触发401时,只会发起1次登录请求
private Mono<String> refreshToken() {
    return cachedLoginMono.updateAndGet(existingMono -> {
        if (existingMono != null) {
            return existingMono;
        }
        // 实际登录逻辑
        return loginUsecase.login()
            .map(LoginResponse::getToken)
            .doOnNext(authenticationRepository::setUserkey)
            // 登录完成(成功/失败)后清空缓存,下次401可重新触发登录
            .doOnTerminate(() -> cachedLoginMono.set(null))
            // 缓存登录结果,给所有等待token的并发请求复用
            .cache();
    }).flatMap(Function.identity());
}

关键说明

  1. Mono.defer()是整个实现的核心:原来的写法在调用send()时就把请求头的值固定了,加了defer之后,每次订阅这个Mono(第一次请求、重试、多调用方订阅)都会重新执行请求构造逻辑,实时读取repository里的最新token,不需要手动修改重试时的请求内容。
  2. 单飞机制解决了并发场景的重复登录问题:如果同时有10个请求因为token过期返回未授权,不会发起10次登录请求,所有请求都会共享同一次登录的结果,等拿到新token后再各自重试,完全避免不必要的登录开销。
  3. 去掉了原来doOnSuccess里抛异常的逻辑,改成在flatMap里校验响应后主动抛异常,符合响应式流的异常传播规则,不会出现上下文丢失的问题。
  4. 如果你偏好使用retryWhen实现重试,逻辑完全一致,只要请求链被Mono.defer()包裹,重试时就会自动拿到最新token,不需要额外处理请求头。注意不要在retry的回调里调用block()方法,避免阻塞响应式线程。
  5. 建议给重试加最大次数限制(这里默认重试1次),避免登录接口异常时出现无限重试的死循环,重试后仍然未授权的话直接抛出异常交给上层业务处理即可。

补充优化:如果对接的API返回的token有明确的过期时间,可以在存储token时同步记录过期时间戳,在token即将过期时提前触发刷新,比等请求返回未授权再刷新的用户体验更好。

内容的提问来源于stack exchange,提问作者m.antkowicz

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.29 03:51:22