前言
最近,项目中需要接入第三方实名认证。
看了第三方提供的开发文档后,得知实名认证分为三种:普通版、签名版、有效期版。
- 普通版:将参数appId、appKey、name、idNum以multipart/form-data方式提交请求。
- 签名版:将参数appId、name、idNum、sign以multipart/form-data方式提交请求。其中参数sign的值由前三个参数和值拼接并使用hmacSHA1签名后经Base64编码得到。
- 有效期版:将参数appId、appKey、name、idNum、startTime、endTime以multipart/form-data方式提交请求。
结合项目业务场景及网络请求的安全性,决定采用签名版实名认证。
签名实现
本以为,这个签名版实名认证的实现难点在于如何得到签名。后来一查,发现很简单,只需安装crypto-js插件,然后按如下实现即可。
import CryptoJS from 'crypto-js' import hmacSha1 from 'crypto-js/hmac-sha1' import Base64 from 'crypto-js/enc-base64' const sign = Base64.stringify(hmacSha1('appIdidNumname', appKey))
Vue项目接入
因为当前web端项目采用的是Vue框架,网络请求用的是axios。
所以一开始,用如下方法尝试请求第三方接口。
import axios from 'axios' realNameCertify() { const formData = new FormData() formData.append('appId', appId) formData.append('name', name) formData.append('idNum', idNum) formData.append('sign', sign) const url = 'https://api.253.com/open/idcard/id-card-auth-valid' return axios.post(url,{data: formData},{ headers: { 'Content-Type': 'multipart/form-data', } }) }
总感觉哪里不对,果然一执行代码,报错跨域了。
vue.config.js文件中已设置了代理公司后端服务的地址,那第三方的地址如何代理呢?
一查,才发现可以设置多个代理。
proxy: { '/api': { target: '', ws: true, pathRewrite: { '^/api': '', }, }, '/a': { target: 'https://api.253.com/', ws: true, pathRewrite: { '^/a': '', }, },
然后将网路请求处的代码改造如下:
import axios from 'axios' import qs from 'qs' const service = axios.create({ baseURL: '/a', timeout: 20000, // request timeout paramsSerializer: (params) => qs.stringify(params, { indices: false }), }) realNameCertify() { const formData = new FormData() formData.append('appId', appId) formData.append('name', name) formData.append('idNum', idNum) formData.append('sign', sign) const url = 'https://api.253.com/open/idcard/id-card-auth-valid' return service({ url: 'open/idcard/id-card-auth/vs', method: 'POST', data: formData, headers: { 'Content-Type': 'multipart/form-data', } }) }
再一执行代码,第三方实名认证接口终于请求成功了。
微信小程序接入
想象着,小程序的接入应该比Vue项目的接入更容易,因为不需要设置代理,不需要axios,签名同样可以用crypto-js插件得到,所以写下了如下代码:
realNameCertify() { const that = this const formData = new FormData() formData.append('appId', appId) formData.append('name', name) formData.append('idNum', idNum) formData.append('sign', sign) const headerRequest = { 'content-type': 'multipart/form-data', } wx.request({ url: 'https://api.253.com/open/idcard/id-card-auth/vs', data:formData, header: headerRequest, method: 'POST', success: function (res) { }, fail: function (msg) { } }) },
然后执行代码,结果报错如下:
再一查,才知道,微信本身没有FormData
对象,无法使用 new FormData
。
无耐用普通对象试了一下,结果第三方接口不接收。
从上面的错误信息们可以看到,大概就是说multipart少了对参数boundary的初始化,其实就是boundary这个参数没有给值。遂将Context-type的值调整为multipart/form-data;boundary=ebf9f03029db4c2799ae16b5428b06bd1,其中boundary里的值可以随便改。
再一执行代码,还是报上述同样的错。
看来,第三方实名认证接口只能接收FormData对象数据。
手写FormData对象
那么,只能想办法然手动搞一个FormData
对象了。怎么手写一个FormData
对象,毫无头绪,只能求助于网络大佬了。果然,网上真有。
// formData.js const mimeMap = require('./mimeMap') function FormData(){ //此处省略n行代码 ... ... ... String.prototype.utf8CodeAt = function(i) { var str = this; ... ... .... }; module.exports = FormData;
const mimeMap = { "0.001": "application/x-001", "0.323": "text/h323", "0.907": "drawing/907", ... ... ... } module.exports = mimeMap;
详细写法可参考博文《微信小程序怎样创建formdata对象,并通过 wx.request 发送file文件》。
在项目中尝试用了一下:
const hmacSha1 = require('../../../../utils/crypto-js/hmac-sha1') const Base64 = require('../../../../utils/crypto-js/enc-base64') const FormData = require('../../../../utils/formData/formData') realNameCertify() { const that = this const sign = Base64.stringify(hmacSha1('appIdidNumname', appKey)) let formData = new FormData(); formData.append('appId', appId) formData.append('name', that.data.formData.nickName) formData.append('idNum', that.data.formData.idCard) formData.append('sign', sign) const data = formData.getData(); const headerRequest = { 'content-type': data.contentType, } wx.request({ url: 'https://api.253.com/open/idcard/id-card-auth/vs', data:data.buffer, header: headerRequest, method: 'POST', success: function (res) { }, fail: function (msg) { } }) },
然后执行代码,显示第三方实名认证接口接入成功了。
本以为到此可以结束了。然而,但是,又出现了新问题。
隐藏的问题
实名认证功能开发完成后,开始了自测,在自测过程中,忽然发现一个页面的数据不能正常渲染了,控制台报错如下:
本想着这个问题是页面历史遗留问题,遂找写此代码的小伙伴一起看一下。结果,小伙伴查看后告诉我,他那边可以正常显示。
然后,我切换到上一个版本,也能正常显示,而这个版本只有新增实名认证功能代码,那么只能是这些新写的代码影响了。
但是,实名认证和上面报错的页面完全没关系,为什么会影响呢?实在有点匪夷所思。
后来,经过多番排查,终于找到原因了。是被下面一段代码影响了。
// formData.js String.prototype.utf8CodeAt = function(i) { ... ... };
原来,在formData.js中,在String原型中,增加了一个方法,而报错的页面正好循环了字符串,并调用了String内置的方法。
for (let i in inputValue) { let val = inputValue[i]; console.log('val--',val) let name = arrSearch(val, PinYinObj); if (reg.test(val)) { pinYinCode += val.toUpperCase(); // 报错行 } else if (name !== false) { pinYinCode += name; } }
我们来看一下inputValue和val值的打印:
发现val值多了一个方法,而这个方法再调用字符串的toUpperCase()方法时当然会造成报错。
问题找到了,那怎么解决呢?
改造formData.js
既然在String.prototype上增加utf8CodeAt方法会造成问题,那么我们就只能想办法将utf8CodeAt方法从String.prototype上剥离出来,且不能影响功能实现。
所以我们可以这样改写:
// 改写前: String.prototype.utf8CodeAt = function(i) { var str = this }; dataString.utf8CodeAt(i) // 改写后 function utf8CodeAt(str,i) { } utf8CodeAt(dataString,i)
改写后,执行代码,之前报错的页面终于可以正常渲染数据了。
总结
虽然过程比较曲折,但经此一役,咱的知识库又更新了:
- 同一个项目中proxy代理可以设置多个
- 微信小程序本身没有
FormData
对象,无法使用new FormData()
- 微信小程序中可以手写
FormData
对象 - 在js内置对象的原型上增加属性和方法要慎重
嗯,又是收获满满的一天!