前后端的双精度浮点数精度不一致问题解决方案,自定义Spring的消息转换器处理JSON转换

在 Java 中,Long 是一个 64 位的长整型,通常用于表示很大的整数。在后端,Long 类型的数据没有问题,因为 Java 本身使用的是 64 位的整数,可以表示的范围非常大。

但是,在前端 JavaScript 中,Long 类型的数据可能会出现精度问题。原因在于 JavaScript 使用的是 双精度浮点数(Number 类型) 来表示数字,而双精度浮点数只能精确表示 最大 53 位的整数,超过 53 位的整数会丢失精度。

具体来说,JavaScript 的 Number 类型可以表示的最大整数是 2^53 - 1,即 9007199254740991。而任何超过这个范围的整数(比如某些大于 53 位的 ID)就会出现精度丢失,无法精确表示。

假设你在后端生成了一个大于 9007199254740991 的 Long 类型 ID,例如:

复制代码
Long id = 1234567890123456789L;
然后通过接口返回给前端,在前端的 JavaScript 中,数字会被转换为 Number 类型,可能会出现精度丢失的情况:

let id = 1234567890123456789;  // JavaScript 会将其转换为双精度浮点数
console.log(id);  // 输出: 1234567890123456700

可以看到,前端打印出来的数字已经失去了精度,因为超出了 JavaScript Number 类型能精确表示的范围。

解决思想是将Long转化为String,但解决方式有很多,要么你在对应数据库表的实体类(比如User)的id类型设置为String,要么可以进行如下配置:

复制代码
package com.xxx.xxxxx.common;

import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.module.SimpleModule;
import com.fasterxml.jackson.databind.ser.std.ToStringSerializer;
import com.fasterxml.jackson.datatype.jsr310.deser.LocalDateDeserializer;
import com.fasterxml.jackson.datatype.jsr310.deser.LocalDateTimeDeserializer;
import com.fasterxml.jackson.datatype.jsr310.deser.LocalTimeDeserializer;
import com.fasterxml.jackson.datatype.jsr310.ser.LocalDateSerializer;
import com.fasterxml.jackson.datatype.jsr310.ser.LocalDateTimeSerializer;
import com.fasterxml.jackson.datatype.jsr310.ser.LocalTimeSerializer;
import java.math.BigInteger;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.LocalTime;
import java.time.format.DateTimeFormatter;
import static com.fasterxml.jackson.databind.DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES;

/**
 * 对象映射器:基于jackson将Java对象转为json,或者将json转为Java对象
 * 将JSON解析为Java对象的过程称为 [从JSON反序列化Java对象]
 * 从Java对象生成JSON的过程称为 [序列化Java对象到JSON]
 */
public class JacksonObjectMapper extends ObjectMapper {

    public static final String DEFAULT_DATE_FORMAT = "yyyy-MM-dd";
    public static final String DEFAULT_DATE_TIME_FORMAT = "yyyy-MM-dd HH:mm:ss";
    public static final String DEFAULT_TIME_FORMAT = "HH:mm:ss";

    public JacksonObjectMapper() {
        super();
        //收到未知属性时不报异常
        this.configure(FAIL_ON_UNKNOWN_PROPERTIES, false);

        //反序列化时,属性不存在的兼容处理
        this.getDeserializationConfig().withoutFeatures(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES);


        SimpleModule simpleModule = new SimpleModule()
                .addDeserializer(LocalDateTime.class, new LocalDateTimeDeserializer(DateTimeFormatter.ofPattern(DEFAULT_DATE_TIME_FORMAT)))
                .addDeserializer(LocalDate.class, new LocalDateDeserializer(DateTimeFormatter.ofPattern(DEFAULT_DATE_FORMAT)))
                .addDeserializer(LocalTime.class, new LocalTimeDeserializer(DateTimeFormatter.ofPattern(DEFAULT_TIME_FORMAT)))

                .addSerializer(BigInteger.class, ToStringSerializer.instance)
                .addSerializer(Long.class, ToStringSerializer.instance)
                .addSerializer(LocalDateTime.class, new LocalDateTimeSerializer(DateTimeFormatter.ofPattern(DEFAULT_DATE_TIME_FORMAT)))
                .addSerializer(LocalDate.class, new LocalDateSerializer(DateTimeFormatter.ofPattern(DEFAULT_DATE_FORMAT)))
                .addSerializer(LocalTime.class, new LocalTimeSerializer(DateTimeFormatter.ofPattern(DEFAULT_TIME_FORMAT)));

        //注册功能模块 例如,可以添加自定义序列化器和反序列化器
        this.registerModule(simpleModule);
    }
}

package com.xxx.xxxxx.config;

import com.xxx.xxxxx.common.JacksonObjectMapper;
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter;
import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurationSupport;

import java.util.List;


@Slf4j
@Configuration
// 继承自 WebMvcConfigurationSupport,这表示让这个类成为 Spring MVC 配置的一部分,允许你定制 Spring MVC 的配置
public class WebConfig extends WebMvcConfigurationSupport {
    @Override
    protected void addResourceHandlers(ResourceHandlerRegistry registry) {
        log.info("资源映射开始。。");
        registry.addResourceHandler("/backend/**").addResourceLocations("classpath:/backend/");
        registry.addResourceHandler("/front/**").addResourceLocations("classpath:/front/");
    }

    @Override
    protected void extendMessageConverters(List<HttpMessageConverter<?>> converters) {
        // 添加一个转换器,除自带八大转换器外,将Long装成String
        MappingJackson2HttpMessageConverter converter = new MappingJackson2HttpMessageConverter();
        converter.setObjectMapper(new JacksonObjectMapper());
        // 优先级设置到最高,让spring优先用咱自己自定义的转换器
        converters.add(0,converter);
    }
}
相关推荐
超龄超能程序猿13 分钟前
Spring 应用中 Swagger 2.0 迁移 OpenAPI 3.0 详解:配置、注解与实践
java·spring boot·后端·spring·spring cloud
惜.己19 分钟前
使用python读取json数据,简单的处理成元组数组
开发语言·python·测试工具·json
en-route35 分钟前
Http请求中的特殊字符
spring·http
fouryears_234178 小时前
Spring,Spring Boot 和 Spring MVC 的关系以及区别
java·spring boot·spring·mvc
stoneSkySpace14 小时前
set、map 比数组,json 对象的性能更好原因分析
json
设计师小聂!15 小时前
尚庭公寓----------分页查询
java·开发语言·spring·maven·mybatis
livemetee17 小时前
springboot 整合spring-kafka客户端:SASL_SSL+PLAINTEXT方式
spring boot·spring·kafka
要开心吖ZSH17 小时前
Spring Cloud LoadBalancer 详解
后端·spring·spring cloud
xgc_java18 小时前
架构解密|一步步打造高可用的 JOCR OCR 识别服务
架构·ocr·状态模式
CodeWolf21 小时前
Filter过滤器和Interceptor拦截器的使用和区别
spring