gpt4 book ai didi

spring-cloud-feign - FeignClient 抛出而不是返回带有错误 http 状态的 ResponseEntity

转载 作者:行者123 更新时间:2023-12-04 07:58:10 35 4
gpt4 key购买 nike

当我使用 ResponseEntity<T> 作为我的 FeignClient 方法的返回值时,我希望它返回一个状态为 400 的 ResponseEntity 如果它是服务器返回的。但它会抛出一个 FeignException

如何从 FeignClient 获得正确的 ResponseEntity 而不是 Exception ?

这是我的 FeignClient:

@FeignClient(value = "uaa", configuration = OauthFeignClient.Conf.class)
public interface OauthFeignClient {

@RequestMapping(
value = "/oauth/token",
method = RequestMethod.POST,
consumes = MULTIPART_FORM_DATA_VALUE,
produces = APPLICATION_JSON_VALUE)
ResponseEntity<OauthTokenResponse> token(Map<String, ?> formParams);

class Conf {

@Value("${oauth.client.password}")
String oauthClientPassword;

@Bean
public Encoder feignFormEncoder() {
return new SpringFormEncoder();
}

@Bean
public Contract feignContract() {
return new SpringMvcContract();
}

@Bean
public BasicAuthRequestInterceptor basicAuthRequestInterceptor() {
return new BasicAuthRequestInterceptor("web-client", oauthClientPassword);
}

}
}

以及我如何使用它:

@PostMapping("/login")
public ResponseEntity<LoginTokenPair> getTokens(@RequestBody @Valid LoginRequest userCredentials) {
Map<String, String> formData = new HashMap<>();

ResponseEntity<OauthTokenResponse> response = oauthFeignClient.token(formData);

//code never reached if contacted service returns a 400
...
}

最佳答案

顺便说一下,我之前给出的解决方案有效,但我最初的意图是个坏主意:错误是错误, 不应该在名义流量上处理 。像 Feign 那样抛出异常,并使用 @ExceptionHandler 处理它是 Spring MVC 世界中更好的方法。

所以有两个解决方案:

  • @ExceptionHandler
  • 添加一个 FeignException
  • 使用 FeignClient 配置 ErrorDecoder 以将错误转换为您的业务层知道(并且已经为其提供 @ExceptionHandler)

  • 我更喜欢第二种解决方案,因为收到的错误消息结构可能会从一个客户端更改为另一个客户端,因此您可以使用每个客户端的错误解码从这些错误中提取更细粒度的数据。

    FeignClient with conf (抱歉 feign-form 引入的噪音)

    @FeignClient(value = "uaa", configuration = OauthFeignClient.Config.class)
    public interface OauthFeignClient {

    @RequestMapping(
    value = "/oauth/token",
    method = RequestMethod.POST,
    consumes = MULTIPART_FORM_DATA_VALUE,
    produces = APPLICATION_JSON_VALUE)
    DefaultOAuth2AccessToken token(Map<String, ?> formParams);

    @Configuration
    class Config {

    @Value("${oauth.client.password}")
    String oauthClientPassword;

    @Autowired
    private ObjectFactory<HttpMessageConverters> messageConverters;

    @Bean
    public Encoder feignFormEncoder() {
    return new SpringFormEncoder(new SpringEncoder(messageConverters));
    }

    @Bean
    public Decoder springDecoder() {
    return new ResponseEntityDecoder(new SpringDecoder(messageConverters));
    }

    @Bean
    public Contract feignContract() {
    return new SpringMvcContract();
    }

    @Bean
    public BasicAuthRequestInterceptor basicAuthRequestInterceptor() {
    return new BasicAuthRequestInterceptor("web-client", oauthClientPassword);
    }

    @Bean
    public ErrorDecoder uaaErrorDecoder(Decoder decoder) {
    return (methodKey, response) -> {
    try {
    OAuth2Exception uaaException = (OAuth2Exception) decoder.decode(response, OAuth2Exception.class);
    return new SroException(
    uaaException.getHttpErrorCode(),
    uaaException.getOAuth2ErrorCode(),
    Arrays.asList(uaaException.getSummary()));

    } catch (Exception e) {
    return new SroException(
    response.status(),
    "Authorization server responded with " + response.status() + " but failed to parse error payload",
    Arrays.asList(e.getMessage()));
    }
    };
    }
    }
    }

    常见业务异常

    public class SroException extends RuntimeException implements Serializable {
    public final int status;

    public final List<String> errors;

    public SroException(final int status, final String message, final Collection<String> errors) {
    super(message);
    this.status = status;
    this.errors = Collections.unmodifiableList(new ArrayList<>(errors));
    }

    @Override
    public boolean equals(Object o) {
    if (this == o) return true;
    if (!(o instanceof SroException)) return false;
    SroException sroException = (SroException) o;
    return status == sroException.status &&
    Objects.equals(super.getMessage(), sroException.getMessage()) &&
    Objects.equals(errors, sroException.errors);
    }

    @Override
    public int hashCode() {
    return Objects.hash(status, super.getMessage(), errors);
    }
    }

    错误处理程序 (从 ResponseEntityExceptionHandler 扩展中提取)

    @ExceptionHandler({SroException.class})
    public ResponseEntity<Object> handleSroException(SroException ex) {
    return new SroError(ex).toResponse();
    }

    错误响应 DTO

    @XmlRootElement
    public class SroError implements Serializable {
    public final int status;

    public final String message;

    public final List<String> errors;

    public SroError(final int status, final String message, final Collection<String> errors) {
    this.status = status;
    this.message = message;
    this.errors = Collections.unmodifiableList(new ArrayList<>(errors));
    }

    public SroError(final SroException e) {
    this.status = e.status;
    this.message = e.getMessage();
    this.errors = Collections.unmodifiableList(e.errors);
    }

    protected SroError() {
    this.status = -1;
    this.message = null;
    this.errors = null;
    }

    public ResponseEntity<Object> toResponse() {
    return new ResponseEntity(this, HttpStatus.valueOf(this.status));
    }

    public ResponseEntity<Object> toResponse(HttpHeaders headers) {
    return new ResponseEntity(this, headers, HttpStatus.valueOf(this.status));
    }

    @Override
    public boolean equals(Object o) {
    if (this == o) return true;
    if (!(o instanceof SroError)) return false;
    SroError sroException = (SroError) o;
    return status == sroException.status &&
    Objects.equals(message, sroException.message) &&
    Objects.equals(errors, sroException.errors);
    }

    @Override
    public int hashCode() {

    return Objects.hash(status, message, errors);
    }
    }

    Feign 客户端使用 注意到由于 @ControllerAdvice@ExceptionHandler({SroException.class}),错误是如何透明处理的(没有尝试/捕获)

    @RestController
    @RequestMapping("/uaa")
    public class AuthenticationController {
    private static final BearerToken REVOCATION_TOKEN = new BearerToken("", 0L);

    private final OauthFeignClient oauthFeignClient;

    private final int refreshTokenValidity;

    @Autowired
    public AuthenticationController(
    OauthFeignClient oauthFeignClient,
    @Value("${oauth.ttl.refresh-token}") int refreshTokenValidity) {
    this.oauthFeignClient = oauthFeignClient;
    this.refreshTokenValidity = refreshTokenValidity;
    }

    @PostMapping("/login")
    public ResponseEntity<LoginTokenPair> getTokens(@RequestBody @Valid LoginRequest userCredentials) {
    Map<String, String> formData = new HashMap<>();
    formData.put("grant_type", "password");
    formData.put("client_id", "web-client");
    formData.put("username", userCredentials.username);
    formData.put("password", userCredentials.password);
    formData.put("scope", "openid");

    DefaultOAuth2AccessToken response = oauthFeignClient.token(formData);
    return ResponseEntity.ok(new LoginTokenPair(
    new BearerToken(response.getValue(), response.getExpiresIn()),
    new BearerToken(response.getRefreshToken().getValue(), refreshTokenValidity)));
    }

    @PostMapping("/logout")
    public ResponseEntity<LoginTokenPair> revokeTokens() {
    return ResponseEntity
    .ok(new LoginTokenPair(REVOCATION_TOKEN, REVOCATION_TOKEN));
    }

    @PostMapping("/refresh")
    public ResponseEntity<BearerToken> refreshToken(@RequestHeader("refresh_token") String refresh_token) {
    Map<String, String> formData = new HashMap<>();
    formData.put("grant_type", "refresh_token");
    formData.put("client_id", "web-client");
    formData.put("refresh_token", refresh_token);
    formData.put("scope", "openid");

    DefaultOAuth2AccessToken response = oauthFeignClient.token(formData);
    return ResponseEntity.ok(new BearerToken(response.getValue(), response.getExpiresIn()));
    }
    }

    关于spring-cloud-feign - FeignClient 抛出而不是返回带有错误 http 状态的 ResponseEntity,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/48441738/

    35 4 0
    Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
    广告合作:1813099741@qq.com 6ren.com