業務來源:自主研發的手機app軟件有分享文章到微信或者QQ以及微博的功能,而在微信中再次點擊分享按鈕的時候,狀況就出現的不可把控了:php
文章顯示的縮略圖不能正常顯示;文章的簡介不能顯示……而咱們領導的要求即是再次分享的時候,顯示本身app的logo,因而就開始了微信jsdk的成天研究。(題外話:其實在去年,本身就看過微信jsdk文檔,可是苦於研究不出什麼名堂,並且當時是找了一種‘投機取巧’的方法,算是完美的解決了當時的需求,可是意外老是在不經意間就降臨了,那天忽然看見了微信公衆號中說道:「html
JSSDK自定義分享接口的策略調整
2017-03-29 微信團隊 微信開發者前端
爲規範自定義分享連接功能在網頁上的使用,自2017年4月25日起,JSSDK「分享到朋友圈」及「發送給朋友」接口,自定義的分享連接,其域名或路徑必須與當前頁面對應的公衆號JS安全域名一致,不然將調用失敗。node
例如,當前頁面是 http://www.abc.com/123,其公衆號對應的JS安全域名爲 www.abc.com 以及 www.xyz.com,則分享自定義連接 http://www.abc.com/456 能夠成功,分享 http://www.xyz.com/123 或 http://www.def.com/123 均將失敗。ajax
對於未接入微信JSSDK或已接入但JSSDK調用失敗的網頁,被用戶分享時,分享卡片將統一使用默認縮略圖和標題簡介,不容許自定義。redis
接口完整用法請參考《微信JSSDK說明文檔》,請開發者及時完成調整。」)算法
這樣的突發狀況就如晴天霹靂,讓本身不得不面對再次拾起關於微信jsdk的研究。歷經「山重水複」的過程,特意把踩過的坑粉分享出來,這樣小夥伴們就不會陷入一樣的錯誤中了(下面的版本是基於nodejs環境下的~)。api
這是官方文檔地址:https://mp.weixin.qq.com/wiki?t=resource/res_main&id=mp1421141115&token=&lang=zh_CN,先看文檔再上手;按照步驟,一步步進行;關於具體的文檔中都有,小夥伴們仔細看文檔確定就能理解,接下來就只是說說本身在實現這個功能時,問題出在哪裏吧……緩存
1.注意點一:(首先配置js接口安全域名,即程序運行的網址是什麼,須要下載的文件,官網上直接下載配置便可;配置完成後,可在「開發者中心」查看對應的接口權限); 安全
2.在須要調用JS接口的頁面引入以下JS文件,(支持https):http://res.wx.qq.com/open/js/jweixin-1.2.0.js
<script src="http://res.wx.qq.com/open/js/jweixin-1.2.0.js"></script>
3.注意點二,也是最關鍵最核心的部分,由於若是這裏配置成功,後面即可成功的調用微信粉分享接口,不然便失敗;
wx.config({
debug: true, // 開啓調試模式,調用的全部api的返回值會在客戶端alert出來,若要查看傳入的參數,能夠在pc端打開,參數信息會經過log打出,僅在pc端時纔會打印。
appId: '', // 必填,公衆號的惟一標識
timestamp: , // 必填,生成簽名的時間戳
nonceStr: '', // 必填,生成簽名的隨機串
signature: '',// 必填,簽名,見附錄1
jsApiList: [] // 必填,須要使用的JS接口列表,全部JS接口列表見附錄2
});
這即是須要配置的東西,下面談談心酸的踩坑經歷:
1)timestamp //當前時間戳 這裏是秒級別的時間戳格式
let timestamp = parseInt( new Date().getTime()/1000);//秒級別
2)nonceStr //隨機字符串 這裏是保留長度爲16的隨機字符串
let nonceStr = Math.random().toString(36).substr(2,16);//長度爲16的隨機字符串
3)signature ——> 重點來了!關於這個簽名的取值,硬生生的耗費了本身很長很長時間,也是由於本身不夠聰明,沒看清楚文檔寫的,這裏仍是要感謝老大,昨晚陪本身加班快十點找問題,直到今早上終於完美解決掉。
關於動態產生的簽名:
a.先使用appid,secret,經過get請求「https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid=appid&secret=secret」,獲得access_token;
b.用獲得的access_token,再次經過get請求」https://api.weixin.qq.com/cgi-bin/ticket/getticket?access_token='+access_token+'&type=jsapi」,最終獲得jsapi_ticket;
c.簽名生成規則以下:參與簽名的字段包括noncestr(隨機字符串), 有效的jsapi_ticket, timestamp(時間戳), url(當前網頁的URL,不包含#及其後面部分) 。對全部待簽名參數按照字段名的ASCII 碼從小到大排序(字典序)後,使用URL鍵值對的格式(即key1=value1&key2=value2…)拼接成字符串string1。例如:
jsapi_ticket=sM4AOVdWfPE4DxkXGEs8VMCPGGVi4C3VM0P37wVUCFvkVAy_90u5h9nbSlYy3-Sl-HhTdfl2fzFy1AOcHKP7qg&noncestr=Wm3WZYTPz0wzccnW×tamp=1414587457&url=http://mp.weixin.qq.com?params=value
這裏須要注意的是全部參數名均爲小寫字符。對string1做sha1加密,字段名和字段值都採用原始值,不進行URL 轉義,獲得signature。
即signature=sha1(string1)。
注意點:重點!
生成簽名以前必須先了解一下jsapi_ticket,jsapi_ticket是公衆號用於調用微信JS接口的臨時票據。正常狀況下,jsapi_ticket的有效期爲7200秒,經過access_token來獲取。因爲獲取jsapi_ticket的api調用次數很是有限,頻繁刷新jsapi_ticket會致使api調用受限,影響自身業務,開發者必須在本身的服務全局緩存jsapi_ticket 。本來打算在項目中使用session來緩存這兩個,最終在老大的知道下,這裏使用的是redis存儲緩存;
完整前臺HTML頁面代碼:
<script src="http://res.wx.qq.com/open/js/jweixin-1.2.0.js"></script> <script> jQuery(function () { //頁面初始化完成後,先post請求將當前頁面的url傳到後臺,進行signature的計算,而這裏必定記得要: encodeURIComponent()一下,去後臺後在: decodeURIComponent(),要不就是頻繁報「invalid signature"的錯誤 jQuery.post("/share/getshare", {"url": encodeURIComponent(window.location.href.split('#')[0]),"t": new Date().getTime()}, function (result) { if (result.errno != 0) { alert("您當前的網絡不穩定請稍後再試!"); return; } var shareUrl = result.data.url; wx.config({ debug: false, // 開啓調試模式,調用的全部api的返回值會在客戶端alert出來,若要查看傳入的參數,能夠在pc端打開,參數信息會經過log打出,僅在pc端時纔會打印。 appId: 'wxf919e3b61eca36ae', // 必填,公衆號的惟一標識 timestamp: result.data.timestamp, // 必填,生成簽名的時間戳 nonceStr: result.data.nonceStr, // 必填,生成簽名的隨機串 signature: result.data.signature,// 必填,簽名,見附錄1 jsApiList: ['onMenuShareAppMessage','onMenuShareTimeline','onMenuShareQQ','onMenuShareWeibo'] // 必填,須要使用的JS接口列表,全部JS接口列表見附錄2 }); wx.ready(function () { var title = "{{data.title}}"; var desc = "{{data.scontent}}"?"{{data.scontent}}":""; var imgUrl = "http://static.gangguwang.com/image/2016/12/16/16/55/5853abfc101887000800111f.jpg"; wx.onMenuShareTimeline({//「分享到朋友圈」 title: title, // 分享標題 link: shareUrl, // 分享連接 imgUrl: imgUrl, // 分享圖標 success: function () { // 用戶確認分享後執行的回調函數 }, cancel: function () { // 用戶取消分享後執行的回調函數 } }); wx.onMenuShareAppMessage({//「分享給朋友」 title: title, // 分享標題 desc: desc, // 分享描述 link: shareUrl, // 分享連接 imgUrl: imgUrl, // 分享圖標 type: 'link', // 分享類型,music、video或link,不填默認爲link dataUrl: '', // 若是type是music或video,則要提供數據連接,默認爲空 success: function () { console.log('succ~'); // 用戶確認分享後執行的回調函數 }, cancel: function () { console.log('fail~'); // 用戶取消分享後執行的回調函數 } }); wx.onMenuShareQQ({//「分享到QQ」 title: title, // 分享標題 desc: desc, // 分享描述 link: shareUrl, // 分享連接 imgUrl: imgUrl, // 分享圖標 success: function () { // 用戶確認分享後執行的回調函數 }, cancel: function () { // 用戶取消分享後執行的回調函數 } }); wx.onMenuShareWeibo({//「分享到騰訊微博」 title: title, // 分享標題 desc: desc, // 分享描述 link: shareUrl, // 分享連接 imgUrl: imgUrl, // 分享圖標 success: function () { // 用戶確認分享後執行的回調函數 }, cancel: function () { // 用戶取消分享後執行的回調函數 } }); }); }); }) </script>
後臺的處理邏輯:
//這裏先要引入sha1依賴包 //getshare方法即是得到動態簽名的方法(這是基於node.js環境下的) async getshareAction(){ //將前臺獲得的url先decodeURIComponent()一下再使用 let url = decodeURIComponent(this.post().url); //當前時間戳 let timestamp = parseInt( new Date().getTime()/1000); //隨機字符串 let nonceStr = Math.random().toString(36).substr(2,16); ////獲取緩存信息 存在redis中,從 redis 裏獲取緩存(全局緩存jsapi_ticket -----> 有效期7200秒) let ticket = await think.cache('ticket_weixinshare_ywg', undefined, {type: 'redis'}); let access_token = await think.cache('token_weixinshare_ywg', undefined, {type: 'redis'}); let req = think.promisify(request.get); if(!access_token){//不存在 let options1 = {//獲取 acess_token url: 'https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid=appid&secret=secret', method: "get" }; let res = await req(options1); access_token = JSON.parse(res.body).access_token; //設置緩存 access_token(這裏是thinkjs的redis設置緩存的方法) await think.cache('token_weixinshare_ywg', access_token); } if(!ticket){ let options2={//計算 jsapi_ticket url: 'https://api.weixin.qq.com/cgi-bin/ticket/getticket?access_token='+access_token+'&type=jsapi', method: "get" }; let res2 = await req(options2); if(JSON.parse(res2.body).errcode==0){ ticket=JSON.parse(res2.body).ticket; //設置緩存 ticket await think.cache('ticket_weixinshare_ywg', ticket); }else{ console.log('=========error==='+JSON.parse(res2.body).errmsg) } } //按照字段名的ASCII 碼從小到大排序(字典序) let raw = async (args) =>{ let keys = Object.keys(args); keys = keys.sort(); let newArgs = {}; keys.forEach(function (key) { newArgs[key.toLowerCase()] = args[key]; }); let string = ''; for (let k in newArgs) { string += '&' + k + '=' + newArgs[k]; } string = string.substr(1); return string; }; let ret = { jsapi_ticket: ticket, nonceStr: nonceStr, timestamp: timestamp, url: url }; let string1 = await raw(ret); //將獲得的字符串進行sha1加密,而後返回將 wx.config()中所須要的值返回到前臺頁面 let signature = sha1(string1); this.success({"timestamp":timestamp,"nonceStr":nonceStr,"signature":signature,"url":url}); }
至此運行,即可以神清氣爽的看見下面的提示:
而昨天一成天的狀況即是:
而對於「invalid signature」的問題排查官方文檔也給出了相應的介紹:
2.invalid signature簽名錯誤。建議按以下順序檢查:
1.確認簽名算法正確,可用 http://mp.weixin.qq.com/debug/cgi-bin/sandbox?t=jsapisign 頁面工具進行校驗。
2.確認config中nonceStr(js中駝峯標準大寫S), timestamp與用以簽名中的對應noncestr, timestamp一致。
3.確認url是頁面完整的url(請在當前頁面alert(location.href.split('#')[0])確認),包括'http(s)://'部分,以及'?'後面的GET參數部分,但不包括'#'hash後面的部分。
4.確認 config 中的 appid 與用來獲取 jsapi_ticket 的 appid 一致。
5.確保必定緩存access_token和jsapi_ticket。
6.確保你獲取用來簽名的url是動態獲取的,動態頁面可參見實例代碼中php的實現方式。若是是html的靜態頁面在前端經過ajax將url傳到後臺簽名,前端須要用js獲取當前頁面除去'#'hash部分的連接(可用location.href.split('#')[0]獲取,並且須要encodeURIComponent),由於頁面一旦分享,微信客戶端會在你的連接末尾加入其它參數,若是不是動態獲取當前連接,將致使分享後的頁面簽名失敗。
而本身按照步驟來實現的獲取簽名,也老是報錯的根本緣由就是url取得有問題,所以建議你們本身閱讀文檔,獲取到頁面的完整路徑並先進行處理後再使用。(本身使用的頁面完整url路徑:http://127.0.0.1:8361/share?id=xxx).
4.成功得到簽名後,即可以經過ready接口處理接下來的數據(關於這部分的介紹,文檔中很詳細,相信你們一看便知,就很少說了。)
關於掉微信分享接口,其實這個功能應該在年前就作好的,只是當時也是由於簽名老是獲取失敗的緣由而將此擱置,直至今天不得不從新開始研究,忽然明白了「凡是出來混,老是要還的」這句至理名言的真理性。而本身遇到的這個問題,也但願之後不會再犯,還有能夠用到的小夥伴們也能用上。