基于koa实现的微信JS-SDK调用Demo

2021-07-23 10:30:58 浏览数 (1)

介绍使用koa框架实现的一个微信 JS-SDK 调用示例

  • 前置准备
  • koa项目开发
  • Demo 本地调试
  • 视频演示
  • 注意事项
  • 参考资料

# 前置准备

  • 一个测试公众号
  • 一台服务器(带域名)

登录测试公众号后台添加JS安全域名

# koa项目开发

微信JS-SDK权限验证的签名必须在服务器端实现,签名用的url必须是调用JS接口页面的完整URL,所以这里决定用koa来同时完成页面渲染及生成签名所需验证配置。 项目依赖库如下:

  • koa-router
  • request-promise
  • koa-views
  • koa-static
  • koa2-cors
  • memory-cache
  • sha1

下面介绍一下核心的签名方法: 主要就是请求全局token后获取jsapi_ticket来获取签名。

代码语言:javascript复制
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  
          '&timestamp='  
          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  
          '&timestamp='  
          timestamp  
          '&url='  
          url
      ),
    };
  }
  return sig;
}

之后我们只要在koa的router中定义一个路由去生成签名就行了

代码语言:javascript复制
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目录下面

参考代码如下:

代码语言:javascript复制
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 文件
代码语言:javascript复制
<script src="http://res.wx.qq.com/open/js/jweixin-1.0.0.js"></script>
  • 通过config接口注入权限验证配置
代码语言:javascript复制
wx.config({
    debug: true, // 开启调试模式,调用的所有api的返回值会在客户端alert出来,若要查看传入的参数,可以在pc端打开,参数信息会通过log打出,仅在pc端时才会打印。
    appId: '', // 必填,公众号的唯一标识
    timestamp: , // 必填,生成签名的时间戳
    nonceStr: '', // 必填,生成签名的随机串
    signature: '',// 必填,签名,见附录1
    jsApiList: [] // 必填,需要使用的JS接口列
});
  • ready接口处理成功验证
代码语言:javascript复制
wx.ready(function(){
    //config信息验证后会执行ready方法,所有接口调用都必须在config接口获得结果之后,config是一个客户端的异步操作,所以如果需要在页面加载时就调用相关接口,则须把相关接口放在ready函数中调用来确保正确执行。对于用户触发时才调用的接口,则可以直接调用,不需要放在ready函数中。
});
  • error接口处理失败验证
代码语言:javascript复制
wx.error(function(res){
    //config信息验证失败会执行error函数,如签名过期导致验证失败,具体错误信息可以打开config的debug模式查看,也可以在返回的res参数中查看,对于SPA可以在这里更新签名。
});

完整页面代码如下:

代码语言:javascript复制
<!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 本地调试

一般我们有内网穿透、代理劫持等几种方法去进行本地调试,这里我们使用一个比较常用的方法内网穿透(用你自己熟练的方法就好)来测试。

  1. 修改项目根目录下的 config.js,换成自己公众号的appid及secrect
  2. 在项目根目录执行node app.js启动服务后浏览器打开如下地址 http://localhost:4000/ 即可看到 Demo 页面
  3. 在前置准备中我们设置了一个JS接口安全域名,可以基于这个域名做穿透服务来访问我们本地koa项目,添加公网端口映射本地koa项目的端口(这里的例子是4000),然后浏览器访问JS接口安全域名即可看到 Demo 页面
  4. 使用微信打开JS接口安全域名即可测试使用

项目代码已开源:公众号后台回复 koa-wx-js-sdk 即可获取

# 视频演示

以下是测试视频,仅供参考

# 注意事项

  • 签名用的noncestr和timestamp必须与wx.config中的nonceStr和timestamp相同。
  • 签名用的url必须是调用JS接口页面的完整URL。
  • 出于安全考虑,开发者必须在服务器端实现签名的逻辑。

# 参考资料

  • JS-SDK说明文档
  • 官方DEMO页面
  • koa2实现静态资源服务器
  • 测试公众号后台

0 人点赞