上一篇咱们讲了基本的准备工做,接下来,进入实战,因为楼主我并无备案过的域名
(穷,没钱,没办法哈),还好, 一直通不过签名验证
,微信比较人性化,提供测试号
,能够测大部分的接口,而且设置JS接口安全域名
,没有限制,能够写任何地址,哪怕是localhost:9999
也是能够的。javascript
因为用户体验和安全性方面的考虑,微信公众号的注册有必定门槛,某些高级接口的权限须要微信认证后才能够获取。php
因此,为了帮助开发者快速了解和上手微信公众号开发,熟悉各个接口的调用,微信推出了微信公众账号测试号,经过手机微信扫描二维码便可得到测试号
,在这个测试号里面能够模拟各类操做,好比分享啥的,很容易经过验证。html
进入微信公众账号测试号申请系统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_ticket
,jsapi_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
。这里须要注意的是全部参数名均为小写字符。对string1
做sha1
加密,字段名和字段值都采用原始值,不进行URL 转义。
说明:
noncestr
随机字符串,通常本身生成
jsapi_ticket
从微信服务器或者本身的缓存中
timestamp
时间戳本身生成
url
当前页面的url
,必定要动态获取,千万不要hardcode
而后再按照字典排序,进行排序
jsapi_ticket&noncestr×tamp&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×tamp=1414587457&url=http://mp.weixin.qq.com?params=value
复制代码
步骤2. 对string1
进行sha1
签名,获得signature
:
0f9de62fce790f9a083d5c99e95740ceb90c27ed
复制代码
注意事项
1.签名用的
noncestr
和timestamp
必须与wx.config
中的nonceStr
和timestamp
相同。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 请求以下:
这样我就获取了 签名 等一系列数据。
步骤一:绑定域名
先登陆微信公众平台进入“公众号设置”的“功能设置”里填写“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.确认
config
中nonceStr
(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_token
和jsapi_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备案数据同步有一天延迟,因此请在第二日绑定