【前言】 某天,接到這么一個(gè)需求:自定義微信網(wǎng)頁(yè)分享出來(lái)的標(biāo)題,描述和圖標(biāo)。以前沒(méi)玩過(guò)這個(gè),感覺(jué)應(yīng)該很簡(jiǎn)單,動(dòng)手了之后,躺過(guò)各種坑才知道并沒(méi)那么容易。完全獨(dú)立研究排錯(cuò),感受頗多,分享出來(lái)給大家鋪一鋪路
一: 需求來(lái)源
?開(kāi)發(fā)了一個(gè)移動(dòng)端H5活動(dòng)頁(yè)面,該頁(yè)面要實(shí)現(xiàn)微信中的“分享給好友”,“分享到朋友圈”,“分享到QQ”,“分享到騰訊微博”等功能。如果沒(méi)有接入jssdk,分享出來(lái)的就是如下樣子:
?除了標(biāo)題比較容易自定義以外,描述內(nèi)容和圖標(biāo)都是默認(rèn)樣式,好丑。如果就這么分享出去,活動(dòng)本身的吸引力就會(huì)被大打折扣,所以,這個(gè)功能必須完成!
?
二: 需要工具
?我自己在開(kāi)發(fā)的時(shí)候,不知道需要這些東西,都是遇到一個(gè)坑,才去找的一個(gè)蘿卜;這里提前提到,帶你走捷徑:
- 微信公眾平臺(tái)技術(shù)文檔 - 必備,接入微信的功能,一切都是從這里開(kāi)始
- 微信JS接口簽名校驗(yàn)工具 - 該工具能幫你核查生成的簽名的正確性
-
微信web開(kāi)發(fā)者工具 - 模擬web網(wǎng)頁(yè)調(diào)試微信接口,能查看JS-SDK的狀態(tài),查看權(quán)限列表,還能接入移動(dòng)設(shè)備調(diào)試;完美解決微信接口調(diào)試難的問(wèn)題。獲取方法:
登錄微信公眾平臺(tái) -> 進(jìn)入開(kāi)發(fā)者工具 -> 下載微信web開(kāi)發(fā)者工具
下載后,使用方法和chrome類(lèi)似,不多說(shuō)。 -
Xshell - 用來(lái)部署服務(wù)端代碼(linux環(huán)境),因?yàn)榻尤胛⑿拍K必須是線上聯(lián)調(diào)的,所以你的服務(wù)端都是需要部署到線上,即使是聯(lián)調(diào)服務(wù)器,也要滿(mǎn)足外網(wǎng)能訪問(wèn)。當(dāng)然,如果服務(wù)端人員能幫你解決這個(gè)問(wèn)題,請(qǐng)無(wú)視。
?
三: 開(kāi)發(fā)前需要
1. 綁定域名
?登錄微信公眾平臺(tái)(真實(shí)運(yùn)營(yíng)的服務(wù)號(hào)),進(jìn)入“公眾號(hào)設(shè)置”,選擇導(dǎo)航標(biāo)簽中的“功能設(shè)置”,填寫(xiě)“JS接口安全域名”。注意,該頁(yè)上也有描述,需要下載一個(gè) .txt
文件放到你網(wǎng)頁(yè)(你所填寫(xiě)的安全域名)要部署的服務(wù)器的根目錄(或目錄路徑)下,這個(gè)文件是微信用來(lái)檢測(cè)你的服務(wù)器是否可達(dá)使用,不同公眾號(hào)的txt
文件不同,我就不小心用放在桌面上的自己的公眾號(hào)的測(cè)試文件放到服務(wù)器中,然后以公司的服務(wù)號(hào)去跑,一直通不過(guò)。
?服務(wù)號(hào)才有對(duì)應(yīng)的接口權(quán)限,如果你綁定了一個(gè)私人的公眾號(hào),一樣沒(méi)法使用。你可以“開(kāi)發(fā)者中心”查看一下你是否擁有對(duì)應(yīng)接口的開(kāi)發(fā)權(quán)限,免得白忙活。
2. 引入 JS 文件
? 和其他的模塊一致,想要調(diào)用微信的功能,同樣也需要引入微信js文件,目前最新版本為1.2.0;該文件有 http形式 和 https形式,下載之后在需要調(diào)用js接口的頁(yè)面引入。也可以全局引入,已經(jīng)壓縮過(guò),大小為11.5k,還算小。
?模塊化推薦使用 AMD/CMD 標(biāo)準(zhǔn)加載
?
四: 開(kāi)始開(kāi)發(fā)
1. 權(quán)限驗(yàn)證配置
?有了上述的準(zhǔn)備,我們可以通過(guò)config
在要使用JS-SDK的頁(yè)面注入配置信息,這是你調(diào)用微信接口微信驗(yàn)證的入口。config
接收一個(gè)對(duì)象,具體如下:
即:
wx.config({
debug: false, // 是否打開(kāi)調(diào)試模式,調(diào)用的api會(huì)被alert出來(lái),在pc端也能看到log信息
appid:'', // 必填,微信公眾號(hào)的唯一標(biāo)識(shí)
timestamp: , // 必填,生成簽名的時(shí)間戳
nonceStr: '', // 必填,生成簽名的隨機(jī)串
signature: '', //必填,用于驗(yàn)證的簽名
jsApiList: [] //必填,需要使用到的JS接口列表
})
?大部分文檔都這么一說(shuō)而過(guò),然而還是容易踩坑。
?糞坑: 為什么叫糞坑,因?yàn)樽銐虻椭巧獭R婚_(kāi)始沒(méi)對(duì)微信公眾號(hào)的這些機(jī)制了解多少,直接用appid
, appSecret
生成靜態(tài)的accesstoken
,然后用accesstoken
生成了靜態(tài)的jsapi_ticket
,最后生成靜態(tài)的signature
,然后就把這幾個(gè)參數(shù)填進(jìn)去了,肯定錯(cuò)啊。這些信息除了id
和secret
其他都是動(dòng)態(tài)的。
?坑1: 可以從上圖中看出來(lái),我直接設(shè)置debug
參數(shù)為false
,并不是我覺(jué)得多余,而是根本看不明白,本地調(diào)試基本沒(méi)法看出問(wèn)題。怎么辦? 在 “需要工具” 中提供的微信web開(kāi)發(fā)者工具,他由微信團(tuán)隊(duì)提供,用了才知道,要啥debug調(diào)試,這個(gè)工具簡(jiǎn)直完美,所有信息展示 清清楚楚,一目了然。
?坑2: timestamp
配置項(xiàng)處沒(méi)有引號(hào),也就是說(shuō),這里的類(lèi)型是 INT。另外,還有一個(gè)很重要的就是,這里 單位是秒,相信絕大多數(shù)一開(kāi)始和我一樣,以為是毫秒,直接new Date().getTime()
完事。踩了踩了。
?坑3:nonceStr
必須遵循駝峰式命名規(guī)則。這里比較容易混淆是因?yàn)樵谏珊灻且徊剑枰冉M成字符串string1
,那里要求所有key
包括noncestr
也都是小寫(xiě)的,所以誤以為這里也是小寫(xiě)的。
?坑4:或許你有看到jsApiList
中有的文檔有checkJsApi
項(xiàng),有的又沒(méi)有。是這樣的:在真正調(diào)用微信接口功能如“分享到朋友圈”之前,我們可以對(duì)微信接口先進(jìn)行驗(yàn)證接口是否可用,即,checkJsApi
是一個(gè)基礎(chǔ)接口,所以也要加入該數(shù)組中,如下
?為了不打亂思路,這里將如何獲取簽名過(guò)程放在最后面。因涉及太多重要信息,這個(gè)步驟要求必須在服務(wù)端實(shí)現(xiàn)。
2. ready驗(yàn)證成功回調(diào)
?config
信息驗(yàn)證成功后,下一步就是使用微信的各種接口。微信提供config
的回調(diào)函數(shù)ready(),供我們執(zhí)行這些接口。注意:config
是一個(gè)客戶(hù)端的異步操作,所以如果需要在頁(yè)面加載時(shí)就調(diào)用相關(guān)接口,則須把相關(guān)接口放在ready函數(shù)中調(diào)用來(lái)確保正確執(zhí)行。但是如果是一些交互操作,這不必寫(xiě)在ready中,可以直接調(diào)用。比如說(shuō)“發(fā)送給好友”就是與用戶(hù)交互后的操作。
3. error驗(yàn)證失敗處理
wx.error(function(res){
console.log(res); // res為微信返回的錯(cuò)誤結(jié)果
})
4. 自定義分享內(nèi)容
?這里我定義了我們需要分享的一些內(nèi)容,如, 標(biāo)題,內(nèi)容描述,和圖片地址。
坑:這里的圖片地址必須是絕對(duì)地址。上圖中的
data.url
是發(fā)送給微信校驗(yàn)的那個(gè)url
.
4. 定義分享接口
?這里只寫(xiě)了幾個(gè)最常用的接口,如需要分享西,
- 分享到朋友圈 - onMenuShareTimeLine()
接受一個(gè)對(duì)象,分享到朋友圈需要自定義三個(gè)內(nèi)容,展示圖標(biāo)imgUrl
,標(biāo)題title
和鏈接地址link
。
wx.onMenuShareTimeline({
title: '', // 分享標(biāo)題
link: '', // 分享鏈接,該鏈接域名或路徑必須與當(dāng)前頁(yè)面對(duì)應(yīng)的公眾號(hào)JS安全域名一致
imgUrl: '', // 分享圖標(biāo)絕對(duì)路徑
success: function () {
// 用戶(hù)確認(rèn)分享后執(zhí)行的回調(diào)函數(shù)
},
cancel: function () {
// 用戶(hù)取消分享后執(zhí)行的回調(diào)函數(shù)
}
});
- 發(fā)送給好友 - onMenuShareAppMessage()
?“發(fā)送給好友”需要自定義四個(gè)信息,標(biāo)題title
,描述desc
,圖標(biāo)絕對(duì)地址imgUrl
和跳轉(zhuǎn)的link
- 分享到QQ - onMenuShareQQ()
?自定義四個(gè)信息:標(biāo)題title
,描述desc
,跳轉(zhuǎn)鏈接link
,和圖標(biāo)絕對(duì)地址Url
- 分享到QQ空間 - onMenuShareQZone()
wx.onMenuShareQZone({
title: title,
desc: desc,
link: link,
imgUrl: imgUrl,
success: function () {
// 用戶(hù)確認(rèn)分享后執(zhí)行的回調(diào)函數(shù)
},
cancel: function () {
// 用戶(hù)取消分享后執(zhí)行的回調(diào)函數(shù)
}
});
?
JS-SDK使用權(quán)限簽名算法
?很高大上的名字,聽(tīng)著怪嚇人。其實(shí)只要幾個(gè)參數(shù)給對(duì)了,還是很容易處理的。需要注意的是,這個(gè)步驟需要在服務(wù)端實(shí)現(xiàn)
?上面“糞坑”提到的,雖然校驗(yàn)是失敗的,但步驟就是如此了。這里再概括一下:
登錄微信公眾號(hào) →
拿到appId和appSecret →
用他們?nèi)ノ⑿奴@取access_token →
用access_token去獲取jsapi_ticket →
生成一個(gè)隨機(jī)字符串noncestr →
生成當(dāng)前時(shí)間戳,單位是秒 →
用隨機(jī)jsapi_ticket,當(dāng)前時(shí)間戳,隨機(jī)字符串以及分享的url用&拼接成string1 →
對(duì)string1進(jìn)行sha1加密 →
加密后的字符串就是signature →
數(shù)據(jù)返回客戶(hù)端
?先在腦海形成一個(gè)思路。我們?cè)趤?lái)細(xì)看每一步:
1. 獲取access_token
?微信提供access_token
的有效期是兩個(gè)小時(shí),即7200秒,超時(shí)失效;且下一次獲取也會(huì)使上次獲取的結(jié)果失效。所以我們需要對(duì)它進(jìn)行動(dòng)態(tài)獲取。目前微信支持每個(gè)公眾號(hào)每天最多2000次的access_token
獲取請(qǐng)求。我設(shè)定7000秒去更新一次,這里用的nodejs實(shí)現(xiàn),其實(shí)思路都是一樣的。具體實(shí)現(xiàn)如下:
?這里先校驗(yàn)了本地文件
accesss_token.json
中的access_token
字段是否為空,為空表示是第一次獲取。再利用下面語(yǔ)句判斷本地的是否已經(jīng)過(guò)了我們所設(shè)定的有效期:
accessTokenJson.expire_time < currentTime
如果滿(mǎn)足條件,就重新發(fā)起requestGet請(qǐng)求重新獲取,否則從本地拿。
?為什么要這么做? 因?yàn)閷?duì)外的活動(dòng),如果用戶(hù)量比較大,每一個(gè)用戶(hù)都去獲取一次,微信限定的訪問(wèn)次數(shù)在2000個(gè)訪問(wèn)中就用完了,接下來(lái)當(dāng)天的訪問(wèn)將再也通不過(guò)簽名驗(yàn)證。
2. 生成 jsapi_ticket
?生成jsapi_ticket
的步驟和獲取access_token
的思路一致,具體如下所示:
?這里需要注意的是,不僅要判斷
ticket
是否為空,還要判斷token
是否失效。因?yàn)楫?dāng)token
提前失效后ticket
將變得無(wú)意義.?另外還要考慮這種情況,某次請(qǐng)求獲取到一個(gè)
token
后,我們并不能保證其他人員不會(huì)去微信公眾號(hào)或者web url
等方式去請(qǐng)求獲取token
,這將導(dǎo)致因token
被更新而上次的token
失效,但我們的程序并不知道,繼續(xù)使用“上一次請(qǐng)求”的token
,在下一個(gè)“重新請(qǐng)求”前將一直是失效的,不要漏了這種情況。
3. 生成隨機(jī)字符串
?傳給randomString
一個(gè)長(zhǎng)度參數(shù),生成所需要的隨機(jī)長(zhǎng)度字符串。我這里設(shè)定的是長(zhǎng)度為16
,代碼如下,簡(jiǎn)單不解釋。
4. 獲取JS-SDK簽名
?公式:signature=sha1(string1)
;即,需要生成string1
,再對(duì)string1
進(jìn)行sha1
加密得到signature
?微信文檔中提到,
參與簽名的字段包括noncestr(隨機(jī)字符串), 有效的jsapi_ticket, timestamp(時(shí)間戳), url(當(dāng)前網(wǎng)頁(yè)的URL,不包含#及其后面部分) 。對(duì)所有待簽名參數(shù)按照字段名的ASCII 碼從小到大排序(字典序)后,使用URL鍵值對(duì)的格式(即key1=value1&key2=value2…)拼接成字符串string1.這里需要注意的是所有參數(shù)名均為小寫(xiě)字符。對(duì)string1作sha1加密,字段名和字段值都采用原始值,不進(jìn)行URL 轉(zhuǎn)義。
?其實(shí)簡(jiǎn)單了來(lái)說(shuō),string1
就是jsapi_ticket=它的值&noncestr=隨機(jī)字符串×tamp=當(dāng)前時(shí)間戳(單位秒)&url=當(dāng)前網(wǎng)頁(yè)url
。微信文檔說(shuō)要按字段名的字典序排序,請(qǐng)注意,是按字段名的字典序,因?yàn)樽侄蚊蛔儯哉f(shuō)其實(shí)這個(gè)串中的各個(gè)參數(shù)順序是不變的。
【填坑】:
- 簽名用的
noncestr
和timestamp
必須與wx.config
中的nonceStr
和timestamp
相同。 - 簽名用的
url
必須是調(diào)用JS接口頁(yè)面的完整URL(請(qǐng)用location.href.split('#')[0]
)確認(rèn),包括'http(s)://'
部分,以及'?'
后面的GET
參數(shù)部分,但不包括'#'hash
后面的部分)
?能躺的坑基本已經(jīng)說(shuō)明,如有錯(cuò)誤或缺漏的地方,歡迎交流。