Skip to content

服务器返回前端Long类型精度丢失

数据类型差异

JavaScript 里只有一种数值类型 Number,它遵循 IEEE 754 双精度 64 位浮点数标准。该标准将 64 位划分为不同部分,其中 1 位是符号位,11 位是指数位,52 位是尾数位。整数在这种存储方式下,能精确表示的最大安全整数是 2^53 - 1,也就是 9007199254740991。一旦数值超过这个范围,就无法精确表示,从而造成精度丢失。

数值范围问题

Java 等后端语言的 long 类型通常是 64 位整数,其取值范围为 -2^63 到 2^63 - 1,远大于 JavaScript 中 Number 类型能精确表示的范围。当后端返回的 long 类型数值超出了 JavaScript 的最大安全整数范围,前端在接收和处理时,就会自动将其转换为 Number 类型,这样就会丢失精度。

雪花ID 引发的问题

雪花算法生成的 ID 是 64 位的,其范围是 0 到 2^64 - 1,需要使用到java的Long类型,而 JavaScript 的 Number 类型只能精确到 53 位,因此,当雪花算法生成的 ID 超出 JavaScript 的最大安全整数范围时,就会丢失精度。

解决方法

通过配置全局json序列化器自动转换

java
@Slf4j
@AutoConfiguration(before = JacksonAutoConfiguration.class)
public class JacksonConfig {

    @Bean
    public Jackson2ObjectMapperBuilderCustomizer customizer() {
        return builder -> {
            // 全局配置序列化返回 JSON 处理
            JavaTimeModule javaTimeModule = new JavaTimeModule();
            javaTimeModule.addSerializer(Long.class, BigNumberSerializer.INSTANCE);
            javaTimeModule.addSerializer(Long.TYPE, BigNumberSerializer.INSTANCE);
            javaTimeModule.addSerializer(BigInteger.class, BigNumberSerializer.INSTANCE);
            javaTimeModule.addSerializer(BigDecimal.class, ToStringSerializer.instance);
            DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
            javaTimeModule.addSerializer(LocalDateTime.class, new LocalDateTimeSerializer(formatter));
            javaTimeModule.addDeserializer(LocalDateTime.class, new LocalDateTimeDeserializer(formatter));
            builder.modules(javaTimeModule);
            builder.timeZone(TimeZone.getDefault());
            log.info("初始化 jackson 配置");
        };
    }

}
java
@JacksonStdImpl
public class BigNumberSerializer extends NumberSerializer {

    /**
     * 根据 JS Number.MAX_SAFE_INTEGER 与 Number.MIN_SAFE_INTEGER 得来
     */
    private static final long MAX_SAFE_INTEGER = 9007199254740991L;
    private static final long MIN_SAFE_INTEGER = -9007199254740991L;

    /**
     * 提供实例
     */
    public static final BigNumberSerializer INSTANCE = new BigNumberSerializer(Number.class);

    public BigNumberSerializer(Class<? extends Number> rawType) {
        super(rawType);
    }

    @Override
    public void serialize(Number value, JsonGenerator gen, SerializerProvider provider) throws IOException {
        // 超出范围 序列化位字符串
        if (value.longValue() > MIN_SAFE_INTEGER && value.longValue() < MAX_SAFE_INTEGER) {
            super.serialize(value, gen, provider);
        } else {
            gen.writeString(value.toString());
        }
    }
}

也可以通过@JsonSerialize注解,这个注解会将Long类型先转换成String再进行序列化

java
@JsonSerialize(using = ToStringSerializer.class)
Long id;

粤ICP备20009776号