前端對接微信分享功能
最近,由於公司業務需要,接入了 微信web端分享 介面。雖然微信的介面文件已經很詳細了,但是缺少實戰程式碼。小編搜了一下掘金網站好像也很少這方面的分享(或許是太過簡單,大神們都不屑於分享這類經驗。當然也有客觀原因,現在大多都做小程式了,微信web端的流量被分流了很多。
準備
此為 ofollow,noindex">微信公眾平臺介面文件 地址
此為 微信公眾平臺介面測試帳號申請 地址
此為 獲取基礎access_token 地址
此為 微信JS-SDK說明文件 地址
以下nodejs端是基於egg框架。
開發
1、申請公眾號或測試版公眾號
公眾號申請(這裡不詳說公眾號申請,有需要自己去官網看文件)用於生產環境的開發。
對於前期開發階段,可以去 微信公眾平臺介面測試帳號申請 ,微信掃描二維碼即可申請。
注意事項:
1、填寫JS介面安全域名,繫結伺服器域名
2、掃描“測試號二維碼”,把你的微訊號加入列表,然後你的微信就有許可權進行測試了。 沒有加入列表的微訊號是沒有許可權使用微信介面的 ,這裡要注意一下。
至此,測試公眾號配置完成。
2、獲取基礎access_token介面。
微信有兩個 access_token ,一個是基礎 access_token ,一個是網頁授權 access_token ,具體區別,如圖所述:

就好。
介面api:
(get) APPID%26amp%3Bsecret%3DAPPSECRET" rel="nofollow,noindex">api.weixin.qq.com/cgi-bin/tok…
引數:
grant_type:填寫“client_credential”
appid: 公眾號appId
secret: 公眾號secret
返回值:
成功時
{ "access_token":"ACCESS_TOKEN", "expires_in":7200 } 複製程式碼
expires_in為該access_token的有限時間,由於微信對於獲取accesss_token介面每天有次數限制,所以我們需要把access_token存到伺服器裡,等到其失效後再重新發起請求。
異常時
{ "errcode":錯誤碼, "errmsg":錯誤資訊 } 複製程式碼
錯誤碼:
-
-1 系統繁忙,此時請開發者稍候再試
-
0 請求成功
-
40001 AppSecret錯誤或者AppSecret不屬於這個公眾號,請開發者確認AppSecret的正確性
-
40002 請確保grant_type欄位值為client_credential
-
40164 呼叫介面的IP地址不在白名單中,請在介面IP白名單中進行設定
注意:
1、當遇到錯誤碼為 40164 (呼叫介面的IP地址不在白名單中,請在介面IP白名單中進行設定),要去到公眾號的白名單列表加上自己伺服器的IP, 測試公眾號賬號是不用配置白名單的 ,所以在 把測試公眾號 引數換成 正式公眾號 引數時,記得配置白名單。
程式碼片段:
const { ctx, config } = this; let timestamp = new Date().valueOf(); // 判斷快取裡是否有access_token且沒有過期,並且獲取該access_token時的appId與現在的一致,判斷appId是為了避免切換不同公眾號配置時沒有清快取出現錯誤 // 如果以上條件不能同時滿足,則重新請求access_token if (!ctx.session.tokenObj || ctx.session.tokenObj.expires_in < timestamp || ctx.session.tokenObj.app_id !== config.wx.appId) { const tokenResult = await ctx.curl(`https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid=${config.wx.appId}&secret=${config.wx.secret}`, { dataType: 'json' }); if (tokenResult.status === 200 && tokenResult.data && tokenResult.data.access_token) { ctx.session.tokenObj = { access_token: tokenResult.data.access_token, expires_in: timestamp + tokenResult.data.expires_in * 1000, app_id: config.wx.appId }; } else { // 記錄請求錯誤日誌,方便定位錯誤 // 因為該快取access_token已經不能使用,請求錯誤時記得把access_token快取也清空。 ctx.session.tokenObj = null; ctx.logger.error(new Error(`wxconfig: ${JSON.stringify(config.wx)}`)); ctx.logger.error(new Error(`tokenResult: ${JSON.stringify(tokenResult)}`)); } } 複製程式碼
3、獲取jsapi_ticket
介面api:
(get) api.weixin.qq.com/cgi-bin/tic…
引數:
access_token:上一步請求返回的access_token
type:'jsapi'
返回值:
成功時:
{ "errcode":0, "errmsg":"ok", "ticket":"bxLdikRXVbTPdHSM05e5u5sUoXNKd8-41ZO3MhKoyN5OfkWITDGgnr2fwJ0m9E8NYzWKVZvdVtaUgWvsdshFKA", "expires_in":7200 } 複製程式碼
與access_token一樣,expires_in為該ticket的有限時間,由於微信對於獲取ticket介面每天有次數限制,所以我們需要把access_token存到伺服器裡,等到其失效後再重新發起請求。
異常時:
{ "errcode":42001, "errmsg":"access_token expired hint: [5bugDA09718938!]" } 複製程式碼
程式碼:
timestamp = new Date().valueOf(); // 判斷快取裡是否有jsapi_ticket且沒有過期,並且獲取該jsapi_ticket時的appId與現在的一致,判斷appId是為了避免切換不同公眾號配置時沒有清快取出現錯誤 // 如果以上條件不能同時滿足,則重新請求access_token if (!ctx.session.jsapiObj || ctx.session.jsapiObj.expires_in < timestamp || ctx.session.jsapiObj.app_id !== config.wx.appId) { const jsapiResult = await ctx.curl(`https://api.weixin.qq.com/cgi-bin/ticket/getticket?access_token=${ctx.session.tokenObj.access_token}&type=jsapi`, { dataType: 'json' }); if (jsapiResult.status === 200 && jsapiResult.data && jsapiResult.data.errcode === 0) { ctx.session.jsapiObj = { ticket: jsapiResult.data.ticket, expires_in: timestamp + jsapiResult.data.expires_in * 1000, app_id: config.wx.appId }; ctx.logger.error(new Error(`jsapiResult:success: ${JSON.stringify(jsapiResult)}`)); } else { // 記錄請求錯誤日誌,方便定位錯誤 // 因為該快取jsapi_ticket已經不能使用,請求錯誤時記得把jsapi_ticket快取也清空。 ctx.session.jsapiObj = null; ctx.logger.error(new Error(`wxconfig: ${JSON.stringify(config.wx)}`)); ctx.logger.error(new Error(`jsapiResult: ${JSON.stringify(jsapiResult)}`)); } } 複製程式碼
我發現,這個api基本不會怎麼出現錯誤碼,基本上access_token如果沒有問題,這個api的呼叫也不會報錯。
常出現的錯誤多是上一步的access_token快取策略不合理導致這個介面的access_token引數的值不是有效的access_token。
4、獲取簽名演算法signature值
簽名生成規則如下:
參與簽名的欄位包括
有效的jsapi_ticket、
noncestr(隨機字串)、
timestamp(時間戳)、
url(當前網頁的URL,不包含#及其後面部分) 。
對所有待簽名引數 按照欄位名的ASCII 碼從小到大排序 (字典序)後,使用URL鍵值對的格式(即key1=value1&key2=value2…)拼接成字串string1。這裡需要注意的是 所有引數名均為小寫字元 。對string1作 sha1加密 ,欄位名和欄位值都採用原始值, 不進行URL 轉義 。
程式碼:
const jsapi_ticket = ctx.session.jsapiObj.ticket; const uuidv1 = require('uuid/v1'); const noncestr = uuidv1(); timestamp = new Date().valueOf(); const { url } = ctx.query; const string1 = `jsapi_ticket=${jsapi_ticket}&noncestr=${noncestr}×tamp=${timestamp}&url=${url}`; const crypto = require('crypto'); const hash = crypto.createHash('sha1'); hash.update(string1); const signature = hash.digest('hex'); 複製程式碼
注意:
- 簽名用的noncestr和timestamp必須與下一步的wx.config中的nonceStr和timestamp相同。
- 簽名用的url必須是 呼叫JS介面頁面的完整URL 。
- 出於 安全 考慮,建議在 伺服器端 實現簽名邏輯。
- 引數先後順序就jsapi_ticket、noncestr、timestamp、url,這個順序搞錯會導致簽名
簽名演算法這一步很關鍵,不合理會導致下一步出現錯誤。具體的錯誤相對應的解決辦法可以檢視官方文件,裡面很詳細。地址:微信JS-SDK說明文件,檢視該網站的 附錄5-常見錯誤及解決方法
以上步驟都是node端實現,以下為web端的程式碼。
5、引入微信js檔案
官方js地址: res.wx.qq.com/open/js/jwe…
官方js備用地址: res2.wx.qq.com/open/js/jwe…
6、通過wx.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: , // 必填,生成簽名的時間戳,與生成簽名的timestamp要一致 nonceStr: '', // 必填,生成簽名的隨機串,與生成簽名的nonceStr要一致 signature: '',// 必填,簽名 jsApiList: [] // 必填,需要使用的JS介面列表 }); 複製程式碼
jsApiList具體可檢視:微信JS-SDK說明文件,檢視該網站的 附錄2-所有JS介面列表
7、呼叫微信分享介面
程式碼:
wx.ready(function() { const title = '分享標題'; const desc = '分享描述'; const imgUrl = '分享圖片連結'; // 朋友圈 wx.onMenuShareTimeline({ title: title, // 分享標題 link: url, // 分享連結,該連結域名或路徑必須與當前頁面對應的公眾號JS安全域名一致 imgUrl: imgUrl // 分享圖示 }); // 微信朋友 wx.onMenuShareAppMessage({ title: title, // 分享標題 desc: desc, // 分享描述 link: url, // 分享連結,該連結域名或路徑必須與當前頁面對應的公眾號JS安全域名一致 imgUrl: imgUrl, // 分享圖示 type: 'link', // 分享型別,music、video或link,不填預設為link dataUrl: '' // 如果type是music或video,則要提供資料鏈接,預設為空 }); // qq wx.onMenuShareQQ({ title: title, // 分享標題 desc: desc, // 分享描述 link: url, // 分享連結 imgUrl: imgUrl // 分享圖示 }); // qq空間 wx.onMenuShareQZone({ title: title, // 分享標題 desc: desc, // 分享描述 link: url, // 分享連結 imgUrl: imgUrl // 分享圖示 }); // 騰訊微博 wx.onMenuShareWeibo({ title: title, // 分享標題 desc: desc, // 分享描述 link: url, // 分享連結 imgUrl: imgUrl // 分享圖示 }); }); 複製程式碼
另外還有一些呼叫成功success事件、呼叫失敗fail事件、使用者點選取消分享cancel事件。具體可看:微信JS-SDK說明文件 - JSSDK使用步驟 - 介面呼叫說明
至此,微信分享介面已經可用了。希望對剛接觸微信介面的你有幫助。
全部程式碼
// NODE端 async getWXApiTicket() { const { ctx, config } = this; let timestamp = new Date().valueOf(); if (!ctx.session.tokenObj || ctx.session.tokenObj.expires_in < timestamp || ctx.session.tokenObj.app_id !== config.wx.appId) { const tokenResult = await ctx.curl(`https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid=${config.wx.appId}&secret=${config.wx.secret}`, { dataType: 'json' }); if (tokenResult.status === 200 && tokenResult.data && tokenResult.data.access_token) { ctx.session.tokenObj = { access_token: tokenResult.data.access_token, expires_in: timestamp + tokenResult.data.expires_in * 1000, app_id: config.wx.appId }; } else { ctx.session.tokenObj = null; ctx.logger.error(new Error(`wxconfig: ${JSON.stringify(config.wx)}`)); ctx.logger.error(new Error(`tokenResult: ${JSON.stringify(tokenResult)}`)); } } let res = { code: 500, msg: '獲取失敗' }; if (ctx.session.tokenObj && ctx.session.tokenObj.app_id === config.wx.appId) { timestamp = new Date().valueOf(); if (!ctx.session.jsapiObj || ctx.session.jsapiObj.expires_in < timestamp || ctx.session.jsapiObj.app_id !== config.wx.appId) { const jsapiResult = await ctx.curl(`https://api.weixin.qq.com/cgi-bin/ticket/getticket?access_token=${ctx.session.tokenObj.access_token}&type=jsapi`, { dataType: 'json' }); if (jsapiResult.status === 200 && jsapiResult.data && jsapiResult.data.errcode === 0) { ctx.session.jsapiObj = { ticket: jsapiResult.data.ticket, expires_in: timestamp + jsapiResult.data.expires_in * 1000, app_id: config.wx.appId }; } else { ctx.session.jsapiObj = null; ctx.logger.error(new Error(`wxconfig: ${JSON.stringify(config.wx)}`)); ctx.logger.error(new Error(`jsapiResult: ${JSON.stringify(jsapiResult)}`)); } } if (ctx.session.jsapiObj && ctx.session.jsapiObj.app_id === config.wx.appId) { const jsapi_ticket = ctx.session.jsapiObj.ticket; const uuidv1 = require('uuid/v1'); const noncestr = uuidv1(); timestamp = new Date().valueOf(); const { url } = ctx.query; const string1 = `jsapi_ticket=${jsapi_ticket}&noncestr=${noncestr}×tamp=${timestamp}&url=${url}`; const crypto = require('crypto'); const hash = crypto.createHash('sha1'); hash.update(string1); const signature = hash.digest('hex'); res = { code: 0, data: { nonceStr: noncestr, timestamp, signature, appId: config.wx.appId, jsapi_ticket, string1 } }; } } ctx.body = res; } // JS端 const wx = window['wx']; const url = location.href.split('#')[0]; $.get('/getWXApiTicket?url=' + encodeURIComponent(url), function(res) { if (res.code === 0) { wx.config({ debug: false, // 開啟除錯模式,呼叫的所有api的返回值會在客戶端alert出來,若要檢視傳入的引數,可以在pc端開啟,引數資訊會通過log打出,僅在pc端時才會列印。 appId: res.data.appId, // 必填,公眾號的唯一標識 timestamp: res.data.timestamp, // 必填,生成簽名的時間戳 nonceStr: res.data.nonceStr, // 必填,生成簽名的隨機串 signature: res.data.signature, // 必填,簽名 jsApiList: ['onMenuShareTimeline', 'onMenuShareAppMessage', 'onMenuShareQQ', 'onMenuShareQZone', 'onMenuShareWeibo'] // 必填,需要使用的JS介面列表 }); wx.ready(function() { const title = '分享標題'; const desc = '分享描述'; const imgUrl = '分享圖片連結'; // 朋友圈 wx.onMenuShareTimeline({ title: title, // 分享標題 link: url, // 分享連結,該連結域名或路徑必須與當前頁面對應的公眾號JS安全域名一致 imgUrl: imgUrl // 分享圖示 }); // 微信朋友 wx.onMenuShareAppMessage({ title: title, // 分享標題 desc: desc, // 分享描述 link: url, // 分享連結,該連結域名或路徑必須與當前頁面對應的公眾號JS安全域名一致 imgUrl: imgUrl, // 分享圖示 type: 'link', // 分享型別,music、video或link,不填預設為link dataUrl: '' // 如果type是music或video,則要提供資料鏈接,預設為空 }); // qq wx.onMenuShareQQ({ title: title, // 分享標題 desc: desc, // 分享描述 link: url, // 分享連結 imgUrl: imgUrl // 分享圖示 }); // qq空間 wx.onMenuShareQZone({ title: title, // 分享標題 desc: desc, // 分享描述 link: url, // 分享連結 imgUrl: imgUrl // 分享圖示 }); // 騰訊微博 wx.onMenuShareWeibo({ title: title, // 分享標題 desc: desc, // 分享描述 link: url, // 分享連結 imgUrl: imgUrl // 分享圖示 }); }); } }); 複製程式碼