SpringBoot解决前端js处理大数字丢失精度问题Long转String

一、Jackson对Long类型的处理导致精度丢失的问题

表的某一个字段的类型是 BIGINT,对应的 Java 类的属性的类型就是 Long。当这个字段的值由后端返回给前端网页时,发现了精度丢失的问题。比如后端返回的值是 588085469986509185,到了前端是 588085469986509200,后面的几位数变成了 0,精度丢失了

二、原因

JavaScript 中数字的精度是有限的,BIGINT 类型的的数字超出了 JavaScript 的处理范围。JavaScript 遵循 IEEE 754 规范,采用双精度存储(double precision),占用 64 bit,各位的含义如下:

1位(s) 用来表示符号位

11位(e) 用来表示指数

52位(f) 表示尾数

尾数位最大是 52 位,因此 JS 中能精准表示的最大整数是 Math.pow(2, 53),十进制即 9007199254740992。而 BIGINT 类型的有效位数是63位(扣除一位符号位),其最大值为:Math.pow(2, 63)。任何大于 9007199254740992 的就可能会丢失精度

三、解决方法

解决办法就是让 JavaScript 把数字当成字符串进行处理。对 JavaScript 来说,不进行运算,数字和 字符串处理起来没有什么区别,在 Springboot 中处理方法基本上有以下几种:

3.1 配置参数 write_numbers_as_strings

Jackson 有个配置参数 WRITE_NUMBERS_AS_STRINGS,可以强制将所有数字全部转成字符串输出。其功能介绍为:Feature that forces all Java numbers to be written as JSON strings.

使用方法很简单,只需要配置参数即可

XML 复制代码
spring:
  jackson:
    generator:
      write_numbers_as_strings: true

这种方式的优点是使用方便,不需要调整代码;缺点是颗粒度太大,所有的数字都被转成字符串输出了,包括按照 timestamp 格式输出的时间也是如此

3.2 给 Java 类的属性单独加注解

java 复制代码
@JsonSerialize(using=ToStringSerializer.class)
   private Long userId;

指定了 ToStringSerializer 进行序列化,将数字编码成字符串格式。这种方式的优点是颗粒度可以很精细;缺点同样是太精细,如果需要调整的字段比较多会比较麻烦

3.3 自定义ObjectMapper

最后想到可以单独根据类型进行设置,只对 Long 型数据进行处理,转换成字符串,

而对其他类型的数字不做处理。Jackson 提供了这种支持。方法是对 ObjectMapper 进行定制。根据 SpringBoot 的官方文档,找到一种相对简单的方法,只对 ObjectMapper 进行定制,而不是完全从头定制,方法如下:

java 复制代码
import com.fasterxml.jackson.databind.ser.std.ToStringSerializer;
import org.springframework.boot.autoconfigure.jackson.Jackson2ObjectMapperBuilderCustomizer;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class JacksonConfig {

    @Bean
    public Jackson2ObjectMapperBuilderCustomizer jackson2ObjectMapperBuilderCustomizer() {
        return jacksonObjectMapperBuilder -> {
            // long -> String
            jacksonObjectMapperBuilder.serializerByType(Long.TYPE, ToStringSerializer.instance);
            // Long -> String
            jacksonObjectMapperBuilder.serializerByType(Long.class, ToStringSerializer.instance);
        };
    }
}

如果是 Json 工具类,可以这么设置

java 复制代码
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.module.SimpleModule;
import com.fasterxml.jackson.databind.ser.std.ToStringSerializer;

public class JsonUtil {

    private static ObjectMapper mapper = new ObjectMapper();

    static {
        SimpleModule simpleModule = new SimpleModule();
        // long -> String
        simpleModule.addSerializer(Long.TYPE, ToStringSerializer.instance);
        // Long -> String
        simpleModule.addSerializer(Long.class, ToStringSerializer.instance);
        mapper.registerModule(simpleModule);
    }    
}

四、补充

如果方法3配置JacksonConfig不起作用但方法2起作用,请检查是否配置了MVC,MVC级别比配置JacksonConfig要高,请在WebMvcConfig里配置Long转换String

java 复制代码
@Configuration
public class WebMvcConfig extends WebMvcConfigurationSupport {
    /**
     * 配置转换器
     * @return
     */
    public MappingJackson2HttpMessageConverter mappingJackson2HttpMessageConverter() {
        MappingJackson2HttpMessageConverter converter = new MappingJackson2HttpMessageConverter();
        converter.setObjectMapper(getObjectMapper());
        return converter;
    }

    private ObjectMapper getObjectMapper() {
        ObjectMapper objectMapper = new ObjectMapper();

        // 指定时区
        objectMapper.setTimeZone(TimeZone.getTimeZone("GMT+8:00"));
        // 日期类型字符串处理
        objectMapper.setDateFormat(new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"));

        // Java8日期处理
        JavaTimeModule javaTimeModule = new JavaTimeModule();
        javaTimeModule.addSerializer(LocalDateTime.class, new LocalDateTimeSerializer(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")));
        javaTimeModule.addSerializer(LocalDate.class, new LocalDateSerializer(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")));
        javaTimeModule.addSerializer(LocalTime.class, new LocalTimeSerializer(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")));
        javaTimeModule.addDeserializer(LocalDateTime.class, new LocalDateTimeDeserializer(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")));
        javaTimeModule.addDeserializer(LocalDate.class, new LocalDateDeserializer(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")));
        javaTimeModule.addDeserializer(LocalTime.class, new LocalTimeDeserializer(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")));
        objectMapper.registerModule(javaTimeModule);

        //将Long序列成String
        SimpleModule simpleModule = new SimpleModule();
        simpleModule.addSerializer(Long.class, ToStringSerializer.instance);
        simpleModule.addSerializer(Long.TYPE, ToStringSerializer.instance);
        objectMapper.registerModule(simpleModule);
        return objectMapper;
    }
}
相关推荐
滚雪球~22 分钟前
npm error code ETIMEDOUT
前端·npm·node.js
沙漏无语24 分钟前
npm : 无法加载文件 D:\Nodejs\node_global\npm.ps1,因为在此系统上禁止运行脚本
前端·npm·node.js
supermapsupport25 分钟前
iClient3D for Cesium在Vue中快速实现场景卷帘
前端·vue.js·3d·cesium·supermap
brrdg_sefg27 分钟前
WEB 漏洞 - 文件包含漏洞深度解析
前端·网络·安全
胡西风_foxww33 分钟前
【es6复习笔记】rest参数(7)
前端·笔记·es6·参数·rest
m0_7482548835 分钟前
vue+elementui实现下拉表格多选+搜索+分页+回显+全选2.0
前端·vue.js·elementui
黑胡子大叔的小屋1 小时前
基于springboot的海洋知识服务平台的设计与实现
java·spring boot·毕业设计
星就前端叭1 小时前
【开源】一款基于Vue3 + WebRTC + Node + SRS + FFmpeg搭建的直播间项目
前端·后端·开源·webrtc
m0_748234521 小时前
前端Vue3字体优化三部曲(webFont、font-spider、spa-font-spider-webpack-plugin)
前端·webpack·node.js
Web阿成1 小时前
3.学习webpack配置 尝试打包ts文件
前端·学习·webpack·typescript