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的作者,我就不再这里继续啰嗦了啊。

最后

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

相关推荐
腾讯TNTWeb前端团队6 小时前
helux v5 发布了,像pinia一样优雅地管理你的react状态吧
前端·javascript·react.js
范文杰9 小时前
AI 时代如何更高效开发前端组件?21st.dev 给了一种答案
前端·ai编程
拉不动的猪9 小时前
刷刷题50(常见的js数据通信与渲染问题)
前端·javascript·面试
拉不动的猪9 小时前
JS多线程Webworks中的几种实战场景演示
前端·javascript·面试
FreeCultureBoy10 小时前
macOS 命令行 原生挂载 webdav 方法
前端
uhakadotcom10 小时前
Astro 框架:快速构建内容驱动型网站的利器
前端·javascript·面试
uhakadotcom11 小时前
了解Nest.js和Next.js:如何选择合适的框架
前端·javascript·面试
uhakadotcom11 小时前
React与Next.js:基础知识及应用场景
前端·面试·github
uhakadotcom11 小时前
Remix 框架:性能与易用性的完美结合
前端·javascript·面试
uhakadotcom11 小时前
Node.js 包管理器:npm vs pnpm
前端·javascript·面试