|
|
@@ -1,13 +1,27 @@
|
|
|
package com.sckw.transport.api.feign;
|
|
|
|
|
|
+import com.fasterxml.jackson.databind.DeserializationFeature;
|
|
|
+import com.fasterxml.jackson.databind.ObjectMapper;
|
|
|
+import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
|
|
|
import feign.Logger;
|
|
|
import feign.Request;
|
|
|
+import feign.Response;
|
|
|
import feign.Retryer;
|
|
|
+import feign.codec.Decoder;
|
|
|
import feign.codec.ErrorDecoder;
|
|
|
import lombok.extern.slf4j.Slf4j;
|
|
|
+import org.springframework.beans.factory.ObjectFactory;
|
|
|
+import org.springframework.boot.autoconfigure.http.HttpMessageConverters;
|
|
|
+import org.springframework.cloud.openfeign.support.ResponseEntityDecoder;
|
|
|
+import org.springframework.cloud.openfeign.support.SpringDecoder;
|
|
|
import org.springframework.context.annotation.Bean;
|
|
|
import org.springframework.context.annotation.Configuration;
|
|
|
+import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter;
|
|
|
+import org.springframework.util.StreamUtils;
|
|
|
|
|
|
+import java.io.IOException;
|
|
|
+import java.lang.reflect.Type;
|
|
|
+import java.nio.charset.StandardCharsets;
|
|
|
import java.util.concurrent.TimeUnit;
|
|
|
|
|
|
/**
|
|
|
@@ -56,9 +70,76 @@ public class VehicleTraceFeignConfig {
|
|
|
@Bean
|
|
|
public ErrorDecoder errorDecoder() {
|
|
|
return (methodKey, response) -> {
|
|
|
- log.error("车辆轨迹服务调用失败, 方法: {}, 状态码: {}",
|
|
|
- methodKey, response.status());
|
|
|
- return new RuntimeException("车辆轨迹服务调用失败: " + response.reason());
|
|
|
+ String responseBody = "";
|
|
|
+ try {
|
|
|
+ if (response.body() != null) {
|
|
|
+ responseBody = new String(StreamUtils.copyToByteArray(response.body().asInputStream()), StandardCharsets.UTF_8);
|
|
|
+ }
|
|
|
+ } catch (IOException e) {
|
|
|
+ log.warn("读取响应体失败", e);
|
|
|
+ }
|
|
|
+ log.error("车辆轨迹服务调用失败, 方法: {}, 状态码: {}, 响应体: {}",
|
|
|
+ methodKey, response.status(), responseBody);
|
|
|
+ return new RuntimeException("车辆轨迹服务调用失败: " + response.reason() + ", 响应: " + responseBody);
|
|
|
+ };
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 自定义 Feign 解码器,支持 Java 8 时间类型(LocalDateTime 等)
|
|
|
+ * 支持 ISO 8601 格式:2025-12-05T00:00:00.000+00:00
|
|
|
+ */
|
|
|
+ @Bean
|
|
|
+ public Decoder feignDecoder() {
|
|
|
+ ObjectMapper objectMapper = new ObjectMapper();
|
|
|
+ // 注册 Java 8 时间模块(自动支持 ISO 8601 格式)
|
|
|
+ objectMapper.registerModule(new JavaTimeModule());
|
|
|
+ // 禁用将日期写为时间戳(使用 ISO 8601 字符串格式)
|
|
|
+ objectMapper.disable(com.fasterxml.jackson.databind.SerializationFeature.WRITE_DATES_AS_TIMESTAMPS);
|
|
|
+ // 忽略未知属性,避免外部服务返回多余字段导致解析失败
|
|
|
+ objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
|
|
|
+ // 允许空对象
|
|
|
+ objectMapper.configure(DeserializationFeature.ACCEPT_EMPTY_STRING_AS_NULL_OBJECT, true);
|
|
|
+ // 允许单值作为数组
|
|
|
+ objectMapper.configure(DeserializationFeature.ACCEPT_SINGLE_VALUE_AS_ARRAY, true);
|
|
|
+ // 允许空值
|
|
|
+ objectMapper.configure(DeserializationFeature.ACCEPT_EMPTY_ARRAY_AS_NULL_OBJECT, true);
|
|
|
+
|
|
|
+ MappingJackson2HttpMessageConverter converter = new MappingJackson2HttpMessageConverter(objectMapper);
|
|
|
+ ObjectFactory<HttpMessageConverters> objectFactory = () -> new HttpMessageConverters(converter);
|
|
|
+
|
|
|
+ // 包装解码器,添加错误日志
|
|
|
+ Decoder defaultDecoder = new ResponseEntityDecoder(new SpringDecoder(objectFactory));
|
|
|
+ return new Decoder() {
|
|
|
+ @Override
|
|
|
+ public Object decode(Response response, Type type) throws IOException {
|
|
|
+ // 先保存响应体内容,以便在解码失败时记录日志
|
|
|
+ String responseBody = "";
|
|
|
+ byte[] bodyBytes = null;
|
|
|
+ if (response.body() != null) {
|
|
|
+ try {
|
|
|
+ bodyBytes = StreamUtils.copyToByteArray(response.body().asInputStream());
|
|
|
+ responseBody = new String(bodyBytes, StandardCharsets.UTF_8);
|
|
|
+ } catch (IOException ex) {
|
|
|
+ log.warn("读取响应体失败", ex);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ try {
|
|
|
+ // 使用保存的响应体重新构建 Response,因为流已经被读取
|
|
|
+ if (bodyBytes != null) {
|
|
|
+ Response rebuiltResponse = response.toBuilder()
|
|
|
+ .body(bodyBytes)
|
|
|
+ .build();
|
|
|
+ return defaultDecoder.decode(rebuiltResponse, type);
|
|
|
+ } else {
|
|
|
+ return defaultDecoder.decode(response, type);
|
|
|
+ }
|
|
|
+ } catch (Exception e) {
|
|
|
+ log.error("Feign 解码失败, 类型: {}, 状态码: {}, 响应体: {}, 错误: {}",
|
|
|
+ type, response.status(), responseBody, e.getMessage(), e);
|
|
|
+ throw e;
|
|
|
+ }
|
|
|
+ }
|
|
|
};
|
|
|
}
|
|
|
}
|