项目背景
标准一码游后端接口返回数据好些都包含用户敏感信息(姓名、手机号、身份证号),基于保护用户隐私数据等原因,产品经理提出需求,要求对涉及到这些数据的后端接口进行加密处理。为了方便快速开发,同时尽量以最小的改动代价实现其目标,经过和后端商量,前后端不采取一个个字段加解密的方式(这种方式修改起来涉及到较多的页面的字段处理,前后端工作量都较大,不利于代码维护),而是对涉及到的接口,整体进行加密传输(新增、编辑等操作),解密展示(详情查询、列表展示等操作)。
什么是数字精度丢失
我们知道,浮点类型的数据,在计算机中是以二进制的方式存储的,但是表示的数据也有个上限和下限。当超过限制 ,在计算机上显示只能取最接近的限值。 数字解析精度丢失说的就是这个现象, 简单讲就是数字超过了位数不够表示了。谁让后端返回给前端这样的大数字数据呢。
解决方案
数字解析精度丢失的常用前端解决方案,目前网上常用的有 bignumber.js、json-bigint、lossless-json等,其中json-bigint、lossless-json无疑是其中的佼佼者,特别是解析一堆json数据用于展示的时候,两者使用起来真的不要太香,使用时只需使用两者的parse、stringify方法即可,跟原生的JSON.parse、JSON.stringify没有任何区别,唯一需要注意的地方就是使用前部分特殊参数的配置罢了,还有就是node环境不同,导致json-bigint、lossless-json无法正常安装,或者安装成功,运行报错的情形。
小小抱怨一下
我就在最先使用lossless-json的时候,遇到了坑,当时的node环境不管怎么安装到项目中,总之就是报错,还是那种直接指向安装包,让人无从下手的感觉。害得我都下载了数次依赖,无数遍node环境,搞到npm都无法运行了,还有nvm都崩了。最后一次重新安装的node环境终于可以了,我的那个坑爹,还是原来的node版本啊,当时只要一使用,就报错,现在不闹了,OK了,我找谁说理去啊(真不知道当时哪个环节影响到了)。
同时,下载的lossless-json版本,后台项目(vite搭建,vue版本2.7.14)最新版本4.0.1,都ok了,但是挪移到小程序项目(webpack搭建,vue版本2.6.11)中的时候,又出幺蛾子了,只要一使用lossless-json,就报错指向到 lossless-json 源码中,又经过了多次尝试,最后降级到2.0.1版本,才终于不再报错了。也不确定lossless-json的各个版本,是否和vue版本,或者搭建项目的框架(webpack、vite)有关,或者和node版本有关,具体我也没时间慢慢研究,去查他的文档,毕竟我们只是为了快速解决问题,完成任务,又不是他的代码开发者,要去纠结这些,你说是不是呢。
回归正题
考虑到项目中需要使用此功能的地方很多,为了方便管理维护,我定义了一个全局对象$jsonbig,然后将json-bigint(或者lossless-json)的 parse、stringify方法封装到此对象中,最后在需要使用的地方直接调用即可。
js
this.$jsonbig.parse("JSON字符串");
this.$jsonbig.stringify("JSON对象");
下面我将具体讲一讲在实际项目中是如何使用它们的吧。
一、使用json-bigint
查看json-bigint文档可参考: gitee yarn
如何使用
1、先在js中定义公共方法:
js
const JsonBigint = require('json-bigint')({
storeAsString: true
});
Object.defineProperty(Vue.prototype, '$jsonbig', {
value: JsonBigint
})
2、具体使用
js
// 在使用admate.js的后台框架中使用,新增、编辑、查看详情、分页列表等关键代码片段:
setup: () = >useAdmateAdapter({
urlPrefix: 'sot-admin-api/sys/company',
axiosConfig: {
r: row = >({
method: 'GET',
url: row.companyId
}),
d: {
method: 'POST'
},
getList: {
url: 'list'
},
u: {
headers: {
'Content-Type': 'application/json'
}
},
c: {
headers: {
'Content-Type': 'application/json'
}
}
},
list: {
filter: {
keyword: null,
areaCode: null,
contactName: '',
contactPhone: ''
},
dataAt: 'data.record'
},
form: {
data: {
__parentAreaCode: null,
address: null,
longitude: null,
latitude: null
}
}
},
{
afterGetList({ data }) {
this.$set(this.list, 'data', this.$jsonbig.parse(this.$decrypt(data.record)))
},
afterOpenForm({ data }) {
this.$set(this.form, 'data', this.$jsonbig.parse(this.$decrypt(data)))
}
}),
/**
* 获取订单详情数据
* @return Void
*/
queryForDetail() {
this.$set(this.form__, "loading", true);
this.$POST(`sot - admin - api / hotel / self - order / queryForDetail`, {
id: this.form.data.orderId,
}).then(({ data }) = >{
this.$set(this.form__, "data", this.$jsonbig.parse(this.$decrypt(data)));
}).finally((_) = >{
this.$set(this.form__, "loading", false);
});
},
解释一下, <math xmlns="http://www.w3.org/1998/Math/MathML"> d e c r y p t 为项目中解密用到的公共方法,使用 t h i s . decrypt为项目中解密用到的公共方法,使用this. </math>decrypt为项目中解密用到的公共方法,使用this.decrypt(data)获取到的是json数据字符串,就是它可能丢失了数据精度。使用全局方法 this.$jsonbig.parse将得到我们需要JSON数据对象,经过parse转换后,json数据中可能丢失精度的字段值(BigNumber类型)都将统一转为字符串类型(不论后端原本返回的是字符串还是数值类型)。
注意:
我们标准一码游后台是用vite搭建的vue项目,在引入json-bigint的时候,我们使用到了require,如下
js
require('json-bigint')({ storeAsString: true })
如果您以为像在webpack搭建出来的项目那样,直接require引入就可以了的话,那你真的是想得简单了。
如果你你在实际项目中跑了代码,你会发现,项目运行时会在console里报错,项目也跑不起来了,
js
init.js:143 Uncaught ReferenceError: require is not defined at init.js:143
其实归根结底是vite搭建的vue项目不支持commonJS语法,所以要使用require还得额外安装插件, 推荐使用 vite-plugin-require-transform
js
// 下载
npm i vite-plugin-require-transform --save-dev
// 使用
// 在vite.config.js中引入,并找到plugins,在里面添加requireTransform 配置参数
import requireTransform from 'vite-plugin-require-transform';
plugins: [
// passing string type Regular expression
requireTransform({
fileRegex: /.js$|.vue$/
}),
],
说到这里,有人也许会说,我们不用require,改为import引入不就可以了,
js
require('json-bigint')({ storeAsString: true })
重点来了,这里引用 json-bigint 的时候,后面的参数 storeAsString 这个参数很重要,不可省略(后面接着讲),如果没有参数storeAsString,我想大家都知道如何改为import
js
import JsonBigint from 'json-bigint'
这真的不用我啰嗦解释啥,可是如果带上参数呢,该怎么设置默认参数storeAsString,我尝试了好几种写法,都没成功,又看了下文档和百度,都没见到,或许再去翻一下json-bigint的源代码,也许能找到答案,可是谁叫我太懒了呢,有谁知道的,可以直接留言告诉我,多谢了啊。
回到刚才的话题,刚刚提到的参数 { storeAsString: true } ,为何说必不可少呢?
这是因为如果没有设置这个参数为true,返回给我们的数据,超精度的那个字段值,将是一个BigNumber类型,如下面截图的companyId字段,
这种值,如要要在项目中展示或使用,是会出错的,官方文档告诉我们可以使用toString()或者String()单独对这些字段进行格式化,可这次需求涉及到的页面较多,每个改动到的后端接口返回的字段同样很多,谁知道哪个字段精度丢失了,又在什么地方使用了(毕竟我们不是从头一点点开发项目,遇到不对,立马修改,而是在原来正常运营的项目中修改),因此 storeAsString这个参数就非常有用了,它使我们不必一个个的对BigNumber类型的字段进行处理,直接放心大胆的按原有的逻辑使用即可。
当然官方英文文档不建议我们使用,说这是一种危险的行为,
看吧,两个地方都这样说,搞得我当时使用的时候心惊胆战的,其实这破英文的意思:
指定BigInts是否应该以字符串的形式存储在对象中,而不是默认的BigNumber。
请注意,这是一种危险的行为,因为它破坏了在不更改数据类型的情况下来回转换的默认功能(因为这将把所有bigint转换为be-and-stay字符串)。
看了中文解释,现在我们再使用storeAsString设置为true,是不是瞬间放心不少,破英文单词 dangerous 在这里吓人呢 ,有没有同感啊。
二、使用lossless-json
lossless-json使用和刚介绍的json-bigint类似,同样是在js中定义公共方法,使用时完全一致,唯一不同的地方在于定义公共方法时为了将BigNumber类型字段自动转化为字符串,lossless-json没有类似storeAsString的参数,而是要定义一个函数reviver,然后作为参数重写入parse方法,具体如下设置:
js
import { parse, stringify } from 'lossless-json'
// 对number值进行转换,如果不用这个方法,Number类型就变成LosslessNumber
const bigDataReviver = (key, value) = >{
if (value && value.isLosslessNumber) {
if (Number.isSafeInteger(parseInt(value.value))) {
return parseInt(value.value)
}
return value.value
} else {
return value
}
}
Object.defineProperty(Vue.prototype, '$jsonbig', {
value: {
parse: (keyword, reviver = bigDataReviver) = >{
return parse(keyword, reviver)
},
stringify
}
})
值得一提的是,lossless-json无法像下面这样使用
js
import LosslessJson from 'lossless-json'
// 中间部分省略不显示
Object.defineProperty(Vue.prototype, '$jsonbig', {
value: {
parse: (keyword, reviver = bigDataReviver) = >{
return LosslessJson.parse(keyword, reviver)
},
stringify: LosslessJson.stringify
}
})
如果你跑了项目,你可能会发现,项目无法正常跑起来,并且在浏览器控制台console栏,发现Uncaught SyntaxError报错,如下图
按照这英文提示,结合我们引用时候直接从对象中取parse, stringify方法 ,我们不难理解其含义,lossless-json并没有向我们提供一个默认接口对象,让我们通过一个对象直接调用parse, stringify方法,而是只暴露了parse, stringify。
就像我从json-bigint源码中找到的,
红框框选的这部分,在lossless-json中并没有,所以才出现了上面的报错。
关于在下载lossless-json引入使用报错的情况,我文中开始就已经提到,我就不再赘述,或许你遇都遇不到。
即使你真的遇到了,可以尝试重装,或者使用nvm切换node环境,再或者降级下载的版本,看能不能下载后使用吧,具体什么原因导致的,我是没有兴趣慢慢尝试和研究的,如果你有兴趣,可以去查找资料或者问lossless-json的作者,我就不再这里继续啰嗦了啊。
最后
希望对于阅读到这篇文章的您,能够有所帮助,如果你已经知道掌握了这些,也请不要嘲笑我,我写这些也只是为了记录一下,别无他求。