解析微信JS-SDK配置授權(quán),實(shí)現(xiàn)分享接口
微信開(kāi)放的JS-SDK面向網(wǎng)頁(yè)開(kāi)發(fā)者提供了基于微信內(nèi)的網(wǎng)頁(yè)開(kāi)發(fā)工具包,最直接的好處就是我們可以使用微信分享、掃一掃、卡券、支付等微信特有的能力。7月份的時(shí)候,因?yàn)檫@個(gè)分享的證書(shū)獲取問(wèn)題深深的栽了一坑,后面看到“config:ok”的時(shí)候真的算是石頭落地,瞬間感覺(jué)世界很美好..
這篇文章是微信開(kāi)發(fā)的很多前置條件,包括了服務(wù)端基于JAVA的獲取和緩存全局的access_token,獲取和緩存全局的jsapi_ticket,以及前端配置授權(quán)組件封裝,調(diào)用分享組件封裝。
配置授權(quán)思路:首先根據(jù)access_token獲取jsapi_ticket,在通過(guò)獲取到的jsapi_ticket以及隨機(jī)生成的字符串、時(shí)間戳,再加上需要授權(quán)的頁(yè)面地址url,進(jìn)行SHA-1加密,返回加密字符串,最后根據(jù)加密串調(diào)用微信提供的config接口。
配置JS接口安全域名
公眾平臺(tái)--公眾號(hào)設(shè)置--功能設(shè)置--js接口安全域名

獲取、緩存全局的access_token
/**
* 微信全局票據(jù) ---->>>> access_token
* @return
* @throws ClientProtocolException
* @throws IOException
*/
public String getBaseAccessToken() throws ClientProtocolException, IOException{
try {
String value = redisService.get("WEIXIN_BASE_ACCESS_TOKEN");
if (!StringUtils.isEmpty(value)) {
LOGGER.info("Get base access_token from redis is successful.value:{}",value);
return value;
}else{
synchronized (this) {
//緩存中沒(méi)有、或已經(jīng)失效
String url = "https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid="+WX_APPID+"&secret="+ WX_APPSECRET;
String rs = apiService.doGet(url);
JSONObject obj_content = JSONObject.parseObject(rs);
String accessToken = obj_content.getString("access_token");
Integer time = Integer.parseInt(obj_content.getString("expires_in").toString());
//寫(xiě)緩存
redisService.set("WEIXIN_BASE_ACCESS_TOKEN", accessToken, time - 3600);
LOGGER.info("Set base access_token to redis is successful.parameters time:{},realtime",time,time-3600);
return accessToken;
}
}
} catch (Exception e) {
LOGGER.error("Get base access_token from redis is error.");
}
return null;
}
先從緩存中取key為“WX_BASE_ACCESS_TOKEN” ,如果命中直接返回值,反之通過(guò)httpclient發(fā)送GET請(qǐng)求調(diào)用微信提供的接口獲取全局的access_token,同時(shí)將取到的值寫(xiě)入緩存。
獲取、緩存全局的jsapi_ticket
/**
* jsapi_ticket是公眾號(hào)用于調(diào)用微信JS接口的臨時(shí)票據(jù)
* @return
* @throws IOException
* @throws ClientProtocolException
*/
public String getJsapiTicket() throws ClientProtocolException, IOException{
try {
String value = redisService.get("WEIXIN_JS_API_TICKET");
if (!StringUtils.isEmpty(value)) {
return value;
}else{
synchronized (this) {
//緩存中沒(méi)有、或已經(jīng)失效
//獲取全局的access_token,唯一票據(jù)
String accessToken = getBaseAccessToken();
String url = "https://api.weixin.qq.com/cgi-bin/ticket/getticket?access_token="+ accessToken +"&type=jsapi";
String rs = apiService.doGet(url);
JSONObject obj_content = JSONObject.parseObject(rs);
String jsapi_ticket = obj_content.getString("ticket");
Integer time = Integer.parseInt(obj_content.getString("expires_in").toString());
//寫(xiě)緩存
redisService.set("WEIXIN_JS_API_TICKET", jsapi_ticket, time - 3600);
return jsapi_ticket;
}
}
} catch (Exception e) {
LOGGER.error("Get js_api_ticket from redis is error:{}",e);
}
return null;
}
由于獲取jsapi_ticket微信有100000次限制,所以必須用上緩存。同理獲取access_token,我這里為了保險(xiǎn)起見(jiàn)緩存失效時(shí)間設(shè)置為官方提供的時(shí)間再減去一個(gè)小時(shí)。
jssdk加密串獲取restful
1.Controller
/**
* 微信分享證書(shū)獲取
* @param
* @return signature
* @throws IOException
*/
@RequestMapping(value = "/signature", method = RequestMethod.GET)
public @ResponseBody String createSignature(
@RequestParam String url) throws IOException{
LOGGER.info("RestFul of createSignature parameters url:{}",url);
try {
String rs = wechatService.createSignature(url);
LOGGER.info("RestFul of signature is successful.",rs);
return rs;
} catch (Exception e) {
LOGGER.error("RestFul of signature is error.",e);
}
return null;
}
2.Service
/**
* 根據(jù)jsapi_ticket等參數(shù)進(jìn)行SHA1加密
* @param nonceStr 隨機(jī)字符串
* @param timestamp 當(dāng)前時(shí)間戳
* @param url 當(dāng)前頁(yè)面url
*/
public String createSignature(String url) throws ClientProtocolException, IOException{
String nonceStr = create_nonce_str();
String timestamp = create_timestamp();
String signature = "jsapi_ticket="+getJsapiTicket();
signature += "&noncestr="+nonceStr;
signature += "×tamp="+timestamp;
signature += "&url="+url;
try {
MessageDigest crypt = MessageDigest.getInstance("SHA-1");
crypt.reset();
crypt.update(signature.getBytes("UTF-8"));
signature = byteToHex(crypt.digest());
} catch (Exception e) {
LOGGER.error("Signature for SHA-1 is error:{}",e);
}
Map<String, String> map = new HashMap<String, String>();
map.put("timestamp", timestamp);
map.put("nonceStr", nonceStr);
map.put("signature", signature);
map.put("appid", WX_APPID);
return JSON.toJSONString(map, true);
}
private static String byteToHex(final byte[] hash) {
Formatter formatter = new Formatter();
for (byte b : hash) {
formatter.format("%02x", b);
}
String result = formatter.toString();
formatter.close();
return result;
}
WX_APPID為公眾號(hào)appid,通過(guò)spring@value注解從配置文件獲取,這里不細(xì)說(shuō)。
3.生成隨機(jī)字符串
private static String create_nonce_str() {
return UUID.randomUUID().toString();
}
4.時(shí)間格式化
private static String create_timestamp() {
return Long.toString(System.currentTimeMillis() / 1000);
}
到此為止后臺(tái)全部完成,其實(shí)沒(méi)有太多的解釋?zhuān)屑?xì)讀一遍代碼,可讀性應(yīng)該還行!
封裝獲取授權(quán)組件,實(shí)現(xiàn)分享方法
require.config({
urlArgs: "v=20161116" ,
baseUrl : "/static",
paths: {
jweixin: 'component/jweixin/jweixin-1.0.0',
share: 'component/wechat/share'//微信分享組件
}
})
首先通過(guò)調(diào)用后臺(tái)接口獲取加密字符串,調(diào)用微信提供的wx.config()方法
//jsSDK授權(quán)
$.signature = function(wx,opts,currentUrl,callback){
$.ajax({
data: {url: currentUrl},
type: "GET",
url: WX_ROOT + "wechat/signature",
success: function (json) {
if (json) {
var data = JSON.parse(json);
wx.config({
debug: false,
appId: data.appid,
timestamp: data.timestamp,
nonceStr: data.nonceStr,
signature: data.signature,
jsApiList: [
'onMenuShareTimeline',
'onMenuShareAppMessage',
'onMenuShareQQ',
'onMenuShareWeibo',
'onMenuShareQZone'
]
});
wechatShare.options.isSignature = true;
callback && callback(opts,wx);
}
}
});
}
建議:開(kāi)發(fā)環(huán)境建議開(kāi)啟調(diào)式模式,方便打印日志定位問(wèn)題debug: true
所有接口調(diào)用都必須在config接口獲得結(jié)果之后,config是一個(gè)客戶端的異步操作,我這里用一個(gè)全局變量isSignature緩存了是否已經(jīng)配置授權(quán),然后執(zhí)行回調(diào)。如實(shí)現(xiàn)分享接口:
//分享
$.share = function(opts,wx) {
var options = {
currentUrl: window.location.href.split('#')[0],
imgUrl: null,
title: '達(dá)農(nóng)保險(xiǎn)',
desc: null,
shareUrl: null
}
$.extend(true, options, opts || {});
//判斷是否已經(jīng)授權(quán)
if(!wechatShare.options.isSignature){
$.signature(wx,opts,options.currentUrl,$.share)
}else{
wx.ready(function(){
//分享到朋友圈
wx.onMenuShareTimeline({
title: options.title,
link: options.shareUrl,
imgUrl: options.imgUrl,
success: function () {
//分享統(tǒng)計(jì),分享來(lái)源 1 朋友圈 2分享給朋友 3分享到QQ 4分享到QQ空間
}
});
//分享給朋友
wx.onMenuShareAppMessage({
title: options.title,
desc: options.desc,
link: options.shareUrl,
imgUrl: options.imgUrl
});
});
}
}
我先確認(rèn)是否已經(jīng)配置授權(quán),如果沒(méi)有授權(quán)則調(diào)用$.signature()回調(diào)函數(shù)里傳入$.share,有點(diǎn)類(lèi)似遞歸調(diào)用,當(dāng)再次回到share方法的時(shí)候isSignature已經(jīng)是true了,則執(zhí)行wx.ready()方法,再調(diào)需要調(diào)用的接口,微信開(kāi)放提供了很多接口給我們,分享只是其中一個(gè)。只有想不到的,沒(méi)有實(shí)現(xiàn)不了的....
注意:currentUrl 必須是動(dòng)態(tài)獲取的,通過(guò)window.location.href方法,因?yàn)轫?yè)面分享,微信客戶端會(huì)在你的鏈接末尾加入其它參數(shù),所以需要再將url用‘#'割一下,取第一個(gè),如果有中文最好是用encodeURIComponent轉(zhuǎn)義一下,保證簽名獲取成功。如果報(bào)invalid signature,大部分原因是傳入的url,和加密算法的問(wèn)題,仔細(xì)檢查!
調(diào)用:
var ua = navigator.userAgent.toLowerCase(),
isWechat = ua.indexOf('micromessenger') != -1;//判斷是否為微信瀏覽器
var shareData = {
title: ‘測(cè)試分享',
desc: ‘這里是描述,分享到朋友圈不會(huì)顯示',
link: APP_ROOT + '/base/downloadApp,//分享后打開(kāi)的鏈接,必須在配置的安全域名下
imgUrl: PIC_PATH + (self.data.shareListData[0].imgSmall || '/static/img/coupon/getTicPic.png'),//分享后顯示的圖片
success: function(){
setTimeout(function(){
//運(yùn)營(yíng)數(shù)據(jù)統(tǒng)計(jì)
},0)//偽異步方式調(diào)用
}
}
//微信瀏覽器分享加載
if(isWechat){
require(['jweixin'],function(wx){
require(['share'],function(){
$.share(shareData,wx);
})
})
}
完整js:https://github.com/helijun/component/blob/master/wechat/share.js
常用問(wèn)題總結(jié):
最開(kāi)始做這個(gè)分享功能的時(shí)候,因?yàn)橐粋€(gè)證書(shū)獲取失敗的原因(invalid signature)真的是斷斷續(xù)續(xù)困了好幾天,有的時(shí)候真的是毫無(wú)頭緒了。反復(fù)檢查代碼,逐字逐行的看,真的沒(méi)有發(fā)現(xiàn)任何異常,通過(guò)微信提供的一個(gè)js接口簽名校驗(yàn)工具測(cè)試也是返回ture,然而就是報(bào)證書(shū)失??!微信官方文檔又有點(diǎn)模棱兩可,到最后星期六的一個(gè)下午,靜下心來(lái),再耐心的檢查了一遍后臺(tái)SHA1加密算法,終于看到config true.. 曙光
開(kāi)發(fā)中我們總是會(huì)遇到各種各樣的問(wèn)題,程序員和bug永遠(yuǎn)都是好朋友同時(shí)又是敵人,我們總是徘徊在bug的邊緣,有時(shí)候當(dāng)遇到很奇怪的問(wèn)題的時(shí)候不妨先放一下,注意力先轉(zhuǎn)移一下,去陽(yáng)臺(tái)吹吹風(fēng),說(shuō)不定在某一個(gè)時(shí)刻,問(wèn)題突然就解開(kāi)了..
接口簽名校驗(yàn)工具
https://mp.weixin.qq.com/debug/cgi-bin/sandbox?t=jsapisign
以上就是本文的全部?jī)?nèi)容,希望本文的內(nèi)容對(duì)大家的學(xué)習(xí)或者工作能帶來(lái)一定的幫助,同時(shí)也希望多多支持腳本之家!
相關(guān)文章
再談IE中Flash控件的自動(dòng)激活 ObjectWrap
再談IE中Flash控件的自動(dòng)激活 ObjectWrap...2007-03-03
javascript實(shí)現(xiàn)數(shù)組扁平化六種技巧總結(jié)
這篇文章主要為大家詳細(xì)介紹了六種javascript中實(shí)現(xiàn)數(shù)組扁平化的技巧,文中的示例代碼講解詳細(xì),具有一定的借鑒價(jià)值,感興趣的小伙伴可以了解下2023-12-12
js實(shí)現(xiàn)從右往左勻速顯示圖片(無(wú)縫輪播)
這篇文章主要為大家詳細(xì)介紹了js實(shí)現(xiàn)從右往左勻速顯示圖片,無(wú)縫輪播,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2020-06-06
JS原型prototype和__proto__用法實(shí)例分析
這篇文章主要介紹了JS原型prototype和__proto__用法,結(jié)合實(shí)例形式分析了JS原型prototype和__proto__使用方法及相關(guān)操作注意事項(xiàng),需要的朋友可以參考下2020-03-03
JS字符串分割方法整理匯總示例講解(3種截取方法和6個(gè)輔助方法)
JavaScript在開(kāi)發(fā)中常常會(huì)需要截取字符串,而JS提供了slice()?、substring()、substr()?3種方法實(shí)現(xiàn)截取操作。另外還有字符串相關(guān)的6種輔助方法:indexOf()、lastIndexOf()、split()、join()、concat()、charAt()?。2023-02-02
js完美解決IE6不支持position:fixed的bug
關(guān)于IE6,雖然它已被微軟拋棄很久了,但是由于大天朝的特殊行情(盜版)對(duì)于前端工程師來(lái)說(shuō),解決IE6兼容position:fixed的問(wèn)題顯得很重要。特別是你需要用到頭尾懸停調(diào)用的時(shí)候2015-04-04
JavaScript實(shí)現(xiàn)body內(nèi)任意節(jié)點(diǎn)的自定義屬性功能示例
這篇文章主要介紹了JavaScript實(shí)現(xiàn)body內(nèi)任意節(jié)點(diǎn)的自定義屬性功能,涉及javascript針對(duì)DOM節(jié)點(diǎn)的獲取及屬性設(shè)置相關(guān)操作技巧,需要的朋友可以參考下2017-09-09

