后端生成的 ID:
1961005746230337538
前端收到的 ID:1961005746230337500
—— 少了 38?!這不是 Bug,是 JavaScript 的“安全整數”陷阱!
本文記錄一次真實項目中因 雪花算法 ID 精度丟失 導致的線上問題,并給出徹底、可復用的解決方案。
🐛 問題現象:Long 類型在前后端傳輸中“變短”了
? 看似正常的后端代碼
// 使用雪花算法生成 ID
@TableName("user")
public class User {@TableId(type = IdType.ASSIGN_ID)private Long id; // 1961005746230337538private String name;// getter/setter...
}
接口返回:
{"id": 1961005746230337538,"name": "Chaya"
}
前端接收到的數據
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
類型字段自動序列化為字符串,避免前端解析時精度丟失。
配置方式
@Configuration
public class WebConfig extends WebMvcConfigurationSupport {@Beanpublic 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;}@Overridepublic void configureMessageConverters(List<HttpMessageConverter<?>> converters) {converters.add(customJackson2HttpMessageConverter());super.addDefaultHttpMessageConverters(converters);}}
🔍 關鍵代碼解析
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
),可以自定義序列化器:
public class CustomLongSerializer extends JsonSerializer<Long> {@Overridepublic void serialize(Long value, JsonGenerator gen, SerializerProvider serializers) throws IOException {// 根據字段名或注解判斷是否轉字符串if (isIdField(gen)) {gen.writeString(value.toString());} else {gen.writeNumber(value);}}
}
但大多數場景下,統一轉字符串更簡單可靠。