express+花生壳 打通微信公众平台JSSDK开发之路

目的: 利用nodejs生成微信权限验证配置。
起因: 看老婆经常线上调微信项目,十分辛苦。萌发了帮她搭建测试环境的想法。由于他们项目使用C#开发,自己不太懂,尝试过几次都没办法通过127.0.0.1访问他们的项目。所以还是转战自己熟悉的js领域。好了,话不多说。开始干活。

  1. 第一步:注册花生壳,获取自己的外网域名, 同时通过花生壳的客户端 ,将开发机映射到外网,记得端口需要映身到80端口,微信公众号开发只支持80端口,这一步需要花费8元钱,可以获得一个1年1g流量的域名。(无截图,没找到mac版的花生壳。)
  2. 第二步:获取appid, appsecret 这两个值 ,在微信公众平台管理页可以获取到
  3. 第三步:设置javascript安全域,微信平台只允许用户在提交的安全局域下的使用微信提供的服务。否则你就只有自己玩自己的,无法获取用户信息,调用地图,卡券等等api.
  4. 第四步:通过config接口注入权限验证配置。这一步也就是我们文章的核心。获取到了合法的配置,我们的JSSDK开发之路也就打通了。

在获取配置之前,我们需要了解到,微信权限配置由6个参数组成:

1
2
3
4
5
6
7
8
wx.config({
debug: true, // 开启调试模式,调用的所有api的返回值会在客户端alert出来,若要查看传入的参数,可以在pc端打开,参数信息会通过log打出,仅在pc端时才会打印。
appId: '', // 必填,公众号的唯一标识
timestamp: , // 必填,生成签名的时间戳,
nonceStr: '', // 必填,生成签名的随机串,自定义
signature: '',// 必填,签名,我们要计算的核心部分
jsApiList: [] // 必填,需要使用的JS接口列表,自己去微信平台查看
});

而我们的最终目的就是生成signature,生成流程如下:

  1. 登陆微信管理平台获取appId,appsecret。
  2. 通过appId,appsecret,获取access_token。
  3. 通过access_token,type(type=”jsapi”固定值),获取到ticket。
  4. 通过timestamp,nonceStr, ticket, url(当前URL),拼接获取用于计算签名的字符串,我叫它signatureString
  5. 利用signatureString,通过sha1加密,生成整个环节最重要的signature。
  6. 组装wx.config需要的六个参数,得到最终的权限验证配置。

了解完生成signature的具体流程,我来简单介绍下在express中生成signature的过程。

  1. 前端页面引入微信的JS, https://res.wx.qq.com/open/js/jweixin-1.0.0.js
  2. 定义获取微信配置的路由,注意方法为get
  3. 获取页面URL,可以通过get请求传入, 也可以写成常量,此URL就是你在微信中访问的页面地址,注:不带#及后面的值 ,需要带上协议。
  4. 后端调用微信端口获取access_token, 微信平台上有开发文档详细介绍接口。
  5. 后端调用微信端口获取ticket,微信平台上有开发文档详细介绍接口。
  6. 将ticket,url, timestamp,nonceStr通过URL键值对方式组装成字符串。
  7. 将上一步组装的字符,进行sha1加密,此处,调用nodejs-crypto模块。我自己封装了一下。
  8. 返回给前端组装好的配置结果。(由于是demo, 此处没有考虑异常场景了)
  9. 前端调用wx.config进行接口注入权限验证配置
  10. 在wx.ready中进行微信服务调用。至此打通JSSDK开发环节。

最后将写好的页面,通过我们的 外网地址 直接在微信上访问即可,至此我们已打通JSSDK开发之路,可以调用微信服务了。

明白权限配置生成的过程,我们可以基于任何语言打通JSSDK,作者擅长于javascript所以选择了基于nodejs的express用作后台开发。

下面是我自己写的demo, 后台生成配置,前端验证配置通过后,简单调用了一下定位服务与导航服务

后台接口代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
<!-- 获取微信配置 -->
//定义获取微信配置的接口路由
router.get('/getWxConfig', function(req, res, next) {
var appid="xxxxxxxxxxxxxxxxxxx",//微信平台可获取
appsecret="xxxxxxxxxxxxxxxxxxx",//微信平台可获取
cgiUrl="https://api.weixin.qq.com/cgi-bin",
url=req.query.url; //前端传入的url

//获取token
communicator.get(cgiUrl+"/token?grant_type=client_credential&appid="+appid+"&secret="+appsecret,function(data){
//获取ticket
communicator.get(cgiUrl+"/ticket/getticket?access_token="+data.access_token+"&type=jsapi",function(data){
var timestamp=new Date().getTime(), //时间戳
nonceStr="xxxxxx", //自定义的随机码,用于加密
ticket=data.ticket, //用于生成签名的票据

//生成用于生成签名的字符串
signatureString="jsapi_ticket="+ticket+"&noncestr="+nonceStr+"&timestamp="+timestamp+"&url="+url,
//进行sha1加密,此处核心是调用的node-crypto,出于方便自己封装了一层。
signature=new cipher().encrypt(signatureString),

//定义回传给前端wx.confing的权限验证配置。
wxConfig={
appId: appid,
timestamp: timestamp,
nonceStr: nonceStr,
signature: signature,
jsApiList: [
"onMenuShareTimeline",
"onMenuShareAppMessage",
"onMenuShareQQ",
"onMenuShareWeibo",
"onMenuShareQZone",
"startRecord",
"stopRecord",
"onVoiceRecordEnd",
"playVoice",
"pauseVoice",
"stopVoice",
"onVoicePlayEnd",
"uploadVoice",
"downloadVoice",
"chooseImage",
"previewImage",
"uploadImage",
"downloadImage",
"translateVoice",
"getNetworkType",
"openLocation",
"getLocation",
"hideOptionMenu",
"showOptionMenu",
"hideMenuItems",
"showMenuItems",
"hideAllNonBaseMenuItem",
"showAllNonBaseMenuItem",
"closeWindow",
"scanQRCode",
"chooseWXPay",
"openProductSpecificView",
"addCard",
"chooseCard",
"openCard"
]
};
//响应给前端配置对象
res.json(wxConfig);
});
});
});

前端代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
$.get("/api/getWxConfig?url="+encodeURIComponent(location.href.split('#')[0]),function(config){
wx.config(config);
wx.ready(function(){
//调用微信定位服务
wx.getLocation({
type: 'wgs84', // 默认为wgs84的gps坐标,如果要返回直接给openLocation用的火星坐标,可传入'gcj02'
success: function (res) {
//alert(JSON.stringify(res));
var latitude = res.latitude; // 纬度,浮点数,范围为90 ~ -90
var longitude = res.longitude; // 经度,浮点数,范围为180 ~ -180。
//打开地图导航程序,进行导航。呵呵,此处为demo, 本地导航到本地,仅为效果展示
wx.openLocation({
latitude: latitude, // 纬度,浮点数,范围为90 ~ -90
longitude: longitude, // 经度,浮点数,范围为180 ~ -180。
name: '彩虹之国', // 位置名
address: '我的家', // 地址详情说明
scale: 28, // 地图缩放级别,整形值,范围从1~28。默认为最大
infoUrl: 'http://www.baidu.com' // 在查看位置界面底部显示的超链接,可点击跳转
});
}
});
});
wx.error(function(error){
//配置出错,弹出报错信息
alert(JSON.stirngify(error));
});
});