介绍使用koa框架实现的一个微信 JS-SDK 调用示例
# 前置准备
- 一个测试公众号
- 一台服务器(带域名)
登录测试公众号后台添加JS安全域名
# koa项目开发
微信JS-SDK权限验证的签名必须在服务器端实现,签名用的url必须是调用JS接口页面的完整URL,所以这里决定用koa来同时完成页面渲染及生成签名所需验证配置。
项目依赖库如下:
- koa-router
- request-promise
- koa-views
- koa-static
- koa2-cors
- memory-cache
- sha1
下面介绍一下核心的签名方法:
主要就是请求全局token后获取jsapi_ticket来获取签名。
const config = require('./config');
async function sign(url) {
let sig = {},
noncestr = config.noncestr,
timestamp = Math.floor(Date.now() / 1000), //精确到秒
jsapi_ticket;
if (cache.get('ticket')) {
jsapi_ticket = cache.get('ticket');
sig = {
appId: config.appid,
noncestr: noncestr,
timestamp: timestamp,
url: url,
jsapi_ticket: jsapi_ticket,
signature: sha1(
'jsapi_ticket=' +
jsapi_ticket +
'&noncestr=' +
noncestr +
'×tamp=' +
timestamp +
'&url=' +
url
),
};
} else {
// 获取 token
let tokenRes = await rp({
uri:
config.accessTokenUrl +
'?grant_type=' +
config.grant_type +
'&appid=' +
config.appid +
'&secret=' +
config.secret,
});
tokenRes = JSON.parse(tokenRes);
// 获取 ticket
let ticketRes = await rp({
uri:
config.ticketUrl +
'?access_token=' +
tokenRes.access_token +
'&type=jsapi',
});
var ticketMap = JSON.parse(ticketRes);
// 加入缓存
cache.put('ticket', ticketMap.ticket, config.cache_duration);
sig = {
appId: config.appid,
noncestr: noncestr,
timestamp: timestamp,
url: url,
jsapi_ticket: ticketMap.ticket,
signature: sha1(
'jsapi_ticket=' +
ticketMap.ticket +
'&noncestr=' +
noncestr +
'×tamp=' +
timestamp +
'&url=' +
url
),
};
}
return sig;
}
之后我们只要在koa的router中定义一个路由去生成签名就行了
router.get('/sig', async (ctx, next) => {
try {
//获取当前url
let url =
ctx.request.protocol + '://' + ctx.request.host + ctx.request.originalUrl;
if (ctx.query.url) {
url = ctx.query.url;
}
ctx.data = await sign(url);
} catch (e) {
console.log(e);
}
await next();
});
demo页面我们可以使用koa-views及ejs来渲染:
- 页面放到views目录
- 页面需要引用的css及js可以放到static目录下面
参考代码如下:
const views = require('koa-views');
const static = require('koa-static');
app.use(static(path.join(__dirname, './static')));
app.use(
views(path.join(__dirname, './views'), {
extension: 'ejs',
})
);
views目录下新建页面 index.ejs,写入sdk调用前端 demo 页面,主要内容如下:
- 引入 jweixin-1.6.0.js 文件
<script src="http://res.wx.qq.com/open/js/jweixin-1.0.0.js"></script>
- 通过config接口注入权限验证配置
wx.config({
debug: true, // 开启调试模式,调用的所有api的返回值会在客户端alert出来,若要查看传入的参数,可以在pc端打开,参数信息会通过log打出,仅在pc端时才会打印。
appId: '', // 必填,公众号的唯一标识
timestamp: , // 必填,生成签名的时间戳
nonceStr: '', // 必填,生成签名的随机串
signature: '',// 必填,签名,见附录1
jsApiList: [] // 必填,需要使用的JS接口列
});
- ready接口处理成功验证
wx.ready(function(){
//config信息验证后会执行ready方法,所有接口调用都必须在config接口获得结果之后,config是一个客户端的异步操作,所以如果需要在页面加载时就调用相关接口,则须把相关接口放在ready函数中调用来确保正确执行。对于用户触发时才调用的接口,则可以直接调用,不需要放在ready函数中。
});
- error接口处理失败验证
wx.error(function(res){
//config信息验证失败会执行error函数,如签名过期导致验证失败,具体错误信息可以打开config的debug模式查看,也可以在返回的res参数中查看,对于SPA可以在这里更新签名。
});
完整页面代码如下:
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
<meta charset="utf-8" />
<title>微信JS-SDK Demo</title>
<meta
name="viewport"
content="width=device-width, initial-scale=1, user-scalable=0"
/>
<link
rel="stylesheet"
href="css/style.css"
/>
</head>
<body ontouchstart="">
<div class="wxapi_container">
<div class="wxapi_index_container">
<ul class="label_box lbox_close wxapi_index_list">
<li class="label_item wxapi_index_item">
<a class="label_inner" href="#menu-basic">基础接口</a>
</li>
<li class="label_item wxapi_index_item">
<a class="label_inner" href="#menu-share">分享接口</a>
</li>
<li class="label_item wxapi_index_item">
<a class="label_inner" href="#menu-image">图像接口</a>
</li>
<li class="label_item wxapi_index_item">
<a class="label_inner" href="#menu-voice">音频接口</a>
</li>
<li class="label_item wxapi_index_item">
<a class="label_inner" href="#menu-smart">智能接口</a>
</li>
<li class="label_item wxapi_index_item">
<a class="label_inner" href="#menu-device">设备信息接口</a>
</li>
<li class="label_item wxapi_index_item">
<a class="label_inner" href="#menu-location">地理位置接口</a>
</li>
<li class="label_item wxapi_index_item">
<a class="label_inner" href="#menu-webview">界面操作接口</a>
</li>
<li class="label_item wxapi_index_item">
<a class="label_inner" href="#menu-scan">微信扫一扫接口</a>
</li>
<li class="label_item wxapi_index_item">
<a class="label_inner" href="#menu-shopping">微信小店接口</a>
</li>
<li class="label_item wxapi_index_item">
<a class="label_inner" href="#menu-card">微信卡券接口</a>
</li>
<li class="label_item wxapi_index_item">
<a class="label_inner" href="#menu-pay">微信支付接口</a>
</li>
</ul>
</div>
<div class="lbox_close wxapi_form">
<h3 id="menu-basic">基础接口</h3>
<span class="desc">判断当前客户端是否支持指定JS接口</span>
<button class="btn btn_primary" id="checkJsApi">checkJsApi</button>
<h3 id="menu-share">分享接口</h3>
<span class="desc"
>获取“分享到朋友圈”按钮点击状态及自定义分享内容接口</span
>
<button class="btn btn_primary" id="onMenuShareTimeline">
onMenuShareTimeline
</button>
<span class="desc"
>获取“分享给朋友”按钮点击状态及自定义分享内容接口</span
>
<button class="btn btn_primary" id="onMenuShareAppMessage">
onMenuShareAppMessage
</button>
<span class="desc">获取“分享到QQ”按钮点击状态及自定义分享内容接口</span>
<button class="btn btn_primary" id="onMenuShareQQ">
onMenuShareQQ
</button>
<span class="desc"
>获取“分享到腾讯微博”按钮点击状态及自定义分享内容接口</span
>
<button class="btn btn_primary" id="onMenuShareWeibo">
onMenuShareWeibo
</button>
<span class="desc"
>获取“分享到QZone”按钮点击状态及自定义分享内容接口</span
>
<button class="btn btn_primary" id="onMenuShareQZone">
onMenuShareQZone
</button>
<h3 id="menu-image">图像接口</h3>
<span class="desc">拍照或从手机相册中选图接口</span>
<button class="btn btn_primary" id="chooseImage">chooseImage</button>
<span class="desc">预览图片接口</span>
<button class="btn btn_primary" id="previewImage">previewImage</button>
<span class="desc">上传图片接口</span>
<button class="btn btn_primary" id="uploadImage">uploadImage</button>
<span class="desc">下载图片接口</span>
<button class="btn btn_primary" id="downloadImage">
downloadImage
</button>
<h3 id="menu-voice">音频接口</h3>
<span class="desc">开始录音接口</span>
<button class="btn btn_primary" id="startRecord">startRecord</button>
<span class="desc">停止录音接口</span>
<button class="btn btn_primary" id="stopRecord">stopRecord</button>
<span class="desc">播放语音接口</span>
<button class="btn btn_primary" id="playVoice">playVoice</button>
<span class="desc">暂停播放接口</span>
<button class="btn btn_primary" id="pauseVoice">pauseVoice</button>
<span class="desc">停止播放接口</span>
<button class="btn btn_primary" id="stopVoice">stopVoice</button>
<span class="desc">上传语音接口</span>
<button class="btn btn_primary" id="uploadVoice">uploadVoice</button>
<span class="desc">下载语音接口</span>
<button class="btn btn_primary" id="downloadVoice">
downloadVoice
</button>
<h3 id="menu-smart">智能接口</h3>
<span class="desc">识别音频并返回识别结果接口</span>
<button class="btn btn_primary" id="translateVoice">
translateVoice
</button>
<h3 id="menu-device">设备信息接口</h3>
<span class="desc">获取网络状态接口</span>
<button class="btn btn_primary" id="getNetworkType">
getNetworkType
</button>
<h3 id="menu-location">地理位置接口</h3>
<span class="desc">使用微信内置地图查看位置接口</span>
<button class="btn btn_primary" id="openLocation">openLocation</button>
<span class="desc">获取地理位置接口</span>
<button class="btn btn_primary" id="getLocation">getLocation</button>
<h3 id="menu-webview">界面操作接口</h3>
<span class="desc">隐藏右上角菜单接口</span>
<button class="btn btn_primary" id="hideOptionMenu">
hideOptionMenu
</button>
<span class="desc">显示右上角菜单接口</span>
<button class="btn btn_primary" id="showOptionMenu">
showOptionMenu
</button>
<span class="desc">关闭当前网页窗口接口</span>
<button class="btn btn_primary" id="closeWindow">closeWindow</button>
<span class="desc">批量隐藏功能按钮接口</span>
<button class="btn btn_primary" id="hideMenuItems">
hideMenuItems
</button>
<span class="desc">批量显示功能按钮接口</span>
<button class="btn btn_primary" id="showMenuItems">
showMenuItems
</button>
<span class="desc">隐藏所有非基础按钮接口</span>
<button class="btn btn_primary" id="hideAllNonBaseMenuItem">
hideAllNonBaseMenuItem
</button>
<span class="desc">显示所有功能按钮接口</span>
<button class="btn btn_primary" id="showAllNonBaseMenuItem">
showAllNonBaseMenuItem
</button>
<h3 id="menu-scan">微信扫一扫</h3>
<span class="desc">调起微信扫一扫接口</span>
<button class="btn btn_primary" id="scanQRCode0">
scanQRCode(微信处理结果)
</button>
<button class="btn btn_primary" id="scanQRCode1">
scanQRCode(直接返回结果)
</button>
<h3 id="menu-shopping">微信小店接口</h3>
<span class="desc">跳转微信商品页接口</span>
<button class="btn btn_primary" id="openProductSpecificView">
openProductSpecificView
</button>
<h3 id="menu-card">微信卡券接口</h3>
<span class="desc">批量添加卡券接口</span>
<button class="btn btn_primary" id="addCard">addCard</button>
<span class="desc">调起适用于门店的卡券列表并获取用户选择列表</span>
<button class="btn btn_primary" id="chooseCard">chooseCard</button>
<span class="desc">查看微信卡包中的卡券接口</span>
<button class="btn btn_primary" id="openCard">openCard</button>
<h3 id="menu-pay">微信支付接口</h3>
<span class="desc">发起一个微信支付请求</span>
<button class="btn btn_primary" id="chooseWXPay">chooseWXPay</button>
</div>
</div>
<script src="http://res.wx.qq.com/open/js/jweixin-1.0.0.js"></script>
<!-- 获取签名的核心 end -->
<script>
/*
* 注意:
* 1. 所有的JS接口只能在公众号绑定的域名下调用,公众号开发者需要先登录微信公众平台进入“公众号设置”的“功能设置”里填写“JS接口安全域名”。
* 2. 如果发现在 Android 不能分享自定义内容,请到官网下载最新的包覆盖安装,Android 自定义分享接口需升级至 6.0.2.58 版本及以上。
* 3. 常见问题及完整 JS-SDK 文档地址:http://mp.weixin.qq.com/wiki/7/aaa137b55fb2e0456bf8dd9148dd613f.html
*
* 开发中遇到问题详见文档“附录5-常见错误及解决办法”解决,如仍未能解决可通过以下渠道反馈:
* 邮箱地址:weixin-open@qq.com
* 邮件主题:【微信JS-SDK反馈】具体问题
* 邮件内容说明:用简明的语言描述问题所在,并交代清楚遇到该问题的场景,可附上截屏图片,微信团队会尽快处理你的反馈。
*/
wx.config({
debug: false,
appId: '<%=signPackage["appId"];%>',
timestamp: '<%=signPackage["timestamp"];%>',
nonceStr: '<%=signPackage["noncestr"];%>',
signature: '<%=signPackage["signature"];%>',
jsApiList: [
'checkJsApi',
'onMenuShareTimeline',
'onMenuShareAppMessage',
'onMenuShareQQ',
'onMenuShareWeibo',
'onMenuShareQZone',
'hideMenuItems',
'showMenuItems',
'hideAllNonBaseMenuItem',
'showAllNonBaseMenuItem',
'translateVoice',
'startRecord',
'stopRecord',
'onVoiceRecordEnd',
'playVoice',
'onVoicePlayEnd',
'pauseVoice',
'stopVoice',
'uploadVoice',
'downloadVoice',
'chooseImage',
'previewImage',
'uploadImage',
'downloadImage',
'getNetworkType',
'openLocation',
'getLocation',
'hideOptionMenu',
'showOptionMenu',
'closeWindow',
'scanQRCode',
'chooseWXPay',
'openProductSpecificView',
'addCard',
'chooseCard',
'openCard',
],
});
</script>
<script src="js/zepto.min.js"></script>
<script src="js/demo.js"></script>
<div id="cli_dialog_div"></div>
</body>
</html>
至此我们就完成了一个简易的koa版调用Demo了,下面我们来测试一下
# Demo 本地调试
一般我们有内网穿透、代理劫持等几种方法去进行本地调试,这里我们使用一个比较常用的方法内网穿透(用你自己熟练的方法就好)来测试。
- 修改项目根目录下的 config.js,换成自己公众号的appid及secrect
- 在项目根目录执行
node app.js
启动服务后浏览器打开如下地址 http://localhost:4000/ (opens new window) 即可看到 Demo 页面 - 在前置准备中我们设置了一个JS接口安全域名,可以基于这个域名做穿透服务来访问我们本地koa项目,添加公网端口映射本地koa项目的端口(这里的例子是4000),然后浏览器访问JS接口安全域名即可看到 Demo 页面
- 使用微信打开JS接口安全域名即可测试使用
项目代码已开源:公众号后台回复 koa-wx-js-sdk 即可获取
# 视频演示
以下是测试视频,仅供参考
# 注意事项
- 签名用的noncestr和timestamp必须与wx.config中的nonceStr和timestamp相同。
- 签名用的url必须是调用JS接口页面的完整URL。
- 出于安全考虑,开发者必须在服务器端实现签名的逻辑。