问题出现
后端ID的生成升级为雪花算法(snowflake),什么是雪花算法,其实就是一个生成全局唯一分布式ID的算法,改完之后在http请求回来的时候发现响应的数据跟后端数据库的数据不一致,一时间不知道问题出在哪里。
分析问题
经过分析,由于JavaScript语言本身问题,number类型的数值的最大安全数(Number.MAX_SAFE_INTEGER)是2^53次方, 也即是16位,而雪花算法生成的ID是19位,所以通过http传输到前端之后就会出现精度丢失的问题, 如1540265707848196888会变成1540265707030306800。
解决问题
该问题可以由后端解决,也可以由前端解决,网上大部分答案都是在后端解决的,在后端序列化后返回给前端,本文只分析前端的解决方法。
核心就是将超出js安全数字的值转换为bigint类型或者字符类型
第一种方法
利用JavaScript Bigint数据类型的特性,引入基于这个数据类型的插件json-bigint,然后在axios中的create方法中有一个transformResponse属性,在这个属性中中将json-bigint封装后的数据返回即可。如果数据里面有大于js安全数字的超大数字,这个插件就会把该数字转换为bigint类型,使用的时候就需要将该字段toString()才能得到该数字的字符串类型,具体代码如下。
// 安装
npm i json-bigint -S
// 引入
import JSONBig from 'json-bigint'
// 在axios中使用
const http = axios.create({
timeout: 1000 * 30,
withCredentials: true,
headers: {
'Content-Type': 'application/json; charset=utf-8'
},
transformResponse: [
function(data) {
try{
return JSONBig.parse(data)
}catch(e){
return data
}
}
]
})
// 返回的数据
categoryId: BigNumber c: (2) [15402, 65707030306666] e: 18 s: 1
// 使用
res.data.data.categoryId = res.data.data.categoryId.toSting()
缺陷/待完善
全局处理后返回,思路是循环判断data中的数据,判断每个数据的数据类型,如果是bigint类型的话,直接toString后返回,但是这样处理的时间复杂度是是O(n^2),而且数据格式涉及分页,列表,对象,以及基本数据类型,处理起来非常麻烦,而且性能极低,接下来看第二种方法。
2022年07月04日更正:写这篇文章前没有好好的看文档,这是作为程序员👨🏻💻这样严谨的职业不改犯的错误,有误导大家的地方请大家多多见谅。
事情是这样的,上面的情况是在json-bigint不加参数的情况下出现的,经查阅json-bigint的npm文档发现了有这样一个参数({ storeAsString: true }),大概意思就是将bigint作为字符串而不是默认BigNumber存储在对象中,但是他后面又跟了一句话,请注意,这是一种危险的行为,因为它破坏了在不更改数据类型的情况下来回转换的默认功能(因为这会将所有bigint转换为be-and-stay字符串),目前还没发现加这个参数会出现什么问题【手动捂脸】。
原文:
Specifies if BigInts should be stored in the object as a string, rather than the default BigNumber. Note that this is a dangerous behavior as it breaks the default functionality of being able to convert back-and-forth without data type changes (as this will convert all BigInts to be-and-stay strings).
然后代码就变成了下面这个样子。
// 安装
npm i json-bigint -S
// 引入
const JSONBig = require('json-bigint')({ storeAsString: true })
// 在axios中使用
const http = axios.create({
timeout: 1000 * 30,
withCredentials: true,
headers: {
'Content-Type': 'application/json; charset=utf-8'
},
transformResponse: [
function(data) {
try{
return JSONBig.parse(data)
}catch(e){
return data
}
}
]
})
// 返回的数据
categoryId: "1540265707030306666"
第二种方法
利用transformResponse函数返回的数据是原始数据(即stringify后的数据)的特性,可以利用正则将大于16位的数字匹配到,然后利用replace方法将改字段的值替换成字符串,这样即简单性能又高,代码如下。
const http = axios.create({
timeout: 1000 * 30,
withCredentials: true,
headers: {
'Content-Type': 'application/json; charset=utf-8',
projectId: getProjectId()
},
transformResponse: [
function(data) {
// 加冒号是防止替换到非字段的值
data = data.replace(/:([0-9]{16,})/g, (a, b) => {
return `:"${b}"`
})
// 判断能被JSON.parse()的数据
const flag = /^{.+}$/.test(data)
return flag ? JSON.parse(data) : {}
}
]
})
// 返回的数据
[{"categoryId":"1540265707030306818","categoryName":"兑换券","categoryDescribe":null,"createTime":"2022-06-24 17:28:56.0"}]
写在最后
第一次在掘金发文,写起来还是问题不断,看到其它内容输出大佬写的文章格式那么优雅,真的自愧不如,但是我会不断优化争取写出更好的文章。关于本文,如果写得不好或者不对的地方还请各位大佬指正,感激不尽。