后端接口要求金额都乘100k?一个axios拦截器搞定
背景
最近在开发电商场景的项目时,看到了后端同学给的接口文档上有关于金额的字段都标注了 multiply 100k。询问后得知是为了避免一些精度问题。(前后端数字传输中小数、长整数都会有精度问题存在,不是这篇文章讨论的重点)。
这就导致了下面几个让人头疼的问题:
前端在展示接口返回的金额时,需要先除100k
有些表单中需要用户填写金额提交时,在请求发出去之前要先乘100k
涉及到计算的部分(比如折扣),接口返回值参与计算时要除100k,用户输入值参与计算时不用除
...
满篇的 xxx * 100000
| xxx / 100000
不仅看上去很不优雅,业务逻辑复杂起来以后,代码变得很难维护。
思路
熟练使用axios这个库的前端攻城狮们一定会想到它的 interceptors
。 没错,拦截器终于要发挥它添加请求头之外的作用了???? (哦不还统一处理过一些异常返回。在拦截器中提前处理了乘除100k的操作以后,业务代码中拿着数字直接用,一个字,爽。
在开始写代码前要考虑先构思一下:
首先要知道哪些字段需要去做乘除的
对于目标字段不同数据类型的处理逻辑
代码
话不多说,我们逐步看看怎么实现
AxiosRequestConfig
中新增路径传参
上文提到过需要知道哪些字段需要去做处理,这边的解决方案是在 AxiosRequestConfig
中添加一个 parse100kFields
字段,接收的是一个字符串数组。
而每个字符串则代表目标字段的路径
const data = { single: [ { price: 100000, }, { price: 200000, } ], total: 1234000, } // 例如这里需要修改 price 和 total 两个字段的值 const config = { //... parse100kFields: [ 'single.price', 'total' ] } 复制代码
为了TypeScript项目中接口声明时添加此config不报错,可以添加 d.ts 文件更改 AxiosRequestConfig
类型声明
// config.d.ts import "axios"; declare module "axios" { export interface AxiosRequestConfig { parse100kFields?: string[]; } } 复制代码
request/response中处理对应路径下的字段
这一步要注意的是处理对象或数组的每一层都要保持引用是相同的,这里的原因是找到要修改的字段直接修改它比较容易,但如果要恢复原对象的整个数据结构会变得很麻烦。
const parse100k = (obj: any, path: string, type: 'multiply' | 'divide') => { path = path.replace(/^\./, ''); const keyArr = path.split('.'); // 路径每解析一层就把下一层需要解析的元素都放进来 (主要是要处理字段类型为数组的子元素) let container = [obj]; try { for (let i = 0; i < keyArr.length; i++) { const key = keyArr[i]; const newContainer = []; container.forEach(singleObj => { if (key in singleObj) { if (i === keyArr.length - 1) { // 若无下一层路径,则做转换 singleObj[key] = numberParser(singleObj[key], type); } else { const nextLevelElement = singleObj[key]; if (Object.prototype.toString.call(nextLevelElement) === "[object Object]") { newContainer.push(nextLevelElement) } else if (Object.prototype.toString.call(nextLevelElement) === "[object Array]") { newContainer.push(...nextLevelElement) } else { // 字段类型非数组或对象,则无法往下层解析 throw new Error() } } container = newContainer; } else { throw new Error() } }) } return obj; } catch (err) { console.warn(`Cannot find ${path} in obj`); return obj; } } 复制代码
数字的处理
这里仅处理 number
类型和可以转成 number 的 string
类型,除此之外把原本的数据返回回去。
这里用到了一个轻量的解决JS计算精度的库 number-precision
import NP from 'number-precision'; const numberParser = (number: any, type: 'multiply' | 'divide') => { // 如果类型不能做转换,保持不变 if (!['number', 'string'].includes(typeof number)) { return number; } const CONVERTER = type === 'multiply' ? 100000 : 0.00001; if (typeof number === 'number') { return NP.times(number, CONVERTER); } // 字符串类型没法被转换成数字的话,保持不变 return isNaN(Number(number)) ? number : String(NP.times(Number(number), CONVERTER)); } 复制代码
FormData的处理
在一些包含文件传输的接口里会用到 FormData
的类型传输,这种类型的处理方法与普通对象有所不同
const parse100kFormData = (formData: FormData, path: string) => { const parsedFields = formData.getAll(path).map(value => numberParser(value, 'multiply')); formData.delete(path); parsedFields.forEach(value => { formData.append(path, value); }) return formData; } 复制代码
interceptors
最终就是 request / response 的interceptor啦
const responseNumberHandler = (response: AxiosResponse) => { const { data } = response.data; const { parse100kFields = [] as string[] } = response.config; if (data) { parse100kFields.forEach(filed => { parse100k(data, filed, 'divide'); }) } return response.data; } const requestNumberHandler = (config: AxiosRequestConfig) => { const { parse100kFields = [] as string[], method, data } = config; if (['POST', 'PUT', 'PATCH'].includes(method.toUpperCase()) && data) { parse100kFields.forEach(field => { (data instanceof FormData) ? parse100kFormData(data, field) : parse100k(data, field, 'multiply'); }) } return config; } net.interceptors.request.use(requestInterceptor); net.interceptors.response.use(responseNumberHandler); 复制代码
最终效果
对下边的接口进行配置
export const getBillingDetail = (params: GetBillingDetailReq) => { return net.get(`${PREFIX}/billing/detail`, { params, parse100kFields: ['order_amount_after_tax'] }); }; 复制代码
在控制台打印一下转换完毕后的数据结构
这样在业务代码里完全不用考虑乘除100k的问题了!乃思!
如果有更好的实现方式和没考虑到的边界情况,欢迎大佬们指教。
作者:天光墟叻仔筠
链接:https://juejin.cn/post/7054172931970039844