作了一個網站,放到線上,用微信打開,點擊分享,但是分享後發給朋友的連接卡片是微信默認自帶的,以下:
這標題,描述以及圖片是默認自帶的,醜不說,分享給別人還覺得是盜號網站呢,而接入微信的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') })