在偶然间,需要做一个公众账号分享的功能,细一想~ 奈何宝宝不会呀,听都没听过肿么破,当时就心累了。
言归正传,由于我所有的开发代码均是在又各位前辈在度娘中留下的辛勤汗水,再由于自己本身就渣,属于能够通过查找资料的方式完成代码就谢天谢地那种类型,所以呢以下所有内容均属于从度娘中获取,只是把自己做过的,寻找过的资料做一个整理
大概
原理
首先下载官方的示例代码:http://demo.open.weixin.qq.com/jssdk/sample.zip
此代码包括:jweixin-1.js、demo.js、style.css、jssdk.html。以上代码略加修改即可以用,下面是具体的实现方法四步曲:
步骤一:绑定域名,先登录微信公众平台进入“公众号设置”的“功能设置”里填写“JS接口安全域名”;
步骤二:引入JS文件,在需要调用JS接口的页面引入JS文件,(支持https):http://res.wx.qq.com/open/js/jweixin-1.0.0.js;
步骤三:通过config接口注入权限验证配置,所有需要使用JS-SDK的页面必须先注入配置信息,否则将无法调用(同一个url仅需调用一次,对于变化url的SPA的web app可在每次url变化时进行调用);
步骤四:通过ready接口处理成功验证。
因此,这里最重要的一步是步骤三,获取config配置的签名signature ,如下:
wx.config({
debug: true, // 开启调试模式,调用的所有api的返回值会在客户端alert出来,若要查看传入的参数,可以在pc端打开,参数信息会通过log打出,仅在pc端时才会打印。 appId: '', // 必填,公众号的唯一标识 timestamp: , // 必填,生成签名的时间戳 nonceStr: '', // 必填,生成签名的随机串 signature: '',// 必填,签名,见附录1 jsApiList: [] // 必填,需要使用的JS接口列表,所有JS接口列表见附录2 });
实现
在开发之前肯定是需要几个从公众平台账号中拿到的开发者id,如不知道是什么,找领导要不给就干嘛~~ 好了,由于此处也是领导给的APPID编号这些,所以具体我也不清楚怎么来的。。。。 百度上应该可以找到吧~
(一)实体类 TokenJson
** * @author Allen * @version 1.0 * 创建时间:2016年4月12日 下午4:54:58 */ public class TokenJson { private String access_token; private int expires_in; public String getAccess_token() { return access_token; } public void setAccess_token(String access_token) { this.access_token = access_token; } public int getExpires_in() { return expires_in; } public void setExpires_in(int expires_in) { this.expires_in = expires_in; } }
实体类TicketJson
/** * @author Allen * @version 1.0 * 创建时间:2016年4月12日 下午5:03:14 */ public class TicketJson { private int errcode; private String errmsg; private String ticket; private String expires_in; public int getErrcode() { return errcode; } public void setErrcode(int errcode) { this.errcode = errcode; } public String getErrmsg() { return errmsg; } public void setErrmsg(String errmsg) { this.errmsg = errmsg; } public String getTicket() { return ticket; } public void setTicket(String ticket) { this.ticket = ticket; } public String getExpires_in() { return expires_in; } public void setExpires_in(String expires_in) { this.expires_in = expires_in; } }
HttpGetRequest用于连接验证服务器
import java.io.BufferedReader; import java.io.InputStream; import java.io.InputStreamReader; import java.net.HttpURLConnection; import java.net.URL; import java.net.URLConnection; /** * @author Allen * @version 1.0 创建时间:2016年4月12日 下午4:30:23 */ public class HttpGetRequest { /** * Get Request * * @return * @throws Exception */ public static String doGet(String url) throws Exception { URL localURL = new URL(url); URLConnection connection = localURL.openConnection(); HttpURLConnection httpURLConnection = (HttpURLConnection) connection; httpURLConnection.setRequestProperty("Accept-Charset", "utf-8"); httpURLConnection.setRequestProperty("Content-Type", "application/text"); InputStream inputStream = null; InputStreamReader inputStreamReader = null; BufferedReader reader = null; StringBuffer resultBuffer = new StringBuffer(); String tempLine = null; if (httpURLConnection.getResponseCode() >= 300) { throw new Exception( "HTTP Request is not success, Response code is " + httpURLConnection.getResponseCode()); } try { inputStream = httpURLConnection.getInputStream(); inputStreamReader = new InputStreamReader(inputStream); reader = new BufferedReader(inputStreamReader); while ((tempLine = reader.readLine()) != null) { resultBuffer.append(tempLine); } } finally { if (reader != null) { reader.close(); } if (inputStreamReader != null) { inputStreamReader.close(); } if (inputStream != null) { inputStream.close(); } } return resultBuffer.toString(); } }
如果有https SSL验证可用也可如下:
MyX509TrustManager 这个不用纠结是什么,它是用于验证SSL的一个自定义Class,此方法不可直接用,需要做一些相应更改,如HTTPS验证方式,SSL证书等等,自我调整应该即可。。。。 (自白:没有通过此方法实现HTTPS,而是使用了HttpGetRequest)/** * 发起https请求并获取结果 * @param requestUrl 请求地址 * @param requestMethod 请求方式(GET/POST) * @param outputStr 提交的数据 * @return JSONObject(通过JSONObject.get(key)的方式获取json对象的属性值) */ public static JSONObject httpRequest(String requestUrl, String requestMethod, String outputStr) { JSONObject jsonObject = null; StringBuffer buffer = new StringBuffer(); try { // 创建SSLContext对象,并使用我们指定的信任管理器初始化 TrustManager[] tm = { new MyX509TrustManager() }; SSLContext sslContext = SSLContext.getInstance("SSL", "SunJSSE"); sslContext.init(null, tm, new java.security.SecureRandom()); // 从上述SSLContext对象中得到SSLSocketFactory对象 SSLSocketFactory ssf = sslContext.getSocketFactory(); URL url = new URL(requestUrl); HttpsURLConnection httpUrlConn = (HttpsURLConnection) url.openConnection(); httpUrlConn.setSSLSocketFactory(ssf); httpUrlConn.setDoOutput(true); httpUrlConn.setDoInput(true); httpUrlConn.setUseCaches(false); // 设置请求方式(GET/POST) httpUrlConn.setRequestMethod(requestMethod); if ("GET".equalsIgnoreCase(requestMethod)) httpUrlConn.connect(); // 当有数据需要提交时 if (null != outputStr) { OutputStream outputStream = httpUrlConn.getOutputStream(); // 注意编码格式,防止中文乱码 outputStream.write(outputStr.getBytes("UTF-8")); outputStream.close(); } // 将返回的输入流转换成字符串 InputStream inputStream = httpUrlConn.getInputStream(); InputStreamReader inputStreamReader = new InputStreamReader(inputStream, "utf-8"); BufferedReader bufferedReader = new BufferedReader(inputStreamReader); String str = null; while ((str = bufferedReader.readLine()) != null) { buffer.append(str); } bufferedReader.close(); inputStreamReader.close(); // 释放资源 inputStream.close(); inputStream = null; httpUrlConn.disconnect(); jsonObject = JSONObject.fromObject(buffer.toString()); } catch (ConnectException ce) { Logger.getAnonymousLogger().info("Weixin server connection timed out."); } catch (Exception e) { Logger.getAnonymousLogger().info("信任管理器请求时..."+e); } return jsonObject; }
WxParams 实体类 用于记录从weixin官网得到的token与ticket
/** * @author Allen * @version 1.0 * 创建时间:2016年4月13日 下午3:53:57 */ public class WxParams { public static String token; public static String tokenTime; public static String tokenExpires; public static String ticket; public static String ticketTime; public static String ticketExpires; }
Sign 用于整理整合得到的值 (虽然我不懂,哈哈哈~~)
import java.io.UnsupportedEncodingException; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; import java.util.Formatter; import java.util.HashMap; import java.util.Map; import java.util.UUID; public class Sign { public static Map<String, String> sign(String jsapi_ticket, String url) { Map<String, String> ret = new HashMap<String, String>(); String nonce_str = create_nonce_str(); String timestamp = create_timestamp(); String string1; String signature = ""; //注意这里参数名必须全部小写,且必须有序 string1 = "jsapi_ticket=" + jsapi_ticket + "&noncestr=" + nonce_str + "×tamp=" + timestamp + "&url=" + url; // System.out.println(string1); try { MessageDigest crypt = MessageDigest.getInstance("SHA-1"); crypt.reset(); crypt.update(string1.getBytes("UTF-8")); signature = byteToHex(crypt.digest()); } catch (NoSuchAlgorithmException e) { e.printStackTrace(); } catch (UnsupportedEncodingException e) { e.printStackTrace(); } ret.put("url", url); ret.put("jsapi_ticket", jsapi_ticket); ret.put("nonceStr", nonce_str); ret.put("timestamp", timestamp); ret.put("signature", signature); return ret; } 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; } private static String create_nonce_str() { return UUID.randomUUID().toString(); } private static String create_timestamp() { return Long.toString(System.currentTimeMillis() / 1000); } }
WxUtil 工具类 js中的入口
import java.util.Map; import net.sf.json.JSONObject; import wrt.book.Json.TicketJson; import wrt.book.Json.TokenJson; /** * @author Allen * @version 1.0 * 创建时间:2016年4月12日 下午4:28:41 */ public class WxUtil { //此处的appid与wx.config 参数appId一致 微信公众账号提供给开发者的信息,以下同理 public static String APPID = "wxb5571f0bxxxxxxxxx"; //同上 public static String SECRET = "c73435d332dxxxxxxxxxxx"; private static TokenJson getAccess_token(){ String url = String.format("https://api.weixin.qq.com/xxxxxxxxxxxxxxxxxxxx",APPID,SECRET); try { String result = HttpGetRequest.doGet(url); System.out.println("微信服务器获取token:"+result); JSONObject rqJsonObject = JSONObject.fromObject(result); TokenJson tokenJson = (TokenJson) JSONObject.toBean(rqJsonObject,TokenJson.class); return tokenJson; } catch (Exception e) { // TODO Auto-generated catch block e.printStackTrace(); return null; } } private static TicketJson getTicket(String token){ String url = String.format("http://api.weixin.qq.com/xxxxxxxxxxxxxxxxxxxxxxxx",token); try { String result = HttpGetRequest.doGet(url); System.out.println("微信服务器获取Ticket:"+result); JSONObject rqJsonObject = JSONObject.fromObject(result); TicketJson ticket = (TicketJson) JSONObject.toBean(rqJsonObject,TicketJson.class); return ticket; } catch (Exception e) { // TODO Auto-generated catch block e.printStackTrace(); return null; } } /** * 获取js sdk 认证信息 * @author * @date 创建时间 2016年7月28日 上午11:25:01 * @param url * @return */ public static Map<String, String> getSign(String url){ //处理token失效的问题 try { long tokenTimeLong = Long.parseLong(WxParams.tokenTime); long tokenExpiresLong = Long.parseLong(WxParams.tokenExpires); //时间差 long differ = (System.currentTimeMillis() - tokenTimeLong) /1000; if (WxParams.token == null || differ > (tokenExpiresLong - 1800)) { System.out.println("token为null,或者超时,重新获取"); TokenJson tokenJson = getAccess_token(); if (tokenJson != null) { WxParams.token = tokenJson.getAccess_token(); WxParams.tokenTime = System.currentTimeMillis()+""; WxParams.tokenExpires = tokenJson.getExpires_in()+""; } } } catch (Exception e) { // TODO: handle exception e.printStackTrace(); TokenJson tokenJson = getAccess_token(); if (tokenJson != null) { WxParams.token = tokenJson.getAccess_token(); WxParams.tokenTime = System.currentTimeMillis()+""; WxParams.tokenExpires = tokenJson.getExpires_in()+""; } } //处理ticket失效的问题 try { long ticketTimeLong = Long.parseLong(WxParams.ticketTime); long ticketExpiresLong = Long.parseLong(WxParams.ticketExpires); //时间差 long differ = (System.currentTimeMillis() - ticketTimeLong) /1000; if (WxParams.ticket == null || differ > (ticketExpiresLong - 1800)) { System.out.println("ticket为null,或者超时,重新获取"); TicketJson ticketJson = getTicket(WxParams.token); if (ticketJson != null) { WxParams.ticket = ticketJson.getTicket(); WxParams.ticketTime = System.currentTimeMillis()+""; WxParams.ticketExpires = ticketJson.getExpires_in()+""; } } } catch (Exception e) { // TODO: handle exception e.printStackTrace(); TicketJson ticketJson = getTicket(WxParams.token); if (ticketJson != null) { WxParams.ticket = ticketJson.getTicket(); WxParams.ticketTime = System.currentTimeMillis()+""; WxParams.ticketExpires = ticketJson.getExpires_in()+""; } } Map<String, String> ret = Sign.sign(WxParams.ticket, url); System.out.println("计算出的签名-----------------------"); for (Map.Entry entry : ret.entrySet()) { System.out.println(entry.getKey() + ", " + entry.getValue()); } System.out.println("-----------------------"); return ret; } }
整个后台代码,全献上了,复制粘贴更改关键部分即可使用,不管你们能不能用,反正我是可以用~ 2333333333。
index.jsp 通过<%%> 嵌入
<% //签名 String url = request.getScheme()+"://"; url+=request.getHeader("host"); url+=request.getRequestURI(); if(request.getQueryString()!=null){ url+="?"+request.getQueryString(); } Map<String,String> sign = WxUtil.getSign(url); String timestamp = sign.get("timestamp"); String nonceStr = sign.get("nonceStr"); String jsapi_ticket = sign.get("jsapi_ticket"); String signature = sign.get("signature"); //String url = sign.get("url"); %>
拿到后台反馈的信息后,使用wx.config({}) ;进行初始化操作如下:
<script type="text/javascript" src="jweixin-1.0.0.js"></script> <script type="text/javascript"> wx.config({ debug: false, appId: <%="'"+WxUtil.APPID+"'"%>, timestamp: <%="'"+timestamp+"'"%>, nonceStr: <%="'"+nonceStr+"'"%>, signature: <%="'"+signature+"'"%>, jsApiList: ['onMenuShareTimeline', 'onMenuShareAppMessage', 'onMenuShareQQ', 'onMenuShareWeibo', 'onMenuShareQZone'] // 功能列表,我们要使用JS-SDK的什么功能 }); // config信息验证后会执行ready方法,所有接口调用都必须在config接口获得结果之后,config是一个客户端的异步操作,所以如果需要在 页面加载时就调用相关接口,则须把相关接口放在ready函数中调用来确保正确执行。对于用户触发时才调用的接口,则可以直接调用,不需要放在ready 函数中。 wx.ready(function(){ // 获取"分享到朋友圈"按钮点击状态及自定义分享内容接口 wx.onMenuShareTimeline({ title: '慧锐通电子书架', // 分享标题 link: 'http://www.wrtrd.net/book/', imgUrl: 'http://www.wrtrd.net/book/images/wxbook.jpg' // 分享图标 }); // 获取"分享给朋友"按钮点击状态及自定义分享内容接口 wx.onMenuShareAppMessage({ title: '慧锐通电子书架', // 分享标题 desc: '慧锐通产品介绍的电子画册,含数字对讲、模拟对讲、云对讲、智能互联、蓝牙门禁等系统!', // 分享描述 link: 'http://www.wrtrd.net/book/', imgUrl: 'http://www.wrtrd.net/book/images/wxbook.jpg', // 分享图标 type: 'link' // 分享类型,music、video或link,不填默认为link }); //获取"分享到QQ"按钮点击状态及自定义分享内容接口 wx.onMenuShareQQ({ title: '慧锐通电子书架', // 分享标题 desc: '慧锐通产品介绍的电子画册,含数字对讲、模拟对讲、云对讲、智能互联、蓝牙门禁等系统!', // 分享描述 link: 'http://www.wrtrd.net/book/', // 分享链接 imgUrl: 'http://www.wrtrd.net/book/images/wxbook.jpg' // 分享图标 }); //获取"分享到腾讯微博"按钮点击状态及自定义分享内容接口 wx.onMenuShareWeibo({ title: '慧锐通电子书架', // 分享标题 desc: '慧锐通产品介绍的电子画册,含数字对讲、模拟对讲、云对讲、智能互联、蓝牙门禁等系统!', // 分享描述 link: 'http://www.wrtrd.net/book/', // 分享链接 imgUrl: 'http://www.wrtrd.net/book/images/wxbook.jpg' // 分享图标 }); //获取"分享到QQ空间"按钮点击状态及自定义分享内容接口 wx.onMenuShareQZone({ title: '慧锐通电子书架', // 分享标题 desc: '慧锐通产品介绍的电子画册,含数字对讲、模拟对讲、云对讲、智能互联、蓝牙门禁等系统!', // 分享描述 link: 'http://www.wrtrd.net/book/', // 分享链接 imgUrl: 'http://www.wrtrd.net/book/images/wxbook.jpg' // 分享图标 }); }); </script>
以上是全部代码,基本稍加更改则可直接使用。
其中,wx.config({})是整体的关键,在页面初始化时会自动的验证后台信息,验证成功会自动调用wx.ready(function(){ }
验证失败调用wx.ready(function(){ // config信息验证后会执行ready方法,所有接口调用都必须在config接口获得结果之后,config是一个客户端的异步操作,所以如果需要在页面加载时就调用相关接口,则须把相关接口放在ready函数中调用来确保正确执行。对于用户触发时才调用的接口,则可以直接调用,不需要放在ready函数中。 });
wx.error(function(res){ // config信息验证失败会执行error函数,如签名过期导致验证失败,具体错误信息可以打开config的debug模式查看,也可以在返回的res参数中查看,对于SPA可以在这里更新签名。 });
使用注意事项看这里:看这里看这里 。 补充两点,如果以上代码都编辑后,测试时发现 wx.config验证通过,基本上按照JS-SDK开发文档来就足够了。同理,如果wx.config验证不通过,请直接百度可能出现的原因,有可能是提供的开发者账号等错误,并有一个隐藏的错误,传入后台的URL与当前页面URL不匹配,此时也不会通过 ~~~ T~T 被这个原因坑了一整天,。
同时,测试时,必须部署到微信公众平台账号中,因为它会检测当前传入的HTTP地址是否与备案的相同,如若不同则会一直报异常,切记切记。。。
无奈本屌只会用,不懂其原理,惭愧惭愧,不过能解决的就是好的不是嘛~ 最后附上相关资料信息
微信开发:JS-SDK之分享接口的实现
微信开发(六)微信分享接入
wx.onMenuShareTimeline使用注意事项
感谢那些为此默默付出的大神们~~ 咱们谢谢他们哒,同时新手勿喷勿喷诺~