我正在寻找一种方法来检索由异常引起的原始响应,该异常已通过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,否则,它将抛出由RestControllerAdvice处理的FunctionalException,以便返回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 {
@JsonProperty
@JsonSerialize(using = LocalDateTimeSerializer.class)
@JsonDeserialize(using = LocalDateTimeDeserializer.class)
private final LocalDateTime timestamp;
@JsonProperty
private final String status;
@JsonProperty
private final String error;
@JsonProperty
private String uniqueIdentifier = "";
@JsonProperty
private final String message;
@JsonProperty
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);
}
现在,当用DB中不存在的id调用这个MapsMoteService.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。
所以在使用时似乎出现了问题
mapper.readValue(inputStream, ExceptionResponse.class);