Nodejs获取微信签名并使用JSSDK

上一篇咱们讲了基本的准备工做,接下来,进入实战,因为楼主我并无备案过的域名(穷,没钱,没办法哈),还好, 一直通不过签名验证,微信比较人性化,提供测试号,能够测大部分的接口,而且设置JS接口安全域名,没有限制,能够写任何地址,哪怕是localhost:9999也是能够的。javascript

一、接口测试号申请

因为用户体验和安全性方面的考虑,微信公众号的注册有必定门槛,某些高级接口的权限须要微信认证后才能够获取。php

因此,为了帮助开发者快速了解和上手微信公众号开发,熟悉各个接口的调用,微信推出了微信公众账号测试号,经过手机微信扫描二维码便可得到测试号,在这个测试号里面能够模拟各类操做,好比分享啥的,很容易经过验证。html

微信JSSDK开发文档前端

进入微信公众账号测试号申请系统java

二、获取 access_token 访问令牌

access_token(有效期7200秒,开发者必须在本身的服务全局缓存access_token`)node

https请求方式: GET
https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid=APPID&secret=APPSECRET
复制代码

参数说明ios

参数 是否必须 说明
grant_type 获取access_token填写client_credential
appid 第三方用户惟一凭证
secret 第三方用户惟一凭证密钥,即appsecret

返回说明web

正常状况下,微信会返回下述JSON数据包给公众号:ajax

{"access_token":"ACCESS_TOKEN","expires_in":7200}
复制代码

实战项目代码:redis

获取 access_token

config/index.json:

api/accessToken.js

// 获取 access_token
const config = require('../config/index.json'); // 配置数据
const axios = require('axios'); // 请求api
const CircularJSON = require('circular-json');

// (设置 | 获取)缓存方法
const cache = require('../utils/cache');

module.exports = getAccessToken = (res) => {

  const fetchUrl = `${config.getAccessToken}?grant_type=client_credential&appid=${config.appid}&secret=${config.appsecret}`;
  // console.log(fetchUrl, config);

  // 获取缓存
  cache.getCache('access_token', function (cacheValue) {
    // 缓存存在
    if (cacheValue) {
      const result = CircularJSON.stringify({
        access_token: cacheValue,
        from: 'cache'
      });
      res.send(result);
    } else {
      // 调取微信api
      axios.get(fetchUrl).then(response => {
        let json = CircularJSON.stringify(response.data);
				res.send(json);
        // 设置缓存
        if (response.data.access_token) {
          cache.setCache('access_token', response.data.access_token)
        }
      }).catch(err => {
        console.log('axios occurs ', err);
      });
    }
  });

};

复制代码

这里用的 axios请求微信api,获取 access_token;

因为access_token 只有7200秒有效时间,而且限制一天最多调2000 次,因此中控服务器最好做缓存,这里使用的 node-cache,作了access_token的缓存,而且删除的缓存的时间也设置的是 7200s,这样在 access_token失效的时候,node缓存也会被删除。

utils/cache.js

// node-cache 保存和获取缓存

const NodeCache = require("node-cache");
const myCache = new NodeCache({
  stdTTL: 7200, // 缓存过时时间
  checkperiod: 120 // 按期检查时间
});


// 设置缓存
var setCache = function (key, value) {
  // 设置缓存
  myCache.set(key, value, function (err, success) {
    if (!err && success) {
      console.log(key + "保存成功", value);
    }
  });
};

// 获取缓存
var getCache = function (key, callback) {
  // 读取缓存
  myCache.get(key, function (err, value) {
    if (!err) {
      if (value) {
        console.log(`存在于缓存中${key}=${value}`);
        callback(value);
      } else {
        console.log(`${key} not found in node-cache`);
        callback();
      }
    } else {
      console.log('get ' + key + ' cache occurs error =', err);
    }
  });
};



module.exports = {
  setCache,
  getCache
}

复制代码

node-cache只能存活于当前进程里面,若是当前node命令被重启,将会从新去请求微信服务器,因此不太适合。

这里其实最好存在 redis数据库里,

路由设置:

app.js

const express = require('express');
const api = require('./api');
const path = require('path');
const app = express();

// accessToken 获取token
app.get('/getAccessToken', (req, res) => {
  api.accessToken(res);
});

....
复制代码

结果:

这是第一次请求,access_token从微信服务器获取最初的数据。

接下来是第二次请求,access_token将会缓存中读取。

三、获取 jsapi_ticket临时票据

生成签名以前必须先了解一下jsapi_ticketjsapi_ticket是公众号用于调用微信JS接口临时票据。正常状况下,jsapi_ticket的有效期为7200秒,经过access_token来获取。因为获取jsapi_ticket的api调用次数很是有限,频繁刷新jsapi_ticket会致使api调用受限,影响自身业务,开发者必须在本身的服务全局缓存jsapi_ticket

用上一步拿到的access_token采用http GET方式请求得到jsapi_ticket(有效期7200秒,开发者必须在本身的服务全局缓存jsapi_ticket):<https://api.weixin.qq.com/cgi-bin/ticket/getticket?access_token=ACCESS_TOKEN&type=jsapi>

成功返回以下JSON:

{
"errcode":0,
"errmsg":"ok",
"ticket":"bxLdikRXVbTPdHSM05e5u5sUoXNKd841ZO3MhKoyN5OfkWITDGgnr2fwJ0m9E8NYzWKVZvdVtaUWvsdshFKA",
"expires_in":7200
}
复制代码

得到jsapi_ticket以后,就能够生成JS-SDK权限验证的签名了。

回到以前说的那个Nodejs + Express项目中:

api/jsapiTicket.js

// 经过 access_token 获取 jsapi_ticket 临时票据
const axios = require('axios'); // 请求api
const CircularJSON = require('circular-json');
const config = require('../config/index.json');
const cache = require('../utils/cache');


module.exports = get_jsapi_ticket = (access_token, res) => {

  const fetchUrl = config.getJsapiTicket + access_token;
  console.log('>>>>', fetchUrl)
  // 判断是否存在于缓存中
  const cacheName = "jsapi_ticket";
  cache.getCache(cacheName, function (cacheValue) {
    if (cacheValue) {
      const result = CircularJSON.stringify({
        ticket: cacheValue,
        from: 'cache'
      });
      res.send(result);
    } else {
      // 调取微信api
      axios.get(fetchUrl).then(response => {
        let json = CircularJSON.stringify(response.data);
        // promise
        res.send(json);
        // 设置缓存
        if (response.data.ticket) {
          cache.setCache(cacheName, response.data.ticket)
        }
      }).catch(err => {
        // console.log('axios occurs ', err);
      });
    }
  });

}

复制代码

路由设置:

const express = require('express');
const api = require('./api');
const path = require('path');
const app = express();
//express请求别的路由中间件
require('run-middleware')(app);

// 获取 jsapi_ticket 临时票据
app.get('/getTicket', (req, res) => {
  app.runMiddleware('/getAccessToken', function (code, body, headers) {
    const result = JSON.parse(body);
    console.log('User token:', result.access_token);
    api.jsapiTicket(result.access_token, res);
  })
});

....
复制代码

这里比较特殊的地方,是用 run-middleware 这个 npm package,从一个路由去直接请求另一个路由的数据。

这样避免重复不少逻辑。咱们直接请求路由获取上一步的 access_token;

废话很少说,运行一下:

第一次,是从微信服务器获取 ticket

第二次,从缓存中:

至此,咱们获取到了 jsapi_ticker;

得到jsapi_ticket以后,就能够生成JS-SDK权限验证的签名了

四、增长签名算法获取微信签名

签名算法

签名生成规则以下:参与签名的字段包括noncestr(随机字符串), 有效的jsapi_ticket, timestamp(时间戳), url(当前网页的URL,不包含#及其后面部分) 。对全部待签名参数按照字段名的ASCII 码从小到大排序(字典序)后,使用URL键值对的格式(即key1=value1&key2=value2…)拼接成字符串string1。这里须要注意的是全部参数名均为小写字符。对string1sha1加密,字段名和字段值都采用原始值,不进行URL 转义。

说明:

noncestr随机字符串,通常本身生成

jsapi_ticket 从微信服务器或者本身的缓存中

timestamp时间戳本身生成

url当前页面的url,必定要动态获取,千万不要 hardcode

而后再按照字典排序,进行排序 jsapi_ticket&noncestr&timestamp&url

最后sha1 加密

signature=sha1(string1)。 示例:

noncestr=Wm3WZYTPz0wzccnW
jsapi_ticket=sM4AOVdWfPE4DxkXGEs8VMCPGGVi4C3VM0P37wVUCFvkVAy_90u5h9nbSlYy3
timestamp=1414587457
url=http://mp.weixin.qq.com?params=value
复制代码

步骤1. 对全部待签名参数按照字段名的ASCII 码从小到大排序(字典序)后,使用URL键值对的格式(即key1=value1&key2=value2…)拼接成字符串string1

jsapi_ticket=sM4AOVdWfPE4DxkXGEs8VMCPGGVi4C3VM0P37wVUCFvkVAy_90u5h9nbSlYy3-Sl-HhTdfl2fzFy1AOcHKP7qg&noncestr=Wm3WZYTPz0wzccnW&timestamp=1414587457&url=http://mp.weixin.qq.com?params=value
复制代码

步骤2. 对string1进行sha1签名,获得signature

0f9de62fce790f9a083d5c99e95740ceb90c27ed
复制代码

注意事项

1.签名用的noncestrtimestamp必须与wx.config中的nonceStrtimestamp相同。

2.签名用的url必须是调用JS接口页面的完整URL

3.出于安全考虑,开发者必须在服务器端实现签名的逻辑。

如出现invalid signature 等错误详见附录常见错误及解决办法

代码以下:

/** * 获取签名 * @returns: * 1. appId 必填,公众号的惟一标识 * 2. timestamp 必填,生成签名的时间戳 * 3. nonceStr 必填,生成签名的随机串 * 4. signature 必填,签名 */
const crypto = require('crypto');
const config = require('../config/index.json');

// sha1加密
function sha1(str) {
  let shasum = crypto.createHash("sha1")
  shasum.update(str)
  str = shasum.digest("hex")
  return str
}

/** * 生成签名的时间戳 * @return {字符串} */
function createTimestamp() {
  return parseInt(new Date().getTime() / 1000) + ''
}

/** * 生成签名的随机串 * @return {字符串} */
function createNonceStr() {
  return Math.random().toString(36).substr(2, 15)
}

/** * 对参数对象进行字典排序 * @param {对象} args 签名所需参数对象 * @return {字符串} 排序后生成字符串 */
function raw(args) {
  var keys = Object.keys(args)
  keys = keys.sort()
  var newArgs = {}
  keys.forEach(function (key) {
    newArgs[key.toLowerCase()] = args[key]
  })

  var string = ''
  for (var k in newArgs) {
    string += '&' + k + '=' + newArgs[k]
  }
  string = string.substr(1)
  return string
}


module.exports = getSign = (params, res) => {

  /** * 签名算法 * 签名生成规则以下: * 参与签名的字段包括noncestr( 随机字符串), * 有效的jsapi_ticket, timestamp( 时间戳), * url( 当前网页的URL, 不包含# 及其后面部分)。 * 对全部待签名参数按照字段名的ASCII 码从小到大排序( 字典序) 后, * 使用URL键值对的格式( 即key1 = value1 & key2 = value2…) 拼接成字符串string1。 * 这里须要注意的是全部参数名均为小写字符。 对string1做sha1加密, 字段名和字段值都采用原始值, 不进行URL 转义。 */
  var ret = {
    jsapi_ticket: params.ticket,
    nonceStr: createNonceStr(),
    timestamp: createTimestamp(),
    url: params.url
  };
  console.log(params, ret);
  var string = raw(ret)
  ret.signature = sha1(string)
  ret.appId = config.appid;
  console.log('ret', ret)
  res.send(ret);
}

复制代码

路由设置:

const express = require('express');
const api = require('./api');
const path = require('path');
const app = express();
//express请求别的路由中间件
require('run-middleware')(app);

//获取签名
app.get('/sign', (req, res) => {
  const params = {};
  console.log(req.query)
  params.url = req.query.url;
  /*** * runMiddleware 请求别的 endPoint 获取 jsapi_ticket */
  app.runMiddleware('/getTicket', function (code, body, headers) {
    const result = JSON.parse(body);
    console.log('User ticket:', result.ticket);
    params.ticket = result.ticket;
    api.getSign(params, res);
  });

});

....

复制代码

postman 请求以下:

这样我就获取了 签名 等一系列数据。

五、JSSDK 使用

微信JS-SDK说明文档

步骤一:绑定域名

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

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

步骤二:引入JS文件

在须要调用JS接口的页面引入以下JS文件,(支持https):res.wx.qq.com/open/js/jwe…

如需进一步提高服务稳定性,当上述资源不可访问时,可改访问:res2.wx.qq.com/open/js/jwe… (支持https)。

备注:支持使用 AMD/CMD 标准模块加载方法加载

步骤三:经过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: '',// 必填,签名
    jsApiList: [] // 必填,须要使用的JS接口列表
});
复制代码

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

wx.ready(function(){
    // config信息验证后会执行ready方法,全部接口调用都必须在config接口得到结果以后,config是一个客户端的异步操做,因此若是须要在页面加载时就调用相关接口,则须把相关接口放在ready函数中调用来确保正确执行。对于用户触发时才调用的接口,则能够直接调用,不须要放在ready函数中。
});
复制代码

实战代码以下:

// promise
const getSignPromise = new Promise((resolve, reject) => {
  const xhr = new XMLHttpRequest();
  xhr.open('GET', location.origin + '/sign?url=' + location.href, true);
  xhr.send();
  xhr.onload = () => {
    if (xhr.readyState === xhr.DONE) {
      if (xhr.status === 200) {
        const result = JSON.parse(xhr.response);
        console.log(result);
        resolve(result);
      }
    }
  }
});

// 分享
getSignPromise.then(res => {
  getWeShare(res);
});

/*** * 微信分享 */
const getWeShare = (params) => {
  wx.config({
    debug: true, // 开启调试模式,调用的全部api的返回值会在客户端alert出来,若要查看传入的参数,能够在pc端打开,参数信息会经过log打出,仅在pc端时才会打印。
    appId: params.appId, // 必填,公众号的惟一标识
    timestamp: params.timestamp, // 必填,生成签名的时间戳
    nonceStr: params.nonceStr, // 必填,生成签名的随机串
    signature: params.signature, // 必填,签名
    jsApiList: [
      'checkJsApi',
      'onMenuShareTimeline',
      'onMenuShareAppMessage',
      'onMenuShareQQ',
      'onMenuShareWeibo',
      'hideMenuItems',
      'chooseImage',
      'updateAppMessageShareData',
      'scanQRCode'
    ] // 必填,须要使用的JS接口列表
  });

  wx.ready(function () { //需在用户可能点击分享按钮前就先调用
    const data = {
      title: '测试JSSDK', // 分享标题
      desc: '后端端口签名测试', // 分享描述
      link: location.href, // 分享连接,该连接域名或路径必须与当前页面对应的公众号JS安全域名一致
      imgUrl: 'http://www.***.cf/img/share.JPG', // 分享图标
      success: function () {
        // 设置成功
      }
    }
    wx.onMenuShareTimeline(data);
    wx.onMenuShareAppMessage(data);
  });
}

// 打开相册
document.getElementById('chooseImage').addEventListener('click', function (params) {
  wx.chooseImage({
    count: 1, // 默认9
    sizeType: ['original', 'compressed'], // 能够指定是原图仍是压缩图,默认两者都有
    sourceType: ['album', 'camera'], // 能够指定来源是相册仍是相机,默认两者都有
    success: function (res) {
      var localIds = res.localIds; // 返回选定照片的本地ID列表,localId能够做为img标签的src属性显示图片
      console.log(localIds);
    }
  });
  wx.scanQRCode({
    needResult: 0, // 默认为0,扫描结果由微信处理,1则直接返回扫描结果,
    scanType: ["qrCode", "barCode"], // 能够指定扫二维码仍是一维码,默认两者都有
    success: function (res) {
      var result = res.resultStr; // 当needResult 为 1 时,扫码返回的结果
    }
  });
})

复制代码

页面写好以后, 咱们能够在 微信开发者工具里面看到:

咱们也能够打开测试公众号,在里面调试,不然,咱们没有权限调取 js 接口

至此,已经算是成功开发了,其余接口再也不作尝试。

六、附录

调用config接口的时候传入参数 debug: true 能够开启debug模式,页面会alert出错误信息。如下为常见错误及解决方法:

1.invalid url domain当前页面所在域名与使用的appid没有绑定,请确认正确填写绑定的域名,仅支持80(http)443(https)两个端口,所以不须要填写端口号(一个appid能够绑定三个有效域名,见 ]目录1.1.1)。

2.·invalid signature签名错误。建议按以下顺序检查:

1.确认签名算法正确,可用mp.weixin.qq.com/debug/cgi-b… 页面工具进行校验。

2.确认confignonceStr(js中驼峰标准大写S), timestamp与用以签名中的对应noncestr, timestamp一致。

3.确认url是页面完整的url(请在当前页面alert(location.href.split('#')[0])确认),包括'http(s)://'部分,以及'?'后面的GET参数部分,但不包括'#'hash后面的部分。

4.确认 config 中的 appid 与用来获取 jsapi_ticket 的 appid 一致。

5.确保必定缓存access_tokenjsapi_ticket

6.确保你获取用来签名的url是动态获取的,动态页面可参见实例代码中php的实现方式。若是是html的静态页面在前端经过ajax将url传到后台签名,前端须要用js获取当前页面除去'#'hash部分的连接(可用location.href.split('#')[0]获取,并且须要encodeURIComponent),由于页面一旦分享,微信客户端会在你的连接末尾加入其它参数,若是不是动态获取当前连接,将致使分享后的页面签名失败。

3.the permission value is offline verifying这个错误是由于config没有正确执行,或者是调用的JSAPI没有传入config的jsApiList参数中。建议按以下顺序检查:

1.确认config正确经过。

2.若是是在页面加载好时就调用了JSAPI,则必须写在wx.ready的回调中。

3.确认config的jsApiList参数包含了这个JSAPI。

4.permission denied该公众号没有权限使用这个JSAPI,或者是调用的JSAPI没有传入config的jsApiList参数中(部分接口须要认证以后才能使用)。

5.function not exist当前客户端版本不支持该接口,请升级到新版体验。

6.为何6.0.1版本config:ok,可是6.0.2版本以后不ok(由于6.0.2版本以前没有作权限验证,因此config都是ok,但这并不意味着你config中的签名是OK的,请在6.0.2检验是否生成正确的签名以保证config在高版本中也ok。)

7.在iOS和Android都没法分享(请确认公众号已经认证,只有认证的公众号才具备分享相关接口权限,若是确实已经认证,则要检查监听接口是否在wx.ready回调函数中触发)

8.服务上线以后没法获取jsapi_ticket,本身测试时没问题。(由于access_token和jsapi_ticket必需要在本身的服务器缓存,不然上线后会触发频率限制。请确保必定对token和ticket作缓存以减小2次服务器请求,不只能够避免触发频率限制,还加快大家本身的服务速度。目前为了方便测试提供了1w的获取量,超过阀值后,服务将再也不可用,请确保在服务上线前必定全局缓存access_token和jsapi_ticket,二者有效期均为7200秒,不然一旦上线触发频率限制,服务将再也不可用)。

9.uploadImage怎么传多图(目前只支持一次上传一张,多张图片需等前一张图片上传以后再调用该接口)

10.无法对本地选择的图片进行预览(chooseImage接口自己就支持预览,不须要额外支持)

11.经过a连接(例如先经过微信受权登陆)跳转到b连接,invalid signature签名失败(后台生成签名的连接为使用jssdk的当前连接,也就是跳转后的b连接,请不要用微信登陆的受权连接进行签名计算,后台签名的url必定是使用jssdk的当前页面的完整url除去'#'部分)

12.出现config:fail错误(这是因为传入的config参数不全致使,请确保传入正确的appId、timestamp、nonceStr、signature和须要使用的jsApiList)

13.如何把jsapi上传到微信的多媒体资源下载到本身的服务器(请参见文档中uploadVoice和uploadImage接口的备注说明)

14.Android经过jssdk上传到微信服务器,第三方再从微信下载到本身的服务器,会出现杂音(微信团队已经修复此问题,目先后台已优化上线)

15.绑定父级域名,是否其子域名也是可用的(是的,合法的子域名在绑定父域名以后是彻底支持的)

16.在iOS微信6.1版本中,分享的图片外链不显示,只能显示公众号页面内链的图片或者微信服务器的图片,已在6.2中修复

17.是否须要对低版本本身作兼容(jssdk都是兼容低版本的,不须要第三方本身额外作更多工做,但有的接口是6.0.2新引入的,只有新版才可调用)

18.该公众号支付签名无效,没法发起该笔交易(请确保你使用的jweixin.js是官方线上版本,不只能够减小用户流量,还有可能对某些bug进行修复,拷贝到第三方服务器中使用,官方将不对其出现的任何问题提供保障,具体支付签名算法可参考 JSSDK微信支付一栏)

19.目前Android微信客户端不支持pushState的H5新特性,因此使用pushState来实现web app的页面会致使签名失败,此问题已在Android6.2中修复

20.uploadImage在chooseImage的回调中有时候Android会不执行,Android6.2会解决此问题,若需支持低版本能够把调用uploadImage放在setTimeout中延迟100ms解决

21.require subscribe错误说明你没有订阅该测试号,该错误仅测试号会出现

22.getLocation返回的坐标在openLocation有误差,由于getLocation返回的是gps坐标,openLocation打开的腾讯地图为火星坐标,须要第三方本身作转换,6.2版本开始已经支持直接获取火星坐标

23.查看公众号(未添加): "menuItem:addContact"不显示,目前仅有从公众号传播出去的连接才能显示,来源必须是公众号

24.ICP备案数据同步有一天延迟,因此请在第二日绑定

相关文章
相关标签/搜索