-
前言:
業務來源:自主研發的手機app軟件有分享文章到微信或者QQ以及微博的功能,而在微信中再次點擊分享按鈕的時候,情況就出現的不可把控了:
文章顯示的縮略圖不能正常顯示;文章的簡介不能顯示……而我們領導的要求便是再次分享的時候,顯示自己app的logo,於是就開始了微信jsdk的整天研究。(題外話:其實在去年,自己就看過微信jsdk文檔,但是苦於研究不出什么名堂,而且當時是找了一種‘投機取巧’的方法,算是完美的解決了當時的需求,但是意外總是在不經意間就降臨了,那天突然看見了微信公眾號中說道:“
JSSDK自定義分享接口的策略調整
2017-03-29 微信團隊 微信開發者
為規范自定義分享鏈接功能在網頁上的使用,自2017年4月25日起,JSSDK“分享到朋友圈”及“發送給朋友”接口,自定義的分享鏈接,其域名或路徑必須與當前頁面對應的公眾號JS安全域名一致,否則將調用失敗。
例如,當前頁面是 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 均將失敗。
對於未接入微信JSSDK或已接入但JSSDK調用失敗的網頁,被用戶分享時,分享卡片將統一使用默認縮略圖和標題簡介,不允許自定義。
接口完整用法請參考《微信JSSDK說明文檔》,請開發者及時完成調整。”)
這樣的突發情況就如晴天霹靂,讓自己不得不面對再次拾起關於微信jsdk的研究。歷經“山重水復”的過程,特地把踩過的坑粉分享出來,這樣小伙伴們就不會陷入同樣的錯誤中了(下面的版本是基於nodejs環境下的~)。
-
過程:
這是官方文檔地址: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接口處理接下來的數據(關於這部分的介紹,文檔中很詳細,相信大家一看便知,就不多說了。)
-
后言:
關於掉微信分享接口,其實這個功能應該在年前就做好的,只是當時也是因為簽名總是獲取失敗的原因而將此擱置,直至今天不得不重新開始研究,突然明白了“凡是出來混,總是要還的”這句至理名言的真理性。而自己遇到的這個問題,也希望以后不會再犯,還有可以用到的小伙伴們也能用上。