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

Spring Reactive API网关转发微服务错误响应异常排查

问题:API网关无法原样转发微服务的错误响应

构建包含User Service、API Gateway和Discovery Service的Spring应用时,遇到以下问题:

  • 直接调用User Service创建已注册邮箱的用户,返回正确的400错误JSON:
{
"type": "about:blank",
"title": "Bad Request",
"status": 400,
"instance": "/users",
"errors": [
"Email already registered"
]
}
  • 但通过API Gateway发起请求时,返回的却是:
An unknown error occurred (with a code 500)

问题根源

从控制台日志和代码逻辑可定位问题:

  1. WebClient错误处理有误:UserController中调用User Service时,将错误响应包装成通用RuntimeException抛出,导致业务错误被当作系统异常处理:
.onStatus(HttpStatusCode::isError, clientResponse ->
    clientResponse.bodyToMono(String.class)
        .flatMap(errorBody -> Mono.error(new RuntimeException(errorBody)))
)
  1. CircuitBreaker fallback逻辑覆盖业务错误:fallback方法捕获所有异常后,默认返回500状态响应,没有区分业务错误和服务不可用异常:
return Mono.just(ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body("An unknown error occurred"));
  1. 全局异常处理器未识别业务异常:GlobalExceptionHandler仅处理特定类型异常,通用RuntimeException被归类为未知异常,返回500。

解决方案

方案1:直接返回错误响应,避免触发fallback(推荐)

修改UserController的WebClient调用逻辑,遇到错误响应时直接返回,不抛出异常:

@PostMapping
public Mono<ResponseEntity<?>> createUser(@RequestBody CreateUserDto userDto) {
    return circuitBreaker.run(
        webClientBuilder.build()
            .post()
            .uri("http://user-service/users")
            .bodyValue(userDto)
            .retrieve()
            // 遇到错误响应时直接封装为ResponseEntity返回,不抛出异常
            .onStatus(HttpStatusCode::isError, clientResponse ->
                clientResponse.bodyToMono(String.class)
                    .map(errorBody -> ResponseEntity.status(clientResponse.statusCode()).body(errorBody))
                    .flatMap(Mono::just)
            )
            .toEntity(User.class)
            .map(userResponse -> ResponseEntity.status(userResponse.getStatusCode()).body(userResponse.getBody())),
        throwable -> fallbackMethod(throwable)
    );
}

方案2:调整fallback逻辑,区分业务错误与服务异常

修改fallback方法,仅对服务不可用类异常返回默认响应,业务错误则解析原错误体返回:

private Mono<ResponseEntity<?>> fallbackMethod(Throwable throwable) {
    log.error("Error occurred: ", throwable);
    if (throwable instanceof ClientAuthorizationRequiredException) {
        return Mono.just(ResponseEntity.status(HttpStatus.UNAUTHORIZED).body("Authorization required"));
    } else if (throwable instanceof NoFallbackAvailableException || throwable instanceof WebClientRequestException) {
        return Mono.just(ResponseEntity.status(HttpStatus.SERVICE_UNAVAILABLE).body("Service unavailable"));
    }
    // 解析业务错误体(假设错误体是JSON格式)
    if (throwable.getMessage() != null && throwable.getMessage().startsWith("{")) {
        return Mono.just(ResponseEntity.status(HttpStatus.BAD_REQUEST).body(throwable.getMessage()));
    }
    return Mono.just(ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body("An unknown error occurred"));
}

方案3:自定义异常类,增强全局异常处理

  1. 创建自定义异常类,携带状态码和错误体:
public class ServiceResponseException extends RuntimeException {
    private final HttpStatusCode statusCode;
    private final String responseBody;

    public ServiceResponseException(HttpStatusCode statusCode, String responseBody) {
        super(responseBody);
        this.statusCode = statusCode;
        this.responseBody = responseBody;
    }

    public HttpStatusCode getStatusCode() {
        return statusCode;
    }

    public String getResponseBody() {
        return responseBody;
    }
}
  1. 修改WebClient错误处理,抛出自定义异常:
.onStatus(HttpStatusCode::isError, clientResponse ->
    clientResponse.bodyToMono(String.class)
        .flatMap(errorBody -> Mono.error(new ServiceResponseException(clientResponse.statusCode(), errorBody)))
)
  1. 在GlobalExceptionHandler中添加自定义异常处理:
// 修改determineHttpStatus方法
private HttpStatusCode determineHttpStatus(Throwable throwable) {
    if (throwable instanceof ServiceResponseException) {
        return ((ServiceResponseException) throwable).getStatusCode();
    }
    // 原有其他逻辑...
}

// 修改renderErrorResponse方法
private Mono<ServerResponse> renderErrorResponse(ServerRequest request) {
    ErrorAttributeOptions options = ErrorAttributeOptions.of(ErrorAttributeOptions.Include.MESSAGE);
    Map<String, Object> errorPropertiesMap = getErrorAttributes(request, options);
    Throwable throwable = getError(request);
    HttpStatusCode httpStatus = determineHttpStatus(throwable);

    if (throwable instanceof ServiceResponseException) {
        ServiceResponseException serviceException = (ServiceResponseException) throwable;
        return ServerResponse.status(serviceException.getStatusCode())
               .contentType(MediaType.APPLICATION_JSON)
               .body(BodyInserters.fromValue(serviceException.getResponseBody()));
    }
    // 原有其他逻辑...
}

验证

修改完成后,通过API Gateway调用创建已注册邮箱的用户,即可原样返回User Service的400错误JSON响应。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.17 04:26:00