From b30396d1493fa24550bebc2eb913f491e85418d9 Mon Sep 17 00:00:00 2001 From: egd Date: Wed, 20 Aug 2025 18:07:58 +0800 Subject: [PATCH 1/2] =?UTF-8?q?fix(feign):=20Feign=20=E9=BB=98=E8=AE=A4?= =?UTF-8?q?=E7=9A=84=E8=A7=A3=E7=A0=81=E5=99=A8=E6=9C=AA=E4=BD=BF=E7=94=A8?= =?UTF-8?q?=E5=85=A8=E5=B1=80=20HttpMessageConverters=EF=BC=8C=E5=AF=BC?= =?UTF-8?q?=E8=87=B4LocalDateTime=20=E4=B8=8D=E8=83=BD=E4=BB=8E=E6=AF=AB?= =?UTF-8?q?=E7=A7=92=E6=97=B6=E9=97=B4=E6=88=B3=E5=8F=8D=E5=BA=8F=E5=88=97?= =?UTF-8?q?=E5=8C=96=E3=80=82=E6=94=B9=E4=B8=BA=20SpringDecoder=20+=20Resp?= =?UTF-8?q?onseEntityDecoder=EF=BC=8C=E5=A4=8D=E7=94=A8=E5=AE=B9=E5=99=A8?= =?UTF-8?q?=E9=87=8C=E7=9A=84=20ObjectMapper=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../config/YudaoJacksonAutoConfiguration.java | 67 +++++++++++++------ 1 file changed, 46 insertions(+), 21 deletions(-) diff --git a/yudao-framework/yudao-spring-boot-starter-web/src/main/java/cn/iocoder/yudao/framework/jackson/config/YudaoJacksonAutoConfiguration.java b/yudao-framework/yudao-spring-boot-starter-web/src/main/java/cn/iocoder/yudao/framework/jackson/config/YudaoJacksonAutoConfiguration.java index c62f0a030..b069c0a9c 100644 --- a/yudao-framework/yudao-spring-boot-starter-web/src/main/java/cn/iocoder/yudao/framework/jackson/config/YudaoJacksonAutoConfiguration.java +++ b/yudao-framework/yudao-spring-boot-starter-web/src/main/java/cn/iocoder/yudao/framework/jackson/config/YudaoJacksonAutoConfiguration.java @@ -1,10 +1,10 @@ package cn.iocoder.yudao.framework.jackson.config; -import cn.hutool.core.collection.CollUtil; import cn.iocoder.yudao.framework.common.util.json.JsonUtils; import cn.iocoder.yudao.framework.common.util.json.databind.NumberSerializer; import cn.iocoder.yudao.framework.common.util.json.databind.TimestampLocalDateTimeDeserializer; import cn.iocoder.yudao.framework.common.util.json.databind.TimestampLocalDateTimeSerializer; +import com.fasterxml.jackson.databind.Module; import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.module.SimpleModule; import com.fasterxml.jackson.datatype.jsr310.deser.LocalDateDeserializer; @@ -13,40 +13,65 @@ import com.fasterxml.jackson.datatype.jsr310.ser.LocalDateSerializer; import com.fasterxml.jackson.datatype.jsr310.ser.LocalTimeSerializer; import lombok.extern.slf4j.Slf4j; import org.springframework.boot.autoconfigure.AutoConfiguration; +import org.springframework.boot.autoconfigure.jackson.Jackson2ObjectMapperBuilderCustomizer; import org.springframework.context.annotation.Bean; import java.time.LocalDate; import java.time.LocalDateTime; import java.time.LocalTime; -import java.util.List; @AutoConfiguration @Slf4j public class YudaoJacksonAutoConfiguration { + /** + * 初始化全局 JsonUtils,直接使用主 ObjectMapper + */ @Bean @SuppressWarnings("InstantiationOfUtilityClass") - public JsonUtils jsonUtils(List objectMappers) { - // 1.1 创建 SimpleModule 对象 - SimpleModule simpleModule = new SimpleModule(); - simpleModule - // 新增 Long 类型序列化规则,数值超过 2^53-1,在 JS 会出现精度丢失问题,因此 Long 自动序列化为字符串类型 - .addSerializer(Long.class, NumberSerializer.INSTANCE) - .addSerializer(Long.TYPE, NumberSerializer.INSTANCE) - .addSerializer(LocalDate.class, LocalDateSerializer.INSTANCE) - .addDeserializer(LocalDate.class, LocalDateDeserializer.INSTANCE) - .addSerializer(LocalTime.class, LocalTimeSerializer.INSTANCE) - .addDeserializer(LocalTime.class, LocalTimeDeserializer.INSTANCE) - // 新增 LocalDateTime 序列化、反序列化规则,使用 Long 时间戳 - .addSerializer(LocalDateTime.class, TimestampLocalDateTimeSerializer.INSTANCE) - .addDeserializer(LocalDateTime.class, TimestampLocalDateTimeDeserializer.INSTANCE); - // 1.2 注册到 objectMapper - objectMappers.forEach(objectMapper -> objectMapper.registerModule(simpleModule)); - - // 2. 设置 objectMapper 到 JsonUtils - JsonUtils.init(CollUtil.getFirst(objectMappers)); + public JsonUtils jsonUtils(ObjectMapper objectMapper) { + JsonUtils.init(objectMapper); log.info("[init][初始化 JsonUtils 成功]"); return new JsonUtils(); } + /** + * 从 Builder 源头定制(关键:使用 *ByType,避免 handledType 要求) + */ + @Bean + public Jackson2ObjectMapperBuilderCustomizer ldtEpochMillisCustomizer() { + return builder -> builder + // Long -> String + .serializerByType(Long.class, NumberSerializer.INSTANCE) + .serializerByType(Long.TYPE, NumberSerializer.INSTANCE) + // LocalDate / LocalTime + .serializerByType(LocalDate.class, LocalDateSerializer.INSTANCE) + .deserializerByType(LocalDate.class, LocalDateDeserializer.INSTANCE) + .serializerByType(LocalTime.class, LocalTimeSerializer.INSTANCE) + .deserializerByType(LocalTime.class, LocalTimeDeserializer.INSTANCE) + // LocalDateTime < - > EpochMillis + .serializerByType(LocalDateTime.class, TimestampLocalDateTimeSerializer.INSTANCE) + .deserializerByType(LocalDateTime.class, TimestampLocalDateTimeDeserializer.INSTANCE); + } + + /** + * 以 Bean 形式暴露 Module(Boot 会自动注册到所有 ObjectMapper) + */ + @Bean + public Module timestampSupportModuleBean() { + SimpleModule m = new SimpleModule("TimestampSupportModule"); + // Long -> String,避免前端精度丢失 + m.addSerializer(Long.class, NumberSerializer.INSTANCE); + m.addSerializer(Long.TYPE, NumberSerializer.INSTANCE); + // LocalDate / LocalTime + m.addSerializer(LocalDate.class, LocalDateSerializer.INSTANCE); + m.addDeserializer(LocalDate.class, LocalDateDeserializer.INSTANCE); + m.addSerializer(LocalTime.class, LocalTimeSerializer.INSTANCE); + m.addDeserializer(LocalTime.class, LocalTimeDeserializer.INSTANCE); + // LocalDateTime < - > EpochMillis + m.addSerializer(LocalDateTime.class, TimestampLocalDateTimeSerializer.INSTANCE); + m.addDeserializer(LocalDateTime.class, TimestampLocalDateTimeDeserializer.INSTANCE); + return m; + } + } From 35d62571fd9475be8648c8244ba70712c1040c8c Mon Sep 17 00:00:00 2001 From: egd Date: Thu, 21 Aug 2025 15:54:47 +0800 Subject: [PATCH 2/2] =?UTF-8?q?fix(jackson):=20=E7=BB=9F=E4=B8=80=20LocalD?= =?UTF-8?q?ateTime=20=E6=AF=AB=E7=A7=92=E6=97=B6=E9=97=B4=E6=88=B3?= =?UTF-8?q?=E7=BC=96=E8=A7=A3=E7=A0=81=EF=BC=8C=E6=B6=88=E9=99=A4=20Feign/?= =?UTF-8?q?WebFlux=20=E7=9A=84=20ObjectMapper=20=E6=97=B6=E5=BA=8F?= =?UTF-8?q?=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../config/YudaoJacksonAutoConfiguration.java | 29 +++--- .../GatewayJacksonAutoConfiguration.java | 92 +++++++++++++++++++ .../jackson/JacksonAutoConfiguration.java | 51 ---------- 3 files changed, 107 insertions(+), 65 deletions(-) create mode 100644 yudao-gateway/src/main/java/cn/iocoder/yudao/gateway/jackson/GatewayJacksonAutoConfiguration.java delete mode 100644 yudao-gateway/src/main/java/cn/iocoder/yudao/gateway/jackson/JacksonAutoConfiguration.java diff --git a/yudao-framework/yudao-spring-boot-starter-web/src/main/java/cn/iocoder/yudao/framework/jackson/config/YudaoJacksonAutoConfiguration.java b/yudao-framework/yudao-spring-boot-starter-web/src/main/java/cn/iocoder/yudao/framework/jackson/config/YudaoJacksonAutoConfiguration.java index b069c0a9c..280f8da34 100644 --- a/yudao-framework/yudao-spring-boot-starter-web/src/main/java/cn/iocoder/yudao/framework/jackson/config/YudaoJacksonAutoConfiguration.java +++ b/yudao-framework/yudao-spring-boot-starter-web/src/main/java/cn/iocoder/yudao/framework/jackson/config/YudaoJacksonAutoConfiguration.java @@ -14,34 +14,24 @@ import com.fasterxml.jackson.datatype.jsr310.ser.LocalTimeSerializer; import lombok.extern.slf4j.Slf4j; import org.springframework.boot.autoconfigure.AutoConfiguration; import org.springframework.boot.autoconfigure.jackson.Jackson2ObjectMapperBuilderCustomizer; +import org.springframework.boot.autoconfigure.jackson.JacksonAutoConfiguration; import org.springframework.context.annotation.Bean; import java.time.LocalDate; import java.time.LocalDateTime; import java.time.LocalTime; -@AutoConfiguration +@AutoConfiguration(after = JacksonAutoConfiguration.class) @Slf4j public class YudaoJacksonAutoConfiguration { - /** - * 初始化全局 JsonUtils,直接使用主 ObjectMapper - */ - @Bean - @SuppressWarnings("InstantiationOfUtilityClass") - public JsonUtils jsonUtils(ObjectMapper objectMapper) { - JsonUtils.init(objectMapper); - log.info("[init][初始化 JsonUtils 成功]"); - return new JsonUtils(); - } - /** * 从 Builder 源头定制(关键:使用 *ByType,避免 handledType 要求) */ @Bean public Jackson2ObjectMapperBuilderCustomizer ldtEpochMillisCustomizer() { return builder -> builder - // Long -> String + // Long -> Number .serializerByType(Long.class, NumberSerializer.INSTANCE) .serializerByType(Long.TYPE, NumberSerializer.INSTANCE) // LocalDate / LocalTime @@ -60,7 +50,7 @@ public class YudaoJacksonAutoConfiguration { @Bean public Module timestampSupportModuleBean() { SimpleModule m = new SimpleModule("TimestampSupportModule"); - // Long -> String,避免前端精度丢失 + // Long -> Number,避免前端精度丢失 m.addSerializer(Long.class, NumberSerializer.INSTANCE); m.addSerializer(Long.TYPE, NumberSerializer.INSTANCE); // LocalDate / LocalTime @@ -74,4 +64,15 @@ public class YudaoJacksonAutoConfiguration { return m; } + /** + * 初始化全局 JsonUtils,直接使用主 ObjectMapper + */ + @Bean + @SuppressWarnings("InstantiationOfUtilityClass") + public JsonUtils jsonUtils(ObjectMapper objectMapper) { + JsonUtils.init(objectMapper); + log.debug("[init][初始化 JsonUtils 成功]"); + return new JsonUtils(); + } + } diff --git a/yudao-gateway/src/main/java/cn/iocoder/yudao/gateway/jackson/GatewayJacksonAutoConfiguration.java b/yudao-gateway/src/main/java/cn/iocoder/yudao/gateway/jackson/GatewayJacksonAutoConfiguration.java new file mode 100644 index 000000000..c7ccadf70 --- /dev/null +++ b/yudao-gateway/src/main/java/cn/iocoder/yudao/gateway/jackson/GatewayJacksonAutoConfiguration.java @@ -0,0 +1,92 @@ +package cn.iocoder.yudao.gateway.jackson; + +import cn.iocoder.yudao.framework.common.util.json.JsonUtils; +import cn.iocoder.yudao.framework.common.util.json.databind.NumberSerializer; +import cn.iocoder.yudao.framework.common.util.json.databind.TimestampLocalDateTimeDeserializer; +import cn.iocoder.yudao.framework.common.util.json.databind.TimestampLocalDateTimeSerializer; +import com.fasterxml.jackson.databind.Module; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.module.SimpleModule; +import com.fasterxml.jackson.datatype.jsr310.deser.LocalDateDeserializer; +import com.fasterxml.jackson.datatype.jsr310.deser.LocalTimeDeserializer; +import com.fasterxml.jackson.datatype.jsr310.ser.LocalDateSerializer; +import com.fasterxml.jackson.datatype.jsr310.ser.LocalTimeSerializer; +import lombok.extern.slf4j.Slf4j; +import org.springframework.context.annotation.Configuration; +import org.springframework.boot.autoconfigure.jackson.Jackson2ObjectMapperBuilderCustomizer; +import org.springframework.boot.web.codec.CodecCustomizer; +import org.springframework.context.annotation.Bean; +import org.springframework.http.codec.json.Jackson2JsonDecoder; +import org.springframework.http.codec.json.Jackson2JsonEncoder; + +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.time.LocalTime; + +@Configuration +@Slf4j +public class GatewayJacksonAutoConfiguration { + + /** + * 从 Builder 源头定制(关键:使用 *ByType,避免 handledType 要求) + */ + @Bean + public Jackson2ObjectMapperBuilderCustomizer ldtEpochMillisCustomizer() { + return builder -> builder + // Long -> Number + .serializerByType(Long.class, NumberSerializer.INSTANCE) + .serializerByType(Long.TYPE, NumberSerializer.INSTANCE) + // LocalDate / LocalTime + .serializerByType(LocalDate.class, LocalDateSerializer.INSTANCE) + .deserializerByType(LocalDate.class, LocalDateDeserializer.INSTANCE) + .serializerByType(LocalTime.class, LocalTimeSerializer.INSTANCE) + .deserializerByType(LocalTime.class, LocalTimeDeserializer.INSTANCE) + // LocalDateTime < - > EpochMillis + .serializerByType(LocalDateTime.class, TimestampLocalDateTimeSerializer.INSTANCE) + .deserializerByType(LocalDateTime.class, TimestampLocalDateTimeDeserializer.INSTANCE); + } + + /** + * 以 Bean 形式暴露 Module(Boot 会自动注册到所有 ObjectMapper) + */ + @Bean + public Module timestampSupportModuleBean() { + SimpleModule m = new SimpleModule("TimestampSupportModule"); + // Long -> Number + m.addSerializer(Long.class, NumberSerializer.INSTANCE); + m.addSerializer(Long.TYPE, NumberSerializer.INSTANCE); + // LocalDate / LocalTime + m.addSerializer(LocalDate.class, LocalDateSerializer.INSTANCE); + m.addDeserializer(LocalDate.class, LocalDateDeserializer.INSTANCE); + m.addSerializer(LocalTime.class, LocalTimeSerializer.INSTANCE); + m.addDeserializer(LocalTime.class, LocalTimeDeserializer.INSTANCE); + // LocalDateTime < - > EpochMillis + m.addSerializer(LocalDateTime.class, TimestampLocalDateTimeSerializer.INSTANCE); + m.addDeserializer(LocalDateTime.class, TimestampLocalDateTimeDeserializer.INSTANCE); + return m; + } + + /** + * 初始化全局 JsonUtils,直接使用主 ObjectMapper + */ + @Bean + @SuppressWarnings("InstantiationOfUtilityClass") + public JsonUtils jsonUtils(ObjectMapper objectMapper) { + JsonUtils.init(objectMapper); + log.debug("[init][初始化 JsonUtils 成功]"); + return new JsonUtils(); + } + + /** + * WebFlux 场景:强制默认编解码器使用同一个 ObjectMapper + */ + @Bean + public CodecCustomizer unifyJackson(ObjectMapper om) { + return configurer -> { + Jackson2JsonDecoder decoder = new Jackson2JsonDecoder(om); + Jackson2JsonEncoder encoder = new Jackson2JsonEncoder(om); + configurer.defaultCodecs().jackson2JsonDecoder(decoder); + configurer.defaultCodecs().jackson2JsonEncoder(encoder); + }; + } +} diff --git a/yudao-gateway/src/main/java/cn/iocoder/yudao/gateway/jackson/JacksonAutoConfiguration.java b/yudao-gateway/src/main/java/cn/iocoder/yudao/gateway/jackson/JacksonAutoConfiguration.java deleted file mode 100644 index d9fcb9de2..000000000 --- a/yudao-gateway/src/main/java/cn/iocoder/yudao/gateway/jackson/JacksonAutoConfiguration.java +++ /dev/null @@ -1,51 +0,0 @@ -package cn.iocoder.yudao.gateway.jackson; - -import cn.hutool.core.collection.CollUtil; -import cn.iocoder.yudao.framework.common.util.json.JsonUtils; -import cn.iocoder.yudao.framework.common.util.json.databind.NumberSerializer; -import cn.iocoder.yudao.framework.common.util.json.databind.TimestampLocalDateTimeDeserializer; -import cn.iocoder.yudao.framework.common.util.json.databind.TimestampLocalDateTimeSerializer; -import com.fasterxml.jackson.databind.ObjectMapper; -import com.fasterxml.jackson.databind.module.SimpleModule; -import com.fasterxml.jackson.datatype.jsr310.deser.LocalDateDeserializer; -import com.fasterxml.jackson.datatype.jsr310.deser.LocalTimeDeserializer; -import com.fasterxml.jackson.datatype.jsr310.ser.LocalDateSerializer; -import com.fasterxml.jackson.datatype.jsr310.ser.LocalTimeSerializer; -import lombok.extern.slf4j.Slf4j; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; - -import java.time.LocalDate; -import java.time.LocalDateTime; -import java.time.LocalTime; -import java.util.List; - -@Configuration -@Slf4j -public class JacksonAutoConfiguration { - - @Bean - public JsonUtils jsonUtils(List objectMappers) { - // 1.1 创建 SimpleModule 对象 - SimpleModule simpleModule = new SimpleModule(); - simpleModule - // 新增 Long 类型序列化规则,数值超过 2^53-1,在 JS 会出现精度丢失问题,因此 Long 自动序列化为字符串类型 - .addSerializer(Long.class, NumberSerializer.INSTANCE) - .addSerializer(Long.TYPE, NumberSerializer.INSTANCE) - .addSerializer(LocalDate.class, LocalDateSerializer.INSTANCE) - .addDeserializer(LocalDate.class, LocalDateDeserializer.INSTANCE) - .addSerializer(LocalTime.class, LocalTimeSerializer.INSTANCE) - .addDeserializer(LocalTime.class, LocalTimeDeserializer.INSTANCE) - // 新增 LocalDateTime 序列化、反序列化规则,使用 Long 时间戳 - .addSerializer(LocalDateTime.class, TimestampLocalDateTimeSerializer.INSTANCE) - .addDeserializer(LocalDateTime.class, TimestampLocalDateTimeDeserializer.INSTANCE); - // 1.2 注册到 objectMapper - objectMappers.forEach(objectMapper -> objectMapper.registerModule(simpleModule)); - - // 2. 设置 objectMapper 到 JsonUtils - JsonUtils.init(CollUtil.getFirst(objectMappers)); - log.info("[init][初始化 JsonUtils 成功]"); - return new JsonUtils(); - } - -}