作了一个网站,放到线上,用微信打开,点击分享,但是分享后发给朋友的连接卡片是微信默认自带的,以下:
这标题,描述以及图片是默认自带的,丑不说,分享给别人还觉得是盗号网站呢,而接入微信的JSSDK后,分享能够自定义内容,以下:
我认可,虽然这分享的标题和内容也并不正经,但这不妨碍我表达咱们能够经过微信JSSDK定义分享内容,接下来咱们将一步一步从零实现JSSDK从后端Node.js的接入。javascript
首先咱们须要在微信公众平台申请测试接口,地址:https://mp.weixin.qq.com/debug/cgi-bin/sandbox?t=sandbox/login
使用微信扫描登陆后,便可来微信公众平台测试帐号系统。前端
其次在微信公众平台测试帐号中,扫描测试号二维码,成为测试公众号的开发者java
此时点击提交是会提示配置失败的,由于在提交的时候,微信是会请求你的服务器地址,而你的当前配置的地址并不能访问,因此会提示配置失败。不过别急,咱们先来搭建一个简单的Node服务器,让微信可以访问该服务器。node
咱们须要在http://www.your_server_name.com 这个域名上搭建一个服务器,而且曝出一个接口为/wxJssdk
ios
const express = require('express') const app = express() app.get('/wxJssdk', (req, res) => { res.send('请求成功了了了了') }) app.listen(80, err => { if(!err) console.log('connect succeed') })
如今咱们在地址栏中访问http://www.your_server_name.com/wxJssdk ,若是页面显示“请求成功了了了了”,则进入到下一步,若是没有成功的话,检查一下你的服务器是否开启Node服务器,如:node index.js
算法
此时保存微信测试公众号后台的接口配置信息,仍然会提示配置失败,这是由于咱们没有按照它的要求返回。express
根据微信公众号开发文档接入指南,微信在请求咱们配置的接口时,会带上以下信息json
参数 | 描述 |
---|---|
signature | 微信加密签名,signature结合了开发者填写的token参数和请求中的timestamp参数、nonce参数。 |
timestamp | 时间戳 |
nonce | 随机数 |
echostr | 随机字符串 |
微信服务器会经过GET请求,来请求咱们所配置的接口,并带上以上表格的信息,而咱们必须按照如下要求,将微信发送的信息进行要求校验,以确保是微信发送的信息,其中校验流程以下:axios
1)将token、timestamp、nonce三个参数进行字典序排序
2)将三个参数字符串拼接成一个字符串进行sha1加密
3)开发者得到加密后的字符串可与signature对比,标识该请求来源于微信后端
const express = require('express') const app = express() const sha1 = require('sha1') app.get('/wxJssdk', (req, res) => { let wx = req.query let token = 'jegfjaeghfuccawegfgjdbh' let timestamp = wx.timestamp let nonce = wx.nonce // 1)将token、timestamp、nonce三个参数进行字典序排序 let list = [token, timestamp, nonce].sort() // 2)将三个参数字符串拼接成一个字符串进行sha1加密 let str = list.join('') let result = sha1(str) // 3)开发者得到加密后的字符串可与signature对比,标识该请求来源于微信 if (result === wx.signature) { res.send(wx.echostr) // 返回微信传来的echostr,表示校验成功,此处不能返回其它 } else { res.send(false) } })
此时咱们重启Node服务器,再次保存接口配置信息便可配置成功。
根据微信JSSDK说明文档,咱们须要完成以下:
登陆微信公众平台进入“公众号设置”的“功能设置”里填写“JS接口安全域名”,即要调用接口的域名,不包含协议
在须要调用JS接口的页面引入此JS文件,(支持https):http://res.wx.qq.com/open/js/jweixin-1.2.0.js
wx.config({ debug: true, // 开启调试模式,调用的全部api的返回值会在客户端alert出来,若要查看传入的参数,能够在pc端打开,参数信息会经过log打出,仅在pc端时才会打印。 appId: '', // 必填,公众号的惟一标识 timestamp: , // 必填,生成签名的时间戳 nonceStr: '', // 必填,生成签名的随机串 signature: '',// 必填,签名 jsApiList: [] // 必填,须要使用的JS接口列表,全部JS接口列表见附录2 });
作你前端该作的,调用微信分享接口,或微信提供的其它接口,whatever you need,固然,这并非咱们所要讲的重点,咱们接下来要看一下微信的配置信息从哪获取
从上一节能够看到,调用微信JSSDK须要如下信息
其中:
生成签名以前必须先了解一下jsapi_ticket,jsapi_ticket是公众号用于调用微信JS接口的临时票据。正常状况下,jsapi_ticket的有效期为7200秒,经过access_token来获取。因为获取jsapi_ticket的api调用次数很是有限,频繁刷新jsapi_ticket会致使api调用受限,影响自身业务,开发者必须在本身的服务全局缓存jsapi_ticket 。
为了保证咱们appid,appsecret,nonceStr等信息不在前端曝露,咱们如下步骤将在服务器上进行操做,以避免他人盗用信息获取(注:微信请求有每日次数限制,一旦超出,则没法使用,具体请求次数限制在微信公众号后台中可查看)
根据微信开发文档[获取access_token文档说明],咱们须要将微信测试公众号后台的appid和和appsecret以GET的请求方式向https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid=APPID&secret=APPSECRET 发起请求获取token,请求成功后咱们会得到下返回JSON转化的字符串
{"access_token":"ACCESS_TOKEN","expires_in":7200}
具体请求代码以下:
const request = require('request') const grant_type = 'client_credential' const appid = 'your app id' const secret = 'your app secret' request('https://api.weixin.qq.com/cgi-bin/token?grant_type=' + grant_type + '&appid=' + appid + '&secret=' + secret, (err, response, body) => { let access_toekn = JSON.parse(body).access_token })
const request = require('request') const grant_type = 'client_credential' const appid = 'your app id' const secret = 'your app secret' request('https://api.weixin.qq.com/cgi-bin/token?grant_type=' + grant_type + '&appid=' + appid + '&secret=' + secret, (err, response, body) => { let access_toekn = JSON.parse(body).access_token request('https://api.weixin.qq.com/cgi-bin/ticket/getticket?access_token=' + access_token + '&type=jsapi', (err, response, body) => { let jsapi_ticket = JSON.parse(body).ticket }) })
生成签名的步骤和最开始的/wxJssdk
的算法是一致的,具体以下:
let jsapi_ticket = jsapi_ticket // 上一步从获取的jsapi_ticket let nonce_str = '123456' // 密钥,字符串任意,能够随机生成 let timestamp = new Date().getTime() // 时间戳 let url = req.query.url // 使用接口的url连接,不包含#后的内容 // 将请求以上字符串,先按字典排序,再以'&'拼接,以下:其中j > n > t > u,此处直接手动排序 let str = 'jsapi_ticket=' + jsapi_ticket + '&noncestr=' + nonce_str + '×tamp=' + timestamp + '&url=' + url // 用sha1加密 let signature = sha1(str)
链接后的代码为:
const request = require('request') const grant_type = 'client_credential' const appid = 'your app id' const secret = 'your app secret' request('https://api.weixin.qq.com/cgi-bin/token?grant_type=' + grant_type + '&appid=' + appid + '&secret=' + secret, (err, response, body) => { let access_toekn = JSON.parse(body).access_token request('https://api.weixin.qq.com/cgi-bin/ticket/getticket?access_token=' + access_token + '&type=jsapi', (err, response, body) => { let jsapi_ticket = JSON.parse(body).ticket let nonce_str = '123456' // 密钥,字符串任意,能够随机生成 let timestamp = new Date().getTime() // 时间戳 let url = req.query.url // 使用接口的url连接,不包含#后的内容 // 将请求以上字符串,先按字典排序,再以'&'拼接,以下:其中j > n > t > u,此处直接手动排序 let str = 'jsapi_ticket=' + jsapi_ticket + '&noncestr=' + nonce_str + '×tamp=' + timestamp + '&url=' + url // 用sha1加密 let signature = sha1(str) }) })
app.post('/wxJssdk/getJssdk', (req, res) => { const request = require('request') const grant_type = 'client_credential' const appid = 'your app id' const secret = 'your app secret' request('https://api.weixin.qq.com/cgi-bin/token?grant_type=' + grant_type + '&appid=' + appid + '&secret=' + secret, (err, response, body) => { let access_toekn = JSON.parse(body).access_token request('https://api.weixin.qq.com/cgi-bin/ticket/getticket?access_token=' + access_token + '&type=jsapi', (err, response, body) => { let jsapi_ticket = JSON.parse(body).ticket let nonce_str = '123456' // 密钥,字符串任意,能够随机生成 let timestamp = new Date().getTime() // 时间戳 let url = req.query.url // 使用接口的url连接,不包含#后的内容 // 将请求以上字符串,先按字典排序,再以'&'拼接,以下:其中j > n > t > u,此处直接手动排序 let str = 'jsapi_ticket=' + jsapi_ticket + '&noncestr=' + nonce_str + '×tamp=' + timestamp + '&url=' + url // 用sha1加密 let signature = sha1(str) res.send({ appId: appid, timestamp: timpstamp, nonceStr: nonce_str, signature: signature, }) }) }) })
axios.post('/wxJssdk/getJssdk', {url: location.href}).then((response) => { var data = response.data wx.config({ debug: false, // 开启调试模式,调用的全部api的返回值会在客户端alert出来,若要查看传入的参数,能够在pc端打开,参数信息会经过log打出,仅在pc端时才会打印。 appId: data.appId, // 必填,公众号的惟一标识 timestamp: data.timestamp, // 必填,生成签名的时间戳 nonceStr: data.nonceStr, // 必填,生成签名的随机串 signature: data.signature,// 必填,签名,见附录1 jsApiList: ['onMenuShareTimeline', 'onMenuShareAppMessage'] // 必填,须要使用的JS接口列表,全部JS接口列表见附录2 }); })
if (wx) { axios.post('/wxJssdk/getJssdk', {url: location.href}).then((response) => { var data = response.data wx.config({ debug: false, // 开启调试模式,调用的全部api的返回值会在客户端alert出来,若要查看传入的参数,能够在pc端打开,参数信息会经过log打出,仅在pc端时才会打印。 appId: data.appId, // 必填,公众号的惟一标识 timestamp: data.timestamp, // 必填,生成签名的时间戳 nonceStr: data.nonceStr, // 必填,生成签名的随机串 signature: data.signature,// 必填,签名,见附录1 jsApiList: ['onMenuShareTimeline', 'onMenuShareAppMessage'] // 必填,须要使用的JS接口列表,全部JS接口列表见附录2 }); wx.ready(function () { wx.onMenuShareTimeline({ title: wxShare.title, desc: wxShare.desc, link: wxShare.link, imgUrl: wxShare.imgUrl }); wx.onMenuShareAppMessage({ title: wxShare.title, desc: wxShare.desc, link: wxShare.link, imgUrl: wxShare.imgUrl }); }) wx.error(function (res) { // config信息验证失败会执行error函数,如签名过时致使验证失败,具体错误信息能够打开config的debug模式查看,也能够在返回的res参数中查看,对于SPA能够在这里更新签名。 }) }) }
至此,后端配置好了,咱们已经可以正常使用微信的接口了,可是微信每日接口请求是有上限的,经过2000次/天,所以若是网站上线后,一量当天访问量超过2000次你的接口将失效,并且每次都请求微信接口两次,形成请求时间浪费,因此咱们须要将以上获取信息缓存在后端,避免形成接口失效以及屡次请求微信后台。
此处直接上代码,利用node_cache包进行缓存
const request = require('request') const express = require('express') const app = express() const sha1 = require('sha1') const waterfall = require('async/waterfall') const NodeCache = require('node-cache') const cache = new NodeCache({stdTTL: 3600, checkperiod: 3600}) //3600秒后过过时 app.get('/wxJssdk', (req, res) => { let wx = req.query // 1)将token、timestamp、nonce三个参数进行字典序排序 let token = 'jegfjaeghfuyawegfgjdbh' let timestamp = wx.timestamp let nonce = wx.nonce // 2)将三个参数字符串拼接成一个字符串进行sha1加密 let list = [token, timestamp, nonce] let result = sha1(list.sort().join('')) // 3)开发者得到加密后的字符串可与signature对比,标识该请求来源于微信 if (result === wx.signature) { res.send(wx.echostr) } else { res.send(false) } }) app.get('/wxJssdk/getJssdk', (req, res) => { let grant_type = 'client_credential' let appid = 'your app id' let secret = 'your app secret' // appscret let steps = [] // 第一步,获取access_token steps.push((cb) => { let steps1 = [] // 第1.1步,从缓存中读取access_token steps1.push((cb1) => { let access_token = cache.get('access_token', (err, access_token) => { cb1(err, access_token) }) }) // 第1.2步,缓存中有access_token则直接返回,若是没有,则从服务器中读取access_token steps1.push((access_token, cb1) => { if (access_token) { cb1(null, access_token, 'from_cache') } else { request('https://api.weixin.qq.com/cgi-bin/token?grant_type=' + grant_type + '&appid=' + appid + '&secret=' + secret, (err, response, body) => { cb1(err, JSON.parse(body).access_token, 'from_server') }) } }) // 第1.3步,若是是新从服务器取的access_token,则缓存起来,不然直接返回 steps1.push((access_token, from_where, cb1) => { if (from_where === 'from_cache') { console.log(' === 成功从缓存中读取access_token: ' + access_token + ' ===') cb1(null, access_token) } else if (from_where === 'from_server') { cache.set('access_token', access_token, (err, success) => { if (!err && success) { console.log(' === 缓存已过时,从服务器中读取access_token: ' + access_token + ' ===') cb1(null, access_token) } else { cb1(err || 'cache设置access_token时,出现未知错误') } }) } else { cb1('1.3获取from_where时,from_where值为空') } }) waterfall(steps1, (err, access_token) => { cb(err, access_token) }) }) // 第二步,获取ticket steps.push((access_token, cb) => { let steps1 = [] // 第2.1步,从缓存中读取ticket steps1.push((cb1) => { let ticket = cache.get('ticket', (err, ticket) => { cb1(err, ticket) }) }) // 第2.2步,缓存中有ticket则直接返回,若是没有,则从服务器中读取ticket steps1.push((ticket, cb1) => { if (ticket) { cb1(null, ticket, 'from_cache') } else { request('https://api.weixin.qq.com/cgi-bin/ticket/getticket?access_token=' + access_token + '&type=jsapi', (err, response, body) => { cb1(err, JSON.parse(body).ticket, 'from_server') }) } }) // 第2.3步,若是新从服务器取的ticket,则缓存起来,不然直接返回 steps1.push((ticket, from_where, cb1) => { if (from_where === 'from_cache') { console.log(' === 成功从缓存中读取ticket: ' + ticket + ' ===') cb1(null, ticket) } else if (from_where === 'from_server') { cache.set('ticket', ticket, (err, success) => { if (!err && success) { console.log(' === 缓存已过时,从服务器中读取ticket: ' + ticket + ' ==='); cb1(null, ticket) } else { cb1(err || 'cache设置ticket时,出现未知错误') } }) } else { cb1('2.3获取from_where时,from_where值为空') } }) waterfall(steps1, (err, ticket) => { cb(err, ticket) }) }) // 第三步,生成签名 steps.push((ticket, cb) => { let jsapi_ticket = ticket let nonce_str = '123456' let timestamp = new Date().getTime() let url = req.query.url let str = 'jsapi_ticket=' + jsapi_ticket + '&noncestr=' + nonce_str + '×tamp=' + timestamp + '&url=' + url let signature = sha1(str) cb(null, { appId: appid, timestamp: timestamp, nonceStr: nonce_str, signature: signature, ticket: ticket }) }) waterfall(steps, (err, data) => { if (err) { res.send({status: 'error', data: err}) } else { res.send({status: 'success', data: data}) } }) }) app.use('/wxJssdk/public', express.static('public')) app.listen(80, err => { if(!err) console.log('connect succeed') })