NodeJs实现自定义分享功能,获取微信授权+用户信息
最近公司搞了个转盘抽奖的运营活动,入口放在了微信公众号里,好久没碰过微信了,刚拾起来瞬间感觉有点懵逼。。。。似乎把之前的坑又都重新踩了一遍,虽然过程曲折,不过好在顺利完成了,而且印象也更加深刻了,抽时间记录一下过程,不然下次再被绊倒的话,就要被老铁们笑话啦。好了,废话不多说,进入正题(先捋一捋流程,再说要注意的问题):
1.公众号配置相关:
登录微信开放平台,找到对应公众号,设置业务域名,JS接口安全域名,网页授权域名等乱七八糟的一堆(文档或者网上都有,可对比参考一下,这里就不赘述了)。
2.接入JSSDK:
页面头部引入即可:
<script src="http://res.wx.qq.com/open/js/jweixin-1.0.0.js"></script>
3.获取access_token、ticket及signature:
//appid、秘钥、随机字符串(不要试图用这些做坏事,因为机智的我已经预料到了。。。)
const APPID = 'wxdc8b4f8e3esdfsdfasd'; const SECRET = 'f5a5fa443923e3fdsfasdasf0'; const NONCESTR = 'Wm3WZYTPz0fdsfasdf';
let timestamp = (new Date()).getTime(); let access_token = ''; let jsapi_ticket = '';
//获取access_token
function getToken(callback) {
const newsUrl= 'https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid=' + APPID + '&secret=' + SECRET;
request(newsUrl, function (error, response, body) {
if(error || response.statusCode != '200'){
getToken(callback);
return;
}
let obj;
try{
obj = JSON.parse(body);
}catch(e){
console.log(e);
}
if(!obj || !('access_token' in obj)){
console.log('Get access token error!', error, response.statusCode, body);
getToken(callback);
return;
}
access_token = JSON.parse(body).access_token;
console.log('Got new access token:', access_token);
timestamp = (new Date()).getTime();
getTicket(callback);
});
}
function getTicket(callback) {
const newsUrl= 'https://api.weixin.qq.com/cgi-bin/ticket/getticket?access_token=' + access_token + '&type=jsapi';
request(newsUrl, function (error, response, body) {
if (!error && response.statusCode == '200') {
if (JSON.parse(body).errcode === 0) {
jsapi_ticket = JSON.parse(body).ticket;
if(callback) {
callback();
}
}
} else {
response.status(response.statusCode).send(body);
}
});
}
//签名加密
function getSignature(url) {
const str = 'jsapi_ticket=' + jsapi_ticket + '&noncestr=' + NONCESTR + '×tamp=' + timestamp + '&url=' + url;
const sha1 = crypto.createHash('sha1');
return sha1.update(str).digest('hex');
}
4.通过接口将appId、timestamp、noncestr、signature返回值页面:
router.get('/wxconfig', function(req, res) { let date = (new Date()).getTime();
//access_token有效时间为2小时 if(date - timestamp >= 7200 * 1000) { getToken(function () { let signature = getSignature(req.query.url); res.send({ appId: APPID, timestamp: timestamp, nonceStr: NONCESTR, signature: signature }); }); } else {
//这里的url是调用接口时传来的,必须是动态获取当前页面地址‘#’号之前的部分 let signature = getSignature(req.query.url); res.send({ appId: APPID, timestamp: timestamp, nonceStr: NONCESTR, signature: signature }); } });
5.调用’/wxconfig’接口,配置自定义分享信息:
getSignature() {
let url = window.location.href.split('#')[0]; this.$http.get('/lucky-draw/wxconfig', { params: { url: url } }).then(data => { this.wxConfig(data.body); this.wxReady(); }, error => { console.log(error); }); }
wxConfig(options) {
wx.config({
debug: false,
appId: options.appId,
timestamp: options.timestamp,
nonceStr: options.nonceStr,
signature: options.signature,
openid: '',
jsApiList: [
'onMenuShareTimeline',
'onMenuShareAppMessage'
]
});
}
wxReady() {
let _this = this;
let id = _this.getQueryString('acti');
let options = {
title: 'xxxxxxxxxxx',
desc: 'xxxxxxxxxxx',
link: 'xxxxxxxxx',
imgUrl: 'xxxxxxxxxx',
success: function (data) {
_this.addOneChanceAfterShare();
},
cancel: function (data) {
console.log(data);
}
};
wx.ready(function () {
wx.onMenuShareTimeline(options);
wx.onMenuShareAppMessage(options);
});
}
6.获取微信授权:
因为授权登录的权限较高,所以微信平台会对链接的顺序进行校验,顺序是固定的。链接格式如下:
“https://open.weixin.qq.com/connect/oauth2/authorize?appid=APPID&redirect_uri=REDIRECT_URI&response_type=code&scope=SCOPE&state=STATE#wechat_redirect”
解释一下参数:
appid:公众号的appid;
redirect_uri:微信授权成功之后需要跳转的链接(你希望授权成功之后跳转到哪个页面,这里就填哪个);
response_type:固定参数“code”(告诉微信给你返回code,这个解释是不是简单粗暴了些);
scope:授权权限,主要分两种,“snsapi_base”:静默授权,用户无感知,但是只能获取到openid;“snsapi_userinfo ”:弹出询问框,需要用户手动确认,可以拿到openid及用户基本信息;
state:没搞太明白是干什么用的,但有一点可以确定,你传什么参数,微信授权跳转之后还会给你带回去这个参数,特定场合可能会用到;
wechat_redirect:这个可以去掉,没有影响。
7.拿code换取openid:
因为我这次做的是一个活动的单页面,页面上需要根据用户的openid及unionid从后台获取数据,所以我希望在进入页面之前就已经拿到我需要的东西了(openid和unionid以及一些数据),我的做法是把回调地址写在node里,以接口的形式接受微信的跳转(这里用词是不是太生硬了啊,哈哈,自行理解一下吧):
//获取code时微信回调地址 router.get('/is_wx_redirect', function (req, res) { //拿code let code = req.query.code || ''; console.log('Get code success!!!', code); //拿活动id let id = req.query.acti; req.session.acti_id = id; let openid, unionid, subscribe; getOpenId(); //获取openid function getOpenId () { let url = 'https://api.weixin.qq.com/sns/oauth2/access_token?appid=' + APPID + '&secret=' + SECRET + '&code=' + code + '&grant_type=authorization_code'; request(url, function (error, response, body) { if (error || response.statusCode != '200') { console.log('获取openid时发生错误:', error); res.send('获取openid时发生错误,错误信息为:' + error); return false; } let data = JSON.parse(body); if (!data.openid) { console.log('发送请求openid成功,但是返回信息中没有openid'); res.send(body); } //获取openid成功后,获取unionid和是否关注等信息 openid = data.openid; console.log('我拿到openid了,openid是:', openid); getIsFocus(); }); } //获取unionid和是否关注等信息 function getIsFocus () { let url = 'https://api.weixin.qq.com/cgi-bin/user/info?access_token=' + access_token + '&openid=' + openid + '&lang=zh_CN'; request(url, function (error, response, body) { if (error || response.statusCode != '200') { console.log('获取unionid时发生错误:', error); res.send('获取unionid时发生错误,错误信息为:' + error); return false; } let data = JSON.parse(body); subscribe = data.subscribe || ''; unionid = data.unionid || ''; //存session req.session.openid = openid; req.session.unionid = unionid; req.session.subscribe = subscribe; console.log('我拿到unionid了,unionid是:', unionid); console.log('我拿到subscribe了,subscribe是:', subscribe); if (!subscribe) { //未关注 console.log('The user is not focus!!'); res.redirect('/lucky-draw/focus_page', 302); return false; } if (!unionid) { //返回信息中无unionid res.send('返回信息中未包含unionid' + data); return false; } //重定向至主页面 console.log('Start redirect!!!!'); res.redirect('/lucky-draw/?acti=' + id, 302); console.log('Redirect success!!'); }); } });
眼尖的老铁可能一眼就发现了几个问题:
1)我并没有用拿到的code去换access_token,而是用之前自定分享时获取过的全局token,这里需要注意了,全局access_token和网页授权access_token是有区别的,获取地址、参数、时效及用途都不同,千万可别弄混淆了,不然非得坑死你(别胡说,我才没被这个坑过。。。。),关于这两个access_toekn的具体区别及用途,可以自行查一下。因为我只需要判断用户是否关注了公众号,从而完成一项业务需求,并不需要用户的基本信息,所以我只拿code换取了openid,然后用openid和全局access_token获取unionid;
2)大堆的log有木有!!微信开发的调试相对来说还是比较简(恶)单(心)的,关键位置的log获取错误处理,会让我们少走很多弯路。
8.其实代码已经写在上一步了,就是拿到初始化页面所需要的信息之后,重定向至该页面。