likes
comments
collection
share

线上紧急Bug:80%你可能会遇到的数据精度问题

作者站长头像
站长
· 阅读数 16

欢迎关注公众号《前端界》,文章会同步更新,也可快速加入前端交流群!

问题出现背景

在某个开开心心上班的周五,突然测试老大找过说昨天上线的项目有大问题,数据都不显示了,早餐都来不及吃赶紧找原因。

我们公司页面上的问题一般都是找前端先定位(前端老湿人~),看到直播列表数据返回正常,但是通过直播id获取直播详情时,数据返回为空。

心想一定是后端的幺蛾子了, 于是找后端说明情况:

根据你返回的直播ID查不到详情数据, 一定是你接口有问题!

此时,告一段落,前端完胜,回去继续吃两块钱买的包子~

第二轮PK

屁股刚落座,后端来了,心情也很复杂的样子:

你给的ID 我查了,数据库根本没有这个ID,我看了,这不是我给你的,你那边是不是对数据做处理了

我再次打开浏览器, 查看数据, 我给的就是Network中你返回的数据啊!没有处理啊。双方各执一词,各自怀疑~

问题定位

我发了请求到的数据作为证据:

线上紧急Bug:80%你可能会遇到的数据精度问题

后端同学给我发了postman返回的数据作为证据: 线上紧急Bug:80%你可能会遇到的数据精度问题

前端获取到的roomid10976458979374928, 后端返回的:roomid10976458979374929,相差一位,怎么这么奇怪!

既然后端数据没有问题,此时我想难道是我浏览器有缓存问题了,难道真是前端的问题

线上紧急Bug:80%你可能会遇到的数据精度问题

这轮PK,心生疑惑

为什么后端返回的json数据,到前端就不一样了呢?

怯怯的继续定位!

发现问题

揣着对这两值的思考,我在控制台打印了一下:

线上紧急Bug:80%你可能会遇到的数据精度问题

线上紧急Bug:80%你可能会遇到的数据精度问题

终于让我发现了问题所在,我在控制台看到了输出结果居然是一样的

此时瞬间想到了JS中数据精度问题,回想经常看的面试八股文,在JavaScript中,Number类型范围-2^53 + 1 到 2^53 - 1

但是10976458979374928也明显超出了数据范围,为什么它打印正常,我又尝试了一下10976458979374927

线上紧急Bug:80%你可能会遇到的数据精度问题

发现输出的也是10976458979374928这个值, 说明超出了都有问题,只是刚好这个值输入一致。

其实 ES6 引入了Number.MAX_SAFE_INTEGERNumber.MIN_SAFE_INTEGER这两个常量,用来表示这个范围的上下限。

Number.isSafeInteger()用来判断一个整数是否落在这个范围之内。

线上紧急Bug:80%你可能会遇到的数据精度问题

可以看到这几个值其实都是超出JS的Number范围的,看到的输出可能会有精度丢失问题的。

为啥后端的数据没有问题呢?

在Java中Long类型的取值范围是-2^63 + 1 到 2^63 - 1, 比JavaScript中大很多,所以后端能正常处理。

解决办法

既然定位到了问题,那怎么解决了,于是又找到了后端,说明情况。后端给了两个解决方案:

  • 方式1: 数据库中存的就是数值型,修改数据库存的类型为字符串
  • 方式2:返回接口时转为字符串类型给前端

因为线上问题,客户群里已经炸锅了, 临时先采用了方式2来解决, 后期后端在进行方式1的变更。

我们后端还是很好说话的,如果遇到不配合的后端,那前端应该怎么来处理呢? 作为有积极向上思想的前端,那就不能太被动, 自己也研究了一下解决方案。

方案1:正则替换

如果我们使用的是axios请求数据,Axios 提供了自定义处理原始后端返回数据的 API:transformResponse , 可以这样处理:

axios({  
method: method,  
url: url,  
data: data,  
transformResponse: [function (data) {  
    // 将Long类型数据转换为字符串
    const convertedJsonString = data.replace(/"(\w+)":(\d{15,})/g, '"$1":"$2"'); 
    return JSON.parse(convertedJsonString);  
}],  
})


// 假设后端返回的JSON数据如下:
const responseData = {
  id: 12345678901234567890, // 这是一个Long类型数据
  name: "John Doe"
};

// 处理过的json数据
console.log(responseData.id); // 这将输出字符串:"12345678901234567890"
console.log(typeof responseData.id); // 这将输出 "string"

方案2:json序列化处理

我们可以借助json-bigint这个第三方包来处理。

为什么我们不直接使用JSON.parse

线上紧急Bug:80%你可能会遇到的数据精度问题

可以看到,使用JSON.parse()转换为JS对象,但是由于JS的Number的范围为,超出安全整数范围无法精确表示。

json-bigint中的parse方法会把超出 JS 安全整数范围的数字转为一个 BigNumber 类型的对象,对象数据是它内部的一个算法处理之后的,我们要做的就是在使用的时候转为字符串来使用。

通过启用storeAsString选项,可以快速将BigNumber转为字符串,代码如下:

    import JSONbig from "json-bigint";
    axios({  
    method: method,  
    url: url,  
    data: data,  
    transformResponse: [function (data) {  
+        const JSONbigToString = JSONbig({ storeAsString: true });
+          // 将Long类型数据转换为字符串
+          return JSONbigToString.parse(data);  
    }],  
    })
    
    
    // 假设后端返回的JSON数据如下:
    const responseData = {
      id: 12345678901234567890, // 这是一个Long类型数据
      name: "John Doe"
    };
    
    // 处理过的json数据
    console.log(responseData.id); // 这将输出字符串:"12345678901234567890"
    console.log(typeof responseData.id); // 这将输出 "string"

这个数据精度引发的血案到这里就告一段落,自己遇到了真的觉得有必要分享给小伙伴们,避免踩坑!当然最好还是让后端处理数据!

欢迎关注公众号《前端界》,文章会同步更新,也可快速加入前端交流群!