在 react 项目中使用微信 JS-SDK 填坑笔记

JavaScript064

在 react 项目中使用微信 JS-SDK 填坑笔记,第1张

现在 android 的微信(6.3.31)已经修好了,需要按照文档说的传入当前页面的url,也就是没吃豆重新 config 一次,但是苹果的微信只要在进入页面的时候 config 一次就可以了,微信管接口的估计是个锤子。

在做图片上传的时候,图片太大老是上传失败,在产品汪(☁️)的建议下用了微信的 JS-SDK,微信会处理压缩,而且还有顺带连预览也解决了,好开心。

后来一发布,整组人感觉不好了。主要问题出现在 url 的配置上。

微信的 官方文档 是这样说的:

实际测试中发现,配置的 url 并不是调用微信 js 的时候所在页面的地址(也就是通过 location.href 获得的地址),而是在进入到网站的第一个页面的地址,这是第一个问题

第二问题是在苹果手机的微信上面,只要调用 wx.config 一次就可以了,但是在 android 的手机,在页面跳转之后,要重新调用 wx.config (url 依然是进入网站的第一个页面的地址,要保存在一个变量里面),"X5 浏览器", 我他妈就呵呵了。如果用了 react-router 的话,直接在 Route 组件上绑定一个 onChange 方法就可以了

1. 微信JSSDK使用步骤简介

我们既然是在做基于微信的开发,当然就离不开微信的开发文档了。开始之前希望大家能先去看下《微信JS-SDK说明文档》。那么我们怎么样才能用上微信的JSSDK呢?以下基本步骤就是基于该文档的。

需要注意的是,如果本人下面的描述你看的有点云里雾里的话,我建议你:

回头看下本系列《小白学react》的历史基础文章,特别是《小白学react之altjs的Action和Store》以及《小白学react之打通React Component任督二脉》,或/和:

直接跳过我的描述,在文章后面下载最新的源码,先阅读源码,碰到问题再反过来看文章的描述。

步骤一:绑定域名

先登录微信公众平台进入“公众号设置”的“功能设置”里填写“JS接口安全域名”。

备注:登录后可在“开发者中心”查看对应的接口权限。

这里绑定的时候需要注意不要带前面的http协议头。写法跟上一篇《小白学react之网页获取微信用户信息》中的网页回调域名设置的写法是一样的。

步骤二:引入JS文件

在需要调用JS接口的页面引入如下JS文件,(支持https):

请注意,如果你的页面启用了https,务必引入 :

,否则将无法在iOS9.0以上系统中成功使用JSSDK

因为我们的index.html是通过ejs模版生成的,所以我们只需要在我们的index.ejs中的body部分末尾加入相应的微信jssdk库的引用就好了。

   <% for (var chunk in htmlWebpackPlugin.files.chunks) { %>     <script src="<%= htmlWebpackPlugin.files.chunks[chunk].entry %>"></script>

     <script src="jweixin-1.0.0.js"></script>

   <% } %>

步骤三:通过config接口注入权限验证配置

所有需要使用JS-SDK的页面必须先注入配置信息,否则将无法调用(同一个url仅需调用一次,对于变化url的SPA的web app可在每次url变化时进行调用,目前Android微信客户端不支持pushState的H5新特性,所以使用pushState来实现web app的页面会导致签名失败,此问题会在Android6.2中修复)。

wx.config({

   debug: true, // 开启调试模式,调用的所有api的返回值会在客户端alert出来,若要查看传入的参数,可以在pc端打开,参数信息会通过log打出,仅在pc端时才会打印。

   appId: '', // 必填,公众号的唯一标识

   timestamp: , // 必填,生成签名的时间戳

   nonceStr: '', // 必填,生成签名的随机串

   signature: '',// 必填,签名,见附录1

   jsApiList: [] // 必填,需要使用的JS接口列表,所有JS接口列表见附录2})

这一步的关键是如何生成正确的签名。这里微信jssdk文档中有给出不同语言版本的签名算法示例大家可以参考。往下我们也会就github上的一个签名算法的封装进行学习。

在我们的实战过程中,签名会在服务器端发生。

react客户端会像之前的获取微信用户信息一样,通过一个restfulApi调用服务器端的api,然后由服务器来生成对应的签名,然后将签名信息返回给客户端。

客户端获取到上面wx.config示例代码中的签名相关信息后,就会调用一个Alt的Action,来触发将获取回来的信息保存到一个跟该Action绑定的jssdk状态管理的Store里面。然后就可以调用wx.config来配置我们需要用到的JS接口列表了。

注意这里的wx这个对象是通过上一步的JS文件引入进来的。我们在react的客户端代码中可以直接通过window.wx对其进行引用:

window.wx.config({

 ...

})

步骤四:通过ready接口处理成功验证

wx.ready(function(){    // config信息验证后会执行ready方法,所有接口调用都必须在config接口获得结果之后,config是一个客户端的异步操作,所以如果需要在页面加载时就调用相关接口,则须把相关接口放在ready函数中调用来确保正确执行。对于用户触发时才调用的接口,则可以直接调用,不需要放在ready函数中。})

随后,react客户端负责jssdk状态管理的store会调用wx.ready来监听config配置是否成功,如果成功的话,就会将该store的一个ready状态设置成true。

这样的话,通过AltContainer绑定了该store的相应的Component组件就能知道响应的jssdk的api是否已经准备就绪,可以进行调用了。

步骤五:通过error接口处理失败验证

wx.error(function(res){    // config信息验证失败会执行error函数,如签名过期导致验证失败,具体错误信息可以打开config的debug模式查看,也可以在返回的res参数中查看,对于SPA可以在这里更新签名。})

同理,如果如果配置失败的话,那么就在wx.error这个监听接口中将ready状态设置成false。

2. 生成签名

如前面所述,我们需要用到jssdk的页面必须要要注入调用到的api的配置信息。

wx.config({

   debug: true, // 开启调试模式,调用的所有api的返回值会在客户端alert出来,若要查看传入的参数,可以在pc端打开,参数信息会通过log打出,仅在pc端时才会打印。

   appId: '', // 必填,公众号的唯一标识

   timestamp: , // 必填,生成签名的时间戳

   nonceStr: '', // 必填,生成签名的随机串

   signature: '',// 必填,签名,见附录1

   jsApiList: [] // 必填,需要使用的JS接口列表

而注入JS接口到页面时,我们可以看到,还需要使用到其他一些信息。其中appId我们可以从公众号管理后台获得。signature是跟所访问页面的url关联的一个签名,它有专门的一套算法来生成。另外两个参数nonceStr和signature都是在签名的过程中生成的。

这里通过wx.config传进去这些参数,主要是为了让微信去判断我们生成的签名和微信通过这些信息生成的签名是否一致,如果不一致的话,那么注入到该页面的jsApiListj就失败。

那么我们在服务器这边的签名算法是如何的呢?根据微信开发文档我们需要提供以下4个参数,然后通过sha1算发来生成对应的签名:

noncestr:一个随机字符串,我们随便填写

jsapi_ticket:jsapi_ticket是公众号用于调用微信JS接口的临时票据

timestamp: 签名时间戳。注意这个时间戳需要和上面传入wx.config的时间戳一致

url: 调用JS接口页面的完整URL。我们可以从react客户端通过location.href获得,并传给服务器端

那么这里主要需要解决的就是如何获得jsapi_ticket这个临时票据了。

根据文档的描述,我们可以通过以下这个接口获得:

cgi-bin/ticket/getticket?access_token=ACCESS_TOKEN&type=jsapi

从中可以看到,我们调用这个接口首先要获得一个access_token。这里微信也有相应的api来处理。

cgi-bin/token?grant_type=client_credential&appid=APPID&secret=APPSECRET

这里需要用到我们的微信公众号的appId和secret,这些我们都是已知的,所以好办。

那么,也就是说,我们其实只需要提供我们的微信公众号的appId和secret,就能获取到access_token,从而就会获得我们需要的jsapi_ticket。

这里我们参考下github上一个示例(wechat-sdk-demo )的签名的实现。其传入的参数有两个,其中:

url:  react客户端传进来的需要注入jsapi的页面url

callback: 一个回调函数,接受一个json格式的参数。主要是用来将生成的签名信息等回传给上层调用函数。

const config = {    grant_type: 'client_credential',    appid: 'xxxx',    secret: "xxxxx",    noncestr:'Wm3WZYTPz0wzccnW',    accessTokenUrlin.qq.com/cgi-bin/token',    ticketUrl:'com/cgi-bin/ticket/getticket',

}

exports.sign = function (url,callback) {  var noncestr = config.noncestr,

       timestamp = Math.floor(Date.now()/1000), //精确到秒

   ...

       request(config.accessTokenUrl + '?grant_type=' + config.grant_type + '&appid=' + config.appid + '&secret=' + config.secret ,function(error, response, body){            if (!error &&response.statusCode == 200) {                var tokenMap = JSON.parse(body)

               request(config.ticketUrl + '?access_token=' + tokenMap.access_token + '&type=jsapi', function(error, resp, json){                    if (!error &&response.statusCode == 200) {                        var ticketMap = JSON.parse(json)

                       cache.put('ticket',ticketMap.ticket,config.cache_duration) //加入缓存

                       callback({                            noncestr:noncestr,                            timestamp:timestamp,                            url:url,                            jsapi_ticket:ticketMap.ticket,                            signature:sha1('jsapi_ticket=' + ticketMap.ticket + '&noncestr=' + noncestr + '&timestamp=' + timestamp + '&url=' + url)

                       })

                   }

               })

           }

       })

   }

}

这里的流程和我们刚才描述的并无二致。首先是通过appId和secret获得调用获取jsapi_ticket的access_token,然后通过该access_token获得我们签名需要用到的jsapi_ticket。noncestr我们是提前随便填写好的。timestamp的算法也比较简单。

最后就是通过sha1这个库提供的方法,将jsapi_ticket,noncestr,timestamp和页面url进行sha1签名,然后将以上这些信息通过callback返回给上层调用函数。

那么我们往下看下我们的上层调用函数。其实就是我们的express路由:

app.get("/api/signature", function(req,res) {    const url = req.query.url.split('#')[0]

   signature.sign(url,function(signatureMap){

       signatureMap.appId = wechat_cfg.appid

       res.send(signatureMap)

   })

})

根据微信开发文档需求,我们首先需要将传进来的url的锚点后面的数据给去掉,保留前面的有效部分。

然后就是调用上面的sign方法来生成签名。上面的签名方法最后传进来的json数据就是这里的signatureMap。我们最终会将这些数据发送回react客户端。

同时,通过上面的wx.config的示例,我们知道还需要用到微信公众号的appId。所以这里一并将其放到signatureMap中进行返回。

那么到此为止,react客户端调用服务端的"/api/signature"返回的数据示例如下:

{

 noncestr: 'Wm3WZYTPz0wzccnW',

 timestamp: 1476873698,

 url: 'com/?code=001kGsd30xcm7F1PAFf305Uud30kGsdr&state=',

 jsapi_ticket: 'sM4AOVdWfPE4DxkXGEs8VBqyVbs-TKGYp4d_ZSQa0Q5WvvMUPNQ6XGpyEcgKOD_xID_GrMCaalSmIF9JbrGaOg',

 signature: '9268ffaf4b9eb0d296fcfefe3d2724118aa05e3c'

}

3.  客户端获取签名信息

3.1 获取签名信息并注入jssdk

和之前的获取微信用户信息一样,我们这里会建立一个新的Source文件WechatSdkSource.js来调用远程服务器的"/api/signature"接口:

var WechatSdkSource = {

 fetchSignatureMap() {    return {

     remote(state,url) {        return co(function *() {          let signatureMap = null         const getSignatureMapUrl = `/api/signature`         try {            let result = yield request.get(getSignatureMapUrl).query({url:url})

           signatureMap = result.body

         } catch (e) {

           signatureMap = null

         }          //console.log("userInfo:", userInfo)

         return signatureMap

       })

     },

     local() {        // Never check locally, always fetch remotely.

       return null

     },      success: WechatSdkActions.updateSignatureMap,      error: WechatSdkActions.getSignatureMapFailed,      loading: WechatSdkActions.getSignatureMap,

   }

 }

}

这里传进来的url由下面将要谈及的上层函数所生成。整个流程就没有什么好说的了,说白了就是通过相应的库发送一个带有url的query参数的请求到服务器端来请求签名信息,相信有跟着这个系列文章的朋友都是很清楚的了。

最终请求成功返回的时候就会调用WechatSdkActions的updateSignatureMap这个Action。

var alt = require('../alt')module.exports  = alt.generateActions(    'updateSignatureMap',    'getSignatureMap',    'getSignatureMapFailed',

)

而这个action就会触发所监听的WechatSdkStore的onUpdateSignatureMap这个回调。

class WechatSdkStore {  constructor() {    this.signatureMap = []   this.errorMessage = null   this.ready = false   this.bindActions(WechatSdkActions)   this.exportAsync(WechatSdkSource)