准备工作
已认证的公众号(一定要认证过的!)
在微信的官方文档常见错误及解决方法中,只有认证的公众号才具有分享相关接口权限绑定JS域名
在 微信公众号 >> 功能设置 >> JS接口安全域名 中配置JS接口安全域名
将MP_verify_1PI6AnuQ7s0acvZv.txt(点击下载)放置在填写的域名目录下添加IP白名单
在 微信公众号 >> 基本配置 >> IP白名单 中将访问微信接口的服务器的IP地址添加到白名单中获取AppID和AppSecret
将获取的AppID和AppSecret保存
AppSecret一旦重置,将影响所有使用此AppID的应用,要慎重!
后端工作
参考微信官方文档JS-SDK使用权限签名算法
- 获取access_token(有效期7200秒,开发者必须在自己的服务全局缓存access_token)
curl "http://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid=${appId}&secret=${secret}"
注意:curl测试时,url用“”,否则服务器不通 - 检查生成的签名
http://mp.weixin.qq.com/debug/cgi-bin/sandbox?t=jsapisign
获取生成签名所需参数
- appId + secret -------> ACCESS_TOKEN
- ACCESS_TOKEN -------> jsapi_ticket
// 1.获取access_token
app.get('/get_access_token', (req, res) => {
res.header("Access-Control-Allow-Origin",req.headers.origin); // 处理跨域
request.get({
url: `http://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid=${appId}&secret=${secret}`,
json: true,
}, (err, response, body) => {
console.log(body, 'token')
try {
if (err) {
res.json(err);
} else {
// 这里获取到ACCESS_TOKEN 传给getticket获取jsapi_ticket
res.send(body)
}
} catch (error) {
let data = {
"code": 500,
"message": "net error"
};
res.send(data);
LOG.error(req.method, req.url, '===========', error);
}
});
})
// 2.根据access_token获取jsapi_ticket
app.get('/get_ticket', (req, res) => {
res.header("Access-Control-Allow-Origin",req.headers.origin);
request.get({
url: `http://api.weixin.qq.com/cgi-bin/ticket/getticket?access_token=${ACCESS_TOKEN}&type=jsapi`,
json: true,
}, (err, response, body) => {
// console.log(body, 'getticket')
try {
if (err) {
res.json(err);
} else {
// 这里生成签名 jsapi_ticket
res.send(body)
}
} catch (error) {
let data = {
"code": 500,
"message": "net error"
};
res.send(data);
LOG.error(req.method, req.url, '===========', error);
}
});
})
签名过程
- 生成随机字符串
- 对象进行ASCII码排序
- 将排序后的对象用key1=value1&key2=value2形式拼接
- 进行sha1加密
- 进行签名算法
// 3.1生成随机字符串
let randomString = (length) => {
let chars = '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ'
let result = '';
for (var i = length; i > 0; --i) result += chars[Math.floor(Math.random() * chars.length)];
return result;
}
// 3.2对象进行ASCII码排序
let sort_ASCII = (obj) => {
var arr = new Array();
var num = 0;
for (var i in obj) {
arr[num] = i;
num++;
}
var sortArr = arr.sort();
var sortObj = {};
for (var i in sortArr) {
sortObj[sortArr[i]] = obj[sortArr[i]];
}
return sortObj;
}
// 3.3将对象用key1=value1&key2=value2形式拼接
let json_to_string = (jsonObj) => {
let jStr= ''
for(var item in jsonObj){
jStr += item + "=" + jsonObj[item]+ "&";
}
jStr = jStr.substring(0, jStr.length - 1);
return jStr
}
// 3.4进行sha1加密
const encrypt = (algorithm, content) => {
let hash = createHash(algorithm)
hash.update(content)
return hash.digest('hex')
}
const sha1 = (content) => encrypt('sha1', content)
// 3.5进行签名算法
let sign = (jsapi_ticket) => {
let timestamp = Date.parse( new Date() ).toString().substr(0,10)
let nonceStr = randomString(16)
let jsonobj = {
noncestr: nonceStr,
jsapi_ticket: jsapi_ticket,
timestamp: timestamp,
url: shareUrl
}
console.log(jsonobj)
let str = json_to_string(sort_ASCII(jsonobj))
console.log(str)
let signature = sha1(str)
console.log(signature)
let data = {
appId: appId, // 必填,公众号的唯一标识
timestamp: timestamp, // 必填,生成签名的时间戳
nonceStr: nonceStr, // 必填,生成签名的随机串
signature: signature,// 必填,签名
url: shareUrl
}
return data
}
前端工作
- 安装所需wx依赖
npm install jweixin-module
- 引入依赖
let wx = require('jweixin-module')
通过config接口注入权限验证配置
所有需要使用wx的页面都要先注入配置,否则调用wx方法报错“无权限”
此处所有配置参数由后端传入
wx.config({
debug: false, // 开启调试模式,调用的所有api的返回值会在客户端alert出来,若要查看传入的参数,可以在pc端打开,参数信息会通过log打出,仅在pc端时才会打印。
appId, // 必填,公众号的唯一标识
timestamp, // 必填,生成签名的时间戳
nonceStr, // 必填,生成签名的随机串
signature, // 必填,签名
jsApiList: ["updateAppMessageShareData", "updateTimelineShareData"] // 必填,需要使用的JS接口列表
});
通过ready接口处理成功验证
config信息验证后会执行ready方法,所有接口调用都必须在config接口获得结果之后,config是一个客户端的异步操作,所以如果需要在页面加载时就调用相关接口,则须把相关接口放在ready函数中调用来确保正确执行。对于用户触发时才调用的接口,则可以直接调用,不需要放在ready函数中。
分享的链接必须是 http or https ,否则不生效
wx.ready(function() {
// config信息验证后会执行ready方法,所有接口调用都必须在config接口获得结果之后,config是一个客户端的异步操作,所以如果需要在页面加载时就调用相关接口,则须把相关接口放在ready函数中调用来确保正确执行。对于用户触发时才调用的接口,则可以直接调用,不需要放在ready函数中。
wx.updateAppMessageShareData({
title: that.shareTitle, // 分享标题
desc: that.shareDesc, // 分享描述
link: that.shareUrl, // 分享链接,该链接域名或路径必须与当前页面对应的公众号JS安全域名一致
imgUrl: `https://staticai.xxxx.com/img/share.265ee3fb.jpg`, // 分享图标
success: function() {
// 设置成功
// alert("分享朋友成功");
},
fail: function(e) {
console.log(e, "分享朋友失败");
}
});
wx.updateTimelineShareData({
title: that.shareTitle, // 分享标题
link: that.shareUrl, // 分享链接,该链接域名或路径必须与当前页面对应的公众号JS安全域名一致
imgUrl: `https://staticai.xxx.com/img/share.265ee3fb.jpg`, // 分享图标
success: function() {
// 设置成功
// alert("分享朋友圈成功");
},
fail: function(e) {
console.log(e, "分享朋友圈失败");
}
});
wx.checkJsApi({
jsApiList: ["updateAppMessageShareData", "updateTimelineShareData"], // 需要检测的JS接口列表,所有JS接口列表见附录2,
success: function(res) {
// 以键值对的形式返回,可用的api值true,不可用为false
// 如:{"checkResult":{"chooseImage":true},"errMsg":"checkJsApi:ok"}
},
fail: function(e) {
console.log(e, "检查jsapi失败");
}
});
wx.error(function(res) {
// config信息验证失败会执行error函数,如签名过期导致验证失败,具体错误信息可以打开config的debug模式查看,也可以在返回的res参数中查看,对于SPA可以在这里更新签名。
console.log(res, "config失败");
});
});
END