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(); - } - -}