💥【血泪现场还原】💥
"三年前项目刚搭建时,我就提出过:JS的Number.MAX_SAFE_INTEGER(2^53-1)
就是颗定时炸弹,ID建议传输给前端之前格式化为字符串类型。
结果后端大佬们坚持说:'我们Java Long随便传、我们生成的ID就是数字、我们不会有那么多数据',现在用户量剧增,ID突破js最大安全范围,前端拿到的ID超出位全变成了000000这样的鬼样子!"
🕳️【史诗级甩锅现场】🕳️
运维:"接口全炸了!"
产品:"用户投诉页面报错!"
后端(推眼镜):"这是前端精度问题,你们自己处理下,我返回的没问题啊"
😖【原因分析】😖
一、Java 中的 long
类型
-
类型:Java 中的
long
是一个 有符号的 64 位整数(signed 64-bit integer)。 -
存储方式:Java 的
long
类型使用 补码表示法(two's complement)来表示整数,这是一种在计算机中常用的表示有符号整数的方法。补码表示允许负数和正数使用相同的空间表示。 -
数值范围:
- 最大值:
2^63 - 1 = 9,223,372,036,854,775,807
- 最小值:
-2^63 = -9,223,372,036,854,775,808
- 最大值:
Java 的 long
类型直接表示 64 位的有符号整数,不存在精度丧失问题,能够表示范围非常大的整数。
二、JavaScript 中的 Number
类型
-
类型:JavaScript 中的
Number
是基于 IEEE 754 双精度浮点数标准(64-bit double-precision floating point) 。这个标准用于表示浮动小数点数,并且包含了整数和小数部分。 -
存储方式:JavaScript 的
Number
类型使用 64 位浮点数格式来存储数据。64 位格式中的 1 位用于符号位,11 位用于指数部分,剩下的 52 位用于尾数(即有效数字的表示)。这种存储方式使得它不仅可以表示整数,还可以表示小数。- 重要的区别:由于浮点数有 52 位用于尾数(有效数字),JavaScript 的
Number
类型只能精确表示最大为2^53 - 1
的整数。当数字大于2^53 - 1
时,它会失去精度,因为有效数字超过了尾数的表示范围。
- 重要的区别:由于浮点数有 52 位用于尾数(有效数字),JavaScript 的
-
数值范围:
- 最大安全整数:
2^53 - 1 = 9,007,199,254,740,991
(即Number.MAX_SAFE_INTEGER
)。 - 最小安全整数:
-(2^53 - 1) = -9,007,199,254,740,991
(即Number.MIN_SAFE_INTEGER
)。
JavaScript
Number
类型支持更广泛的数值范围(包括小数),但由于尾数位数的限制,它无法精确表示所有的整数。 - 最大安全整数:
三、long
和Number
精度差异
-
Java
long
类型:是一个 64 位有符号整数,用于表示整数,范围从-2^63
到2^63 - 1
,精度上没有问题。 -
JavaScript
Number
类型:是一个 64 位双精度浮点数,能够表示非常大的数,但由于其尾数部分的限制,最大安全整数为2^53 - 1
,超过这个值会发生精度丧失。
因此,尽管两者都使用了 64 位的存储空间,它们的使用场景和内部实现差异导致了它们在精度和表示范围上的不同,就会出现在java
中合法的数字在js
中超出安全范围,导致精度丢失。
🚀【前端自救指南】🚀
虽然正确的做法应该是后端用String传输,但既然要背锅,只能硬着头皮解决:
transformResponse
在大多数 Axios
封装的请求方法中,前端接收到后端响应的JSON体中,超长数字由于是字符串格式,所以未丢失精度。虽然 Axios
会自动解析响应数据,但你仍然可以在 transformResponse
中手动处理这种情况,确保接收到的数据不会丢失精度。
例如,在 Axios 请求响应的过程中,你可以拦截并将数字类型的 ID 转换成字符串,或者使用 BigInt
进行转换,也可以使用第三方库 json-bigint
统一处理响应体中所有大数。
// 导入 json-bigint
const JSONbig = require('json-bigint');
// 创建 axios 实例并配置 transformResponse
const axiosInstance = axios.create({
transformResponse: [(data) => {
// 使用 json-bigint 解析响应数据,避免精度丢失
if (data) {
return JSONbig.parse(JSON.stringify(data)); // 先转为 JSON 字符串再解析
}
return data;
}]
});
// 发起请求
axiosInstance.get('/api/your-api')
.then(response => {
// 直接获取 id,已处理为 BigInt 类型
console.log(response.data.id);
})
.catch(error => {
console.error(error);
});
💡【血泪总结】💡
- 所有可能超过15位的ID必须用String传输!
- 分布式ID建议直接设计为String(雪花ID也不安全!)
- 联调时要用超长响应值测试边界值!
- 建议把这篇甩到后端群@全体成员
🎤【灵魂拷问】🎤
"到底是技术无知,还是态度问题?当生产事故发生时,我们真的还要继续玩'前端背锅部'的老梗吗?"
(文末彩蛋:本文ID为"9007199254740993",用Number解析后变成___?评论区见分晓)