后端生成的 ID:
1961005746230337538
前端收到的 ID:
1961005746230337500
------ 少了 38?!这不是 Bug,是 JavaScript 的"安全整数"陷阱!
本文记录一次真实项目中因 雪花算法 ID 精度丢失 导致的线上问题,并给出彻底、可复用的解决方案。

🐛 问题现象:Long 类型在前后端传输中"变短"了
❌ 看似正常的后端代码
java
// 使用雪花算法生成 ID
@TableName("user")
public class User {
@TableId(type = IdType.ASSIGN_ID)
private Long id; // 1961005746230337538
private String name;
// getter/setter...
}
接口返回:
javascript
{
"id": 1961005746230337538,
"name": "Chaya"
}
前端接收到的数据
javascript
console.log(user.id); // 输出:1961005746230337500
末尾的
38
没了!被"四舍五入"了!
根本原因:JavaScript 的 Number 精度限制
- JavaScript 的
Number
类型是 双精度浮点数(64-bit)。 - 它能安全表示的整数范围是:
-2^53 + 1
到2^53 - 1
(即±9,007,199,254,740,991
)。 - 而雪花算法生成的 ID 通常是 19 位 Long,远超 JS 安全范围。
- 浏览器在解析 JSON 时,会自动将大整数转换为
Number
,导致精度丢失。
📌 举例:
1961005746230337538
超过2^53
,JS 无法精确表示,自动"对齐"到最近的可表示值 →1961005746230337500
解决方案:后端将 Long 序列化为字符串!
核心思路
在 Spring Boot 返回 JSON 时,将所有 Long
类型字段自动序列化为字符串,避免前端解析时精度丢失。
配置方式
java
@Configuration
public class WebConfig extends WebMvcConfigurationSupport {
@Bean
public MappingJackson2HttpMessageConverter customJackson2HttpMessageConverter() {
MappingJackson2HttpMessageConverter jsonConverter = new MappingJackson2HttpMessageConverter();
ObjectMapper objectMapper = new ObjectMapper();
SimpleModule simpleModule = new SimpleModule();
simpleModule.addSerializer(Long.class, ToStringSerializer.instance);
simpleModule.addSerializer(Long.TYPE, ToStringSerializer.instance);
objectMapper.registerModule(simpleModule);
jsonConverter.setObjectMapper(objectMapper);
return jsonConverter;
}
@Override
public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
converters.add(customJackson2HttpMessageConverter());
super.addDefaultHttpMessageConverters(converters);
}
}
🔍 关键代码解析
java
SimpleModule simpleModule = new SimpleModule();
simpleModule.addSerializer(Long.class, ToStringSerializer.instance);
simpleModule.addSerializer(Long.TYPE, ToStringSerializer.instance);
objectMapper.registerModule(simpleModule);
✅ 作用:注册一个 Jackson 模块,告诉 ObjectMapper:
- 所有
Long
和long
类型,在序列化成 JSON 时,不要转成数字,而是转成字符串!
✅ 效果对比
类型 | 原始 JSON | 修复后 JSON |
---|---|---|
Long ID | "id": 1961005746230337538 |
"id": "1961005746230337538" |
前端接收 | ❌ 精度丢失 | ✅ 完整字符串,无损 |
其他解决方案对比
方案 | 优点 | 缺点 |
---|---|---|
全局 Long 转 String(本文方案) | 一劳永逸,无需改实体类 | 所有 Long 都变字符串 |
@JsonFormat(shape = JsonFormat.Shape.STRING) |
精准控制字段 | 每个字段都要加,易遗漏 |
前端使用 BigInt |
不改后端 | 兼容性差,JSON 不支持 BigInt |
ID 返回为 String 类型 | 类型安全 | 实体类不"纯洁",影响数据库映射 |
推荐:全局配置 + 按需排除(如某些计数字段仍保留 Long)
如果某些 Long
字段不需要转字符串(如 age
、count
),可以自定义序列化器:
java
public class CustomLongSerializer extends JsonSerializer<Long> {
@Override
public void serialize(Long value, JsonGenerator gen, SerializerProvider serializers) throws IOException {
// 根据字段名或注解判断是否转字符串
if (isIdField(gen)) {
gen.writeString(value.toString());
} else {
gen.writeNumber(value);
}
}
}
但大多数场景下,统一转字符串更简单可靠。