• js数据精度问题引发的和后端扯皮事件
  • 发布于 2个月前
  • 244 热度
    0 评论
问题出现背景
在某个开开心心上班的周五,突然测试老大找过说昨天上线的项目有大问题,数据都不显示了,早餐都来不及吃赶紧找原因。我们公司页面上的问题一般都是找前端先定位(前端老湿人~),看到直播列表数据返回正常,但是通过直播id获取直播详情时,数据返回为空。

心想一定是后端的幺蛾子了, 于是找后端说明情况:根据你返回的直播ID查不到详情数据, 一定是你接口有问题!
此时,告一段落,前端完胜,回去继续吃两块钱买的包子~

第二轮PK
屁股刚落座,后端来了,心情也很复杂的样子:你给的ID 我查了,数据库根本没有这个ID,我看了,这不是我给你的,你那边是不是对数据做处理了?我再次打开浏览器, 查看数据, 我给的就是Network中你返回的数据啊!没有处理啊。双方各执一词,各自怀疑~

问题定位
我发了请求到的数据作为证据:

后端同学给我发了postman返回的数据作为证据:

前端获取到的roomid:10976458979374928, 后端返回的:roomid:10976458979374929,相差一位,怎么这么奇怪!既然后端数据没有问题,此时我想难道是我浏览器有缓存问题了,难道真是前端的问题?

这轮PK,心生疑惑:为什么后端返回的json数据,到前端就不一样了呢?
怯怯的继续定位!

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

终于让我发现了问题所在,我在控制台看到了输出结果居然是一样的。此时瞬间想到了JS中数据精度问题,回想经常看的面试八股文,在JavaScript中,Number类型范围-2^53 + 1 到 2^53 - 1 。但是10976458979374928也明显超出了数据范围,为什么它打印正常,我又尝试了一下10976458979374927:

发现输出的也是10976458979374928这个值, 说明超出了都有问题,只是刚好这个值输入一致。其实 ES6 引入了Number.MAX_SAFE_INTEGER和Number.MIN_SAFE_INTEGER这两个常量,用来表示这个范围的上下限。

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

可以看到这几个值其实都是超出JS的Number范围的,看到的输出可能会有精度丢失问题的。
为啥后端的数据没有问题呢?在Java中Long类型的取值范围是-2^63 + 1 到 2^63 - 1, 比JavaScript中大很多,所以后端能正常处理。

解决办法
既然定位到了问题,那怎么解决了,于是又找到了后端,说明情况。后端给了两个解决方案:
方式1:数据库中存的就是数值型,修改数据库存的类型为字符串

方式2:返回接口时转为字符串类型给前端


因为线上问题,客户群里已经炸锅了, 临时先采用了方式2来解决, 后期后端在进行方式1的变更。我们后端还是很好说话的,如果遇到不配合的后端,那前端应该怎么来处理呢?作为有积极向上思想的前端,那就不能太被动, 自己也研究了一下解决方案。

在一些场景下也只能前端自己动手解决,例如使用的第三方接口返回Long类型。

方案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);  
}],  
})

// 堆代码 duidaima.com
// 假设后端返回的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

可以看到,使用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"
这个数据精度引发的血案到这里就告一段落,自己遇到了真的觉得有必要分享给小伙伴们,避免踩坑!当然最好还是让后端处理数据!
用户评论