解析微信JS-SDK配置授权,实现分享接口
微信开放的JS-SDK面向网页开发者提供了基于微信内的网页开发工具包,最直接的好处就是我们可以使用微信分享、扫一扫、卡券、支付等微信特有的能力。7月份的时候,因为这个分享的证书获取问题深深的栽了一坑,后面看到“config:ok”的时候真的算是石头落地,瞬间感觉世界很美好..
这篇文章是微信开发的很多前置条件,包括了服务端基于JAVA的获取和缓存全局的access_token,获取和缓存全局的jsapi_ticket,以及前端配置授权组件封装,调用分享组件封装。
配置授权思路:首先根据access_token获取jsapi_ticket,在通过获取到的jsapi_ticket以及随机生成的字符串、时间戳,再加上需要授权的页面地址url,进行SHA-1加密,返回加密字符串,最后根据加密串调用微信提供的config接口。
配置JS接口安全域名
公众平台--公众号设置--功能设置--js接口安全域名
获取、缓存全局的access_token
/** * 微信全局票据 ---->>>> 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,同时将取到的值写入缓存。
获取、缓存全局的jsapi_ticket
/** * 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,我这里为了保险起见缓存失效时间设置为官方提供的时间再减去一个小时。
jssdk加密串获取restful
1.Controller
/** * 微信分享证书获取 * @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
/** * 根据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.时间格式化
private static String create_timestamp() { return Long.toString(System.currentTimeMillis() / 1000); }
到此为止后台全部完成,其实没有太多的解释,仔细读一遍代码,可读性应该还行!
封装获取授权组件,实现分享方法
require.config({ urlArgs: "v=20161116" , baseUrl : "/static", paths: { jweixin: 'component/jweixin/jweixin-1.0.0', share: 'component/wechat/share'//微信分享组件 } })
首先通过调用后台接口获取加密字符串,调用微信提供的wx.config()方法
//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的边缘,有时候当遇到很奇怪的问题的时候不妨先放一下,注意力先转移一下,去阳台吹吹风,说不定在某一个时刻,问题突然就解开了..
接口签名校验工具