解决雪花算法ID传到前端丢失精度问题

一、现象

SpringBoot 项目,数据库主键使用bigint存储雪花算法生成 ID,Java 实体类使用Long接收数据库字段。后端 debug 查看 JSON 响应数据 ID 是正确的,但是前端拿到数据后 ID 后几位变成 0,出现精度丢失。
数据库表:

CREATE TABLE `user` (
 `id` bigint(32) NOT NULL COMMENT '用户id',
	...
  PRIMARY KEY (`id`) USING BTREE
) ENGINE=InnoDB DEFAULT CHARSET=utf8 ROW_FORMAT=DYNAMIC COMMENT='用户表';

Java实体:

import lombok.Data;

@Data
public class User {
    /**
     * 用户id
     */
    private Long id;
    //其他成员变量省略
}

示例:后端真实 ID:1352213368413982722,前端接收后变成:1352213368413982700。

二、问题根本原因

JS 的Number类型最大安全整数为 2⁵³‑1,只能保证16 位数字精度;雪花算法生成的 ID 是 19 位数字。

后端返回 JSON,前端 JSON 字符串转 JS 对象时,19 位的 Long ID 会被解析为 JS Number,超出安全数字范围,高位保留、低位直接失真,后几位变成 0。

三、解决思路

方案 1:数据库 id 字段改成 String

直接把数据库bigint修改为字符串类型。但是字符串做主键,索引查询性能会下降,一般不推荐。

方案 2:数据库、后端保持 Long/BigInt,序列化输出给前端转为 String

数据库依旧 bigint、Java 实体依旧 Long,只在返回 JSON 给前端时把 ID 序列化为字符串,前端拿到字符串就不会触发 Number 精度丢失。有两种实现方式:

方式 A:注解(局部生效,只作用于某个字段)

在实体的 id 字段上加@JsonFormat(shape = JsonFormat.Shape.STRING),只对该字段序列化转为字符串。

import com.fasterxml.jackson.annotation.JsonFormat;
import lombok.Data;

@Data
public class User {
    /**
     * 用户id
     */
    @JsonFormat(shape = JsonFormat.Shape.STRING)
    private Long id;
    //其他成员变量省略
}

方式 B:Jackson 全局配置

写配置类,自定义ObjectMapper,注册序列化器,全局所有Long类型序列化输出为字符串,不需要每个实体都加注解。

import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.module.SimpleModule;
import com.fasterxml.jackson.databind.ser.std.ToStringSerializer;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.http.converter.json.Jackson2ObjectMapperBuilder;

/**
 * Jackson全局序列化配置
 * 解决雪花算法Long类型ID返回前端JS精度丢失问题
 * 将所有Long类型序列化为字符串返回
 */
@Configuration
public class JacksonConfig {

    /**
     * 自定义ObjectMapper对象,替换SpringBoot默认的Jackson序列化实例
     * @Primary 优先使用本Bean,覆盖系统默认ObjectMapper
     * @ConditionalOnMissingBean 容器中不存在ObjectMapper才创建,避免重复覆盖
     * @param builder Jackson2ObjectMapperBuilder Spring提供的构建器
     * @return ObjectMapper 自定义序列化对象
     */
    @Bean
    @Primary
    @ConditionalOnMissingBean(ObjectMapper.class)
    public ObjectMapper jacksonObjectMapper(Jackson2ObjectMapperBuilder builder) {
        // 创建ObjectMapper实例,关闭xml映射
        ObjectMapper objectMapper = builder.createXmlMapper(false).build();

        // 创建自定义模块,用于注册自定义序列化器
        SimpleModule simpleModule = new SimpleModule();

        // 注册序列化器:Long对象序列化为字符串
        // ToStringSerializer.instance:直接调用对象toString()输出字符串
        simpleModule.addSerializer(Long.class, ToStringSerializer.instance);

        // 将自定义模块注册到ObjectMapper,序列化时生效
        objectMapper.registerModule(simpleModule);

        return objectMapper;
    }
}
THE END
文章版权归Tinsur.cn所有,转载分享请标注原链接
点赞1 分享
评论 抢沙发

请登录后发表评论

    暂无评论内容