Vue项目中解决数字解析精度丢失问题

项目背景

标准一码游后端接口返回数据好些都包含用户敏感信息(姓名、手机号、身份证号),基于保护用户隐私数据等原因,产品经理提出需求,要求对涉及到这些数据的后端接口进行加密处理。为了方便快速开发,同时尽量以最小的改动代价实现其目标,经过和后端商量,前后端不采取一个个字段加解密的方式(这种方式修改起来涉及到较多的页面的字段处理,前后端工作量都较大,不利于代码维护),而是对涉及到的接口,整体进行加密传输(新增、编辑等操作),解密展示(详情查询、列表展示等操作)。

什么是数字精度丢失

我们知道,浮点类型的数据,在计算机中是以二进制的方式存储的,但是表示的数据也有个上限和下限。当超过限制 ,在计算机上显示只能取最接近的限值。 数字解析精度丢失说的就是这个现象, 简单讲就是数字超过了位数不够表示了。谁让后端返回给前端这样的大数字数据呢。

解决方案

数字解析精度丢失的常用前端解决方案,目前网上常用的有 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的作者,我就不再这里继续啰嗦了啊。

最后

希望对于阅读到这篇文章的您,能够有所帮助,如果你已经知道掌握了这些,也请不要嘲笑我,我写这些也只是为了记录一下,别无他求。

相关推荐
轻口味2 分钟前
命名空间与模块化概述
开发语言·前端·javascript
前端小小王37 分钟前
React Hooks
前端·javascript·react.js
迷途小码农零零发1 小时前
react中使用ResizeObserver来观察元素的size变化
前端·javascript·react.js
娃哈哈哈哈呀1 小时前
vue中的css深度选择器v-deep 配合!important
前端·css·vue.js
旭东怪2 小时前
EasyPoi 使用$fe:模板语法生成Word动态行
java·前端·word
ekskef_sef3 小时前
32岁前端干了8年,是继续做前端开发,还是转其它工作
前端
sunshine6414 小时前
【CSS】实现tag选中对钩样式
前端·css·css3
真滴book理喻4 小时前
Vue(四)
前端·javascript·vue.js
蜜獾云4 小时前
npm淘宝镜像
前端·npm·node.js