Openfeign ErrorDecoder 如何检索原始消息?

问题描述 投票:0回答:0

我正在寻找一种方法来检索由通过 FeignClient 称为 RemoteService 的微服务内引发的异常引起的原始响应。

在微服务“地图”中,我公开了一个 API 以通过 id 查找位置:

@RestController
@RequiredArgsConstructor
@RequestMapping("/api/location")
public class LocationRestController {

    private final LocationService locationService;

    @GetMapping(value = "/{id}", produces = MediaType.APPLICATION_JSON_VALUE)
    public @ResponseBody LocationDto findById(@PathVariable Long id) throws FunctionalException {
        return locationService.findByIdToDto(id);
    }

}

如果找到,API 将返回 LocationDto,否则,它将抛出 FunctionException,该异常将由 RestControllerAdvice 处理,以便返回 ExceptionResponse :

@RestControllerAdvice
public class ExceptionRestController {

    @ResponseBody
    @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
    @ExceptionHandler(value = { FunctionalException.class })
    public ExceptionResponse handleFunctionalException(FunctionalException exception) {
        return new ExceptionResponse(exception, Map.of("uniqueIdentifier", exception.getUniqueIdentifier()));
    }

    @ResponseBody
    @ExceptionHandler(Exception.class)
    @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
    public ExceptionResponse handleAllUncaughtException(Exception exception) {
        return new ExceptionResponse(exception);
    }

}

异常响应是:

@Log4j2
@Data
public class ExceptionResponse {

    private final LocalDateTime timestamp;

    private final String status;

    private final String error;

    private String uniqueIdentifier = "";
    
    private final String message;

    private final String stackTrace;
    
    public ExceptionResponse(Exception exception) {
        timestamp = LocalDateTime.now();
        status = String.valueOf(HttpStatus.INTERNAL_SERVER_ERROR.value());
        error = HttpStatus.INTERNAL_SERVER_ERROR.getReasonPhrase();
        message = exception.getMessage();
        stackTrace = ExceptionUtils.getStackTrace(exception);
        log.error(stackTrace);
    }
    
    public ExceptionResponse(Exception exception, Map<String, String> fieldValue) {
        timestamp = LocalDateTime.now();
        status = fieldValue.getOrDefault("status", String.valueOf(HttpStatus.INTERNAL_SERVER_ERROR.value()));
        error = fieldValue.getOrDefault("error", HttpStatus.INTERNAL_SERVER_ERROR.getReasonPhrase());
        uniqueIdentifier = fieldValue.getOrDefault("uniqueIdentifier", "");
        message = fieldValue.getOrDefault("message", exception.getMessage());
        stackTrace = ExceptionUtils.getStackTrace(exception);
        log.error(stackTrace);
    }

    @JsonCreator
    public ExceptionResponse(LocalDateTime timestamp, String status, String error, String uniqueIdentifier,
            String message, String stackTrace) {
        this.timestamp = timestamp;
        this.status = status;
        this.error = error;
        this.uniqueIdentifier = uniqueIdentifier;
        this.message = message;
        this.stackTrace = stackTrace;
    }

}

在微服务“超市”中,我必须使用 OpenFeign 调用微服务“地图”的公开 API:

@FeignClient(name = "mapsClient", url = "http://localhost:9888", configuration = ClientConfiguration.class)
public interface MapsRemoteService {

    @GetMapping(value = "/api/location/{id}")
    LocationDto findLocationById(@PathVariable("id") Long id);

}

现在,当使用数据库中不存在的 id 调用此 MapsRemoteService.findLocationById(id) 时,我想检索 ExceptionResponse

所以我尝试创建一个 ErrorDecoder:

客户端配置:

@Configuration
public class ClientConfiguration {

    @Bean
    public OkHttpClient client() {
        return new OkHttpClient();
    }

    @Bean
    Logger.Level feignLoggerLevel() {
        return Logger.Level.FULL;
    }
    
    @Bean
    public ErrorDecoder errorDecoder() {
        return new ClientErrorDecoder();
    }

}

错误解码器:

public class ClientErrorDecoder implements ErrorDecoder {

    @Override
    public Exception decode(String methodKey, Response response) {
        ObjectMapper mapper = new ObjectMapper();
        RemoteServiceException remoteServiceException = null;
        
        // Attempt 1
        try (InputStream inputStream = response.body().asInputStream())  {
            ExceptionResponse exceptionResponse = mapper.readValue(inputStream, ExceptionResponse.class);
            remoteServiceException = buildRemoteServiceException(exceptionResponse);
        } catch (IOException e) {

        }
        
        // Attempt 2
        try (Reader reader = response.body().asReader())  {
            ExceptionResponse exceptionResponse = mapper.readValue(reader, ExceptionResponse.class);
            remoteServiceException = buildRemoteServiceException(exceptionResponse);
        } catch (IOException e) {

        }
        
        // Attempt 3
        try {
            String body = response.body().toString();
            ExceptionResponse exceptionResponse = mapper.readValue(body, ExceptionResponse.class);
            remoteServiceException = buildRemoteServiceException(exceptionResponse);
        } catch (IOException e) {

        }
        
        if(remoteServiceException == null){
            return new ErrorDecoder.Default().decode(methodKey, response);
        }

        return remoteServiceException;
    }
    
    private static RemoteServiceException buildRemoteServiceException(ExceptionResponse exceptionResponse) {
        return new RemoteServiceException(exceptionResponse.getMessage(), exceptionResponse.getUniqueIdentifier());
    }

}

我尝试了在互联网上找到的所有方法,但没有任何效果。

奇怪的是我将inputStream作为临时文件保存到资源文件夹中。这是正确的。

我用这段代码来编写文件:

try (InputStream inputStream = response.body().asInputStream())  {
    File targetFile = new File("src/main/resources/targetFile.tmp");

    java.nio.file.Files.copy(
              inputStream, 
              targetFile.toPath(), 
              StandardCopyOption.REPLACE_EXISTING);

    IOUtils.closeQuietly(inputStream);
} catch (IOException e) {
    // return new ErrorDecoder.Default().decode(methodKey, response);
}

这是文件的内容:

{
    "timestamp":"2023-07-25T22:38:48.530815",
    "status":"500",
    "error":"Internal Server Error",
    "uniqueIdentifier":"bc4bd3ae-2d40-4863-9e9f-6b10ecece27d",
    "message":"No Location found with the id: 10",
    "stackTrace":"7711 characters avoided for readability"
}

它完美地代表了具有 6 个字段的 ExceptionResponse 类。

java jackson spring-cloud-feign feign openfeign
© www.soinside.com 2019 - 2024. All rights reserved.