具有自定义 JSON 响应的 Feign 错误解码器

Ste*_*ann 6 spring-boot spring-cloud-feign feign

我有一个 API,它将以自定义响应消息的形式返回失败,同时仍然发送 Http 200 响应。例子:

Status: 200
Body: {
   "code":404,
   "message":"Data not found",
   "data":{},
   "status":"ERROR"
}
Run Code Online (Sandbox Code Playgroud)

我当前的 ErrorDecoder 实现:

public class ApiFeignErrorDecoder implements ErrorDecoder {

    private final Gson gson;

    public ApiFeignErrorDecoder(Gson gson) {
        this.gson = gson;
    }

    @Override
    public Exception decode(String methodKey, Response response) {

        try {
            ApiResponse apiResponse = gson.fromJson(response.body().asReader(), BaseApiResponse.class);
            ResponseStatus status = apiResponse.getStatus();
            switch (status) {
                case SUCCESS:
                    return null;
                case FAIL:
                    return new Exception("Failure message recorded");
                case ERROR:
                    return new Exception("Error message recorded");
                default:
                    return new Exception("No suitable status found.");
            }
        } catch (IOException e) {
            e.printStackTrace();
            return new ErrorDecoder.Default().decode(methodKey, response);
        }
    }

}
Run Code Online (Sandbox Code Playgroud)

我遇到的问题是 Feign/Spring 仅当 HttpStatus > 300 时才会切换到 ErrorDecoder。错误解码器的 JavaDocs 也描述了此限制:

“Response.status() 不在 2xx 范围内的错误处理响应被归类为错误,由 ErrorDecoder 处理。也就是说,某些 RPC api 即使在 200 状态下也会返回 Response.body() 中定义的错误。例如,在 DynECT api 中,作业仍在运行的情况会返回 200 状态,并以 json 编码。当发生此类情况时,您应该引发特定于应用程序的异常(可能是可重试的)。

我现在的问题是我到底可以实现什么/如何实现这些,或者是否有一种方法可以扩展 ErrorDecoder 以便我能够处理这些错误消息。我认为我应该能够将它们放入解码器中,甚至实现/覆盖 HttpClient,但我不确定正确/最好的方法是什么。

Hei*_*erg 5

SpringDecoderErrorDecoder一样,您可以使用它编写自定义逻辑来处理成功响应。

private class ResponseDecoder extends SpringDecoder {

    public ResponseDecoder(ObjectFactory<HttpMessageConverters> messageConverters) {
        super(messageConverters);
    }

    @Override
    public Object decode(Response response, Type type) throws IOException, FeignException {
        try {
            ApiResponse apiResponse = gson.fromJson(response.body().asReader(), BaseApiResponse.class);
            ResponseStatus status = apiResponse.getStatus();
            switch (status) {
                case SUCCESS:
                    return null;
                case FAIL:
                    throw new FeignException(FAIL, "Failure message recorded");
                case ERROR:
                    throw new FeignException(ERROR, "Error message recorded");
                default:
                    throw new FeignException(YOUR_DEFAULT_ERROR_CODE, "No suitable status found.");
            }
        } catch (IOException e) {
            e.printStackTrace();
            return null; // return accordingly to your use-case
        }
    }

}
Run Code Online (Sandbox Code Playgroud)

假客户端接口:

@FeignClient(value = "foo", configuration = FooClientConfig.class)
public interface FooClient {
    //Your mappings
}
Run Code Online (Sandbox Code Playgroud)

Feign 客户端自定义配置:

@Configuration
public class FooClientConfig {

    @Bean
    public Decoder feignDecoder() {
        // if you're using gson, add gson converter here instead
        HttpMessageConverter jacksonConverter = new MappingJackson2HttpMessageConverter(customObjectMapper());
        ObjectFactory<HttpMessageConverters> objectFactory = () -> new HttpMessageConverters(jacksonConverter);
        return new ResponseEntityDecoder(new ResponseDecoder(objectFactory));
    }

    public ObjectMapper customObjectMapper(){
        ObjectMapper objectMapper = new ObjectMapper();
        //Customize as much as you want
        return objectMapper;
    }
}
Run Code Online (Sandbox Code Playgroud)