微信開放的JS-SDK面向網頁開發者提供了基於微信內的網頁開發工具包,最直接的好處就是咱們可使用微信分享、掃一掃、卡券、支付等微信特有的能力。7月份的時候,由於這個分享的證書獲取問題深深的栽了一坑,後面看到「config:ok」的時候真的算是石頭落地,瞬間感受世界很美好..前端
這篇文章是微信開發的不少前置條件,包括了服務端基於JAVA的獲取和緩存全局的access_token,獲取和緩存全局的jsapi_ticket,以及前端配置受權組件封裝,調用分享組件封裝。git
配置受權思路:首先根據access_token獲取jsapi_ticket,在經過獲取到的jsapi_ticket以及隨機生成的字符串、時間戳,再加上須要受權的頁面地址url,進行SHA-1加密,返回加密字符串,最後根據加密串調用微信提供的config接口。程序員
公衆平臺--公衆號設置--功能設置--js接口安全域名github
/** * 微信全局票據 ---->>>> 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) { //緩存中沒有、或已經失效 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()); //寫緩存 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」 ,若是命中直接返回值,反之經過httpclient發送GET請求調用微信提供的接口獲取全局的access_token,同時將取到的值寫入緩存。ajax
/** * jsapi_ticket是公衆號用於調用微信JS接口的臨時票據 * @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) { //緩存中沒有、或已經失效 //獲取全局的access_token,惟一票據 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()); //寫緩存 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,我這裏爲了保險起見緩存失效時間設置爲官方提供的時間再減去一個小時。redis
/** * 微信分享證書獲取 * @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; }
/** * 根據jsapi_ticket等參數進行SHA1加密 * @param nonceStr 隨機字符串 * @param timestamp 當前時間戳 * @param url 當前頁面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爲公衆號appid,經過spring@value註解從配置文件獲取,這裏不細說。
3.生成隨機字符串算法
private static String create_nonce_str() { return UUID.randomUUID().toString(); }
4.時間格式化spring
private static String create_timestamp() { return Long.toString(System.currentTimeMillis() / 1000); }
到此爲止後臺所有完成,其實沒有太多的解釋,仔細讀一遍代碼,可讀性應該還行!json
require.config({ urlArgs: "v=20161116" , baseUrl : "/static", paths: { jweixin: 'component/jweixin/jweixin-1.0.0', share: 'component/wechat/share'//微信分享組件 } })
首先經過調用後臺接口獲取加密字符串,調用微信提供的wx.config()方法api
//jsSDK受權 $.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); } } }); }
建議:開發環境建議開啓調式模式,方便打印日誌定位問題debug: true
全部接口調用都必須在config接口得到結果以後,config是一個客戶端的異步操做,我這裏用一個全局變量isSignature緩存了是否已經配置受權,而後執行回調。如實現分享接口:
//分享 $.share = function(opts,wx) { var options = { currentUrl: window.location.href.split('#')[0], imgUrl: null, title: '達農保險', desc: null, shareUrl: null } $.extend(true, options, opts || {}); //判斷是否已經受權 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 () { //分享統計,分享來源 1 朋友圈 2分享給朋友 3分享到QQ 4分享到QQ空間 } }); //分享給朋友 wx.onMenuShareAppMessage({ title: options.title, desc: options.desc, link: options.shareUrl, imgUrl: options.imgUrl }); }); } }
我先確認是否已經配置受權,若是沒有受權則調用$.signature()回調函數裏傳入$.share,有點相似遞歸調用,當再次回到share方法的時候isSignature已是true了,則執行wx.ready()方法,再調須要調用的接口,微信開放提供了不少接口給咱們,分享只是其中一個。只有想不到的,沒有實現不了的....
注意:currentUrl 必須是動態獲取的,經過window.location.href方法,由於頁面分享,微信客戶端會在你的連接末尾加入其它參數,因此須要再將url用‘#’割一下,取第一個,若是有中文最好是用encodeURIComponent轉義一下,保證簽名獲取成功。若是報invalid signature,大部分緣由是傳入的url,和加密算法的問題,仔細檢查!
調用:
var ua = navigator.userAgent.toLowerCase(), isWechat = ua.indexOf('micromessenger') != -1;//判斷是否爲微信瀏覽器
var shareData = {
title: ‘測試分享’,
desc: ‘這裏是描述,分享到朋友圈不會顯示’,
link: APP_ROOT + '/base/downloadApp,//分享後打開的連接,必須在配置的安全域名下
imgUrl: PIC_PATH + (self.data.shareListData[0].imgSmall || '/static/img/coupon/getTicPic.png'),//分享後顯示的圖片
success: function(){
setTimeout(function(){
//運營數據統計
},0)//僞異步方式調用 }
}
//微信瀏覽器分享加載
if(isWechat){
require(['jweixin'],function(wx){
require(['share'],function(){
$.share(shareData,wx);
})
})
}
完整js:https://github.com/helijun/component/blob/master/wechat/share.js
最開始作這個分享功能的時候,由於一個證書獲取失敗的緣由(invalid signature)真的是斷斷續續困了好幾天,有的時候真的是毫無頭緒了。反覆檢查代碼,逐字逐行的看,真的沒有發現任何異常,經過微信提供的一個js接口簽名校驗工具測試也是返回ture,然而就是報證書失敗!微信官方文檔又有點模棱兩可,到最後星期六的一個下午,靜下心來,再耐心的檢查了一遍後臺SHA1加密算法,終於看到config true.. 曙光
開發中咱們老是會遇到各類各樣的問題,程序員和bug永遠都是好朋友同時又是敵人,咱們老是徘徊在bug的邊緣,有時候當遇到很奇怪的問題的時候不妨先放一下,注意力先轉移一下,去陽臺吹吹風,說不定在某一個時刻,問題忽然就解開了..