前言
网页实现微信分享功能,这个其实在百度上是有很多例子的,而且写得也都还不错。不过我这个跟他们的不大一样。一般的博客会将分享需要的微信凭证这些写进一个项目中,本项目获取,本项目实现分享功能。而我是获取微信凭证是单独的一个项目,这样一个服务号的获取的微信凭证,可以提供给很多个项目使用,拓展性还是可以的,有需要的朋友可以参考下。
方案的选择
其实我刚开始做的时候想到两个方案:
1、将获取的微信凭证放进static静态变量中。具体做法是:web项目启动,然后启动监听器,利用初始化方法启动一个线程,这个线程就是定时发请求去获取微信凭证,然后保存到public static静态变量中,如果有需要的话直接去取。这个是我很早之前做发送微信模板消息考虑的做法,但是我使用main方法本次测试的时候,好像是取不到static的值,当时百度上很多人都说可以用这种方式,但是我测试的时候是取不到static 里面的值,当然感兴趣的朋友可以试一下,也许你会有不一样的发现。
2、方案二如前言所示。
项目总体实现思路
其实要实现一个服务号获取的凭证给多个应用或者项目共享,很简单。就是把获取微信凭证的这个项目独立出来,然后使用全局缓存的方式,每次其它项目需要的时候就发请求,参数中携带有时间戳,然后与缓存中的时间戳相比较,如果时间差超过两小时,那么重新获取微信凭证并保留在缓存中,如果时间差并未超过两小时,那么将缓存中保留的凭证返回。也可以这么理解,其它项目发请求过来获取微信凭证的时候,每次都是从缓存中取出微信凭证,至于要不要重新获取比较时间戳即可。
具体实现流程
1、 要使用微信的东西,首先当然是查看微信官方文档进行了解,对整个流程有个清晰的认识。
开发前的准备请看下图。
设置的时候请注意上面的文字
对于上面必须文件的放置,可以放到你填写的域名指向的tomcat下的/webapps/ROOT这个里面,访问域名+文件名比如说:wx.qq.com/MP_verify_TefDJNVX7f91MSYR.txt如果可以成功访问,那么恭喜你设置成功。
2、考虑到在开发的过程中需要调试,所以建议大家使用微信web开发者工具进行调试,这样效果比手机好得多。
下载工具之后,到公众平台——开发——开发者工具——web开发者工具 授权给自己的微信号,然后就可以使用微信web开发者工具进行相关的开发调试了。
3、需要获取的微信凭证
我当时记得官方有个具体的链接解释的,就是调用什么接口,返回参数和注意事项,但是我一时半会找不到了,所以我在这里罗列下。
首先是accessToken,具体可以查看获取access_token
然后使用accessToken获取api_ticket,在微信JS-SDK说明文档 微信卡券下的获取api_ticket这部分可以看到对应的信息。
获取这两个凭证的时候,我们注意到这两个有时间和次数限制,所以我的建议是存到全局缓存ServletContext对象中:
ServletContext sc = request.getSession().getServletContext();
sc.setAttribute("accessToken", accessToken);
sc.setAttribute("jsapiTicket", jsapiTicket);
然后通过这样来取值就可以实现微信凭证共享的目的了。
accessToken = (String) sc.getAttribute(“accessToken”);
jsapiTicket = (String) sc.getAttribute(“jsapiTicket”);
4、请求的简单验证
作为一个网络接口,安全是一个很重要的考虑。所以我这里通过对参数进行校验来决定是否返回微信凭证。
String timestampString = Tools.GetString(request.getParameter("timestamp"), "");// 请求的时间戳
String nonceStr = Tools.GetString(request.getParameter("nonceStr"), ""); //uuid生成的随机数,概率避免被恶意请求资源
String auth = Tools.GetString(request.getParameter("auth"), "");// 密钥,避免恶意请求
这个部分比较简单,其中auth是对参数和密钥进行md5加密的字符串,获取字符串之后,将参数加密对比就行,无需解密对比(哈哈)。用springMVC就可以实现了,具体可以查看我的代码。下面来介绍重点部分。
5、后面这部分由于是我应用的一个小模块,所以我把这部分代码贴出来,大家可以自己整理下。
首先是访问页面,然后发请求获取凭证
//在访问视图的时候,同时发请求,获取和制作需要的信息
//controller
@RequestMapping(value = "/remindpage", method = { RequestMethod.GET, RequestMethod.POST })
public ModelAndView login(@PathVariable("channel") String channel, @PathVariable("activity") String activity,HttpServletRequest request) throws Exception {
ModelAndView mav = new ModelAndView(activity+"/remindpage");
Map<String, Object> shareMap = WechatUtil.getWxConfig(activity,request);
mav.addObject("appId", shareMap.get("appId"));
mav.addObject("timestamp", shareMap.get("timestamp"));
mav.addObject("nonceStr", shareMap.get("nonceStr"));
mav.addObject("signature", shareMap.get("signature"));
mav.addObject("requestUrl", shareMap.get("requestUrl"));
mav.addObject("wechatShareTitle", shareMap.get("wechatShareTitle"));
mav.addObject("wechatShareDesc", shareMap.get("wechatShareDesc"));
mav.addObject("wechatSharePic", shareMap.get("wechatSharePic"));
int joinUserNum = userService.countJoinUserNum(); //统计报名人数
mav.addObject("joinUserNum", joinUserNum);
String subscribe = Tools.GetString(request.getParameter("subscribe"), "");
if(subscribe.equals("0")){
mav.addObject("subscribe", "0");
}
return mav;
}
工具类WechatUtil.java
发送请求前,使用base64加密的时候,有个小细节要注意下,当加密的字符串超过一定长度,会自动增加换行符号,所以请看Base64加密后有换行回车的解决办法
package com.aotain.wechat.utils;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.io.PrintWriter;
import java.net.ConnectException;
import java.net.HttpURLConnection;
import java.net.URL;
import java.security.MessageDigest;
import java.util.Formatter;
import java.util.HashMap;
import java.util.Map;
import java.util.UUID;
import javax.net.ssl.HttpsURLConnection;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLSocketFactory;
import javax.net.ssl.TrustManager;
import javax.servlet.ServletContext;
import javax.servlet.http.HttpServletRequest;
import org.apache.log4j.Logger;
import sun.net.www.URLConnection;
import net.sf.json.JSONObject;
public class WechatUtil {
private static Logger Log = Logger.getLogger("sysLog");
private static String AppId = Constants.getProValue("APPID");
private static String WechatKey = Constants.getProValue("wechatKey");
/**
* 方法名:httpRequest</br> 详述:发送http请求</br> 开发人员:souvc </br> 创建时间:2016-1-5
* </br>
*
* @param requestUrl
* @param requestMethod
* @param outputStr
* @return 说明返回值含义
* @throws 说明发生此异常的条件
*/
public static JSONObject httpRequest(String requestUrl, String requestMethod, String outputStr) throws Exception {
requestUrl = requestUrl.replaceAll("[\\s*\t\n\r]", ""); //避免base64加密后自动回车换行过长自动换行
JSONObject jsonObject = null;
StringBuffer buffer = new StringBuffer();
InputStream inputStream = null;
InputStreamReader inputStreamReader = null;
BufferedReader bufferedReader = null;
HttpURLConnection httpUrlConn = null;
try {
URL url = new URL(requestUrl);
httpUrlConn = (HttpURLConnection) url.openConnection();
httpUrlConn.setDoOutput(true);
httpUrlConn.setDoInput(true);
httpUrlConn.setUseCaches(false);
httpUrlConn.setRequestMethod(requestMethod);
//httpUrlConn.setRequestProperty("content-type", "application/x-www-form-urlencoded");
if ("GET".equalsIgnoreCase(requestMethod))
httpUrlConn.connect();
if (null != outputStr) {
OutputStream outputStream = httpUrlConn.getOutputStream();
outputStream.write(outputStr.getBytes("UTF-8"));
outputStream.close();
}
inputStream = httpUrlConn.getInputStream();
inputStreamReader = new InputStreamReader(inputStream, "utf-8");
bufferedReader = new BufferedReader(inputStreamReader);
String str = null;
while ((str = bufferedReader.readLine()) != null) {
buffer.append(str);
}
jsonObject = JSONObject.fromObject(buffer.toString());
} catch (Exception e) {
Log.error("发送HTTP请求异常,请求地址:"+requestUrl+",请求方法:"+requestMethod+",请求结果:"+jsonObject);
}finally{
if(bufferedReader != null){
bufferedReader.close();
}
if(inputStreamReader != null){
inputStreamReader.close();
}
if(inputStream != null){
inputStream.close();
}
httpUrlConn.disconnect();
}
return jsonObject;
}
/**
* 方法名:getWxConfig</br> 详述:获取微信的配置信息 </br> 开发人员:souvc </br> 创建时间:2016-1-5
* </br>
*
* @param request
* @return 说明返回值含义
* @throws Exception
* @throws 说明发生此异常的条件
*/
public static Map<String, Object> getWxConfig(String activity,HttpServletRequest request) throws Exception {
String WechatShareTitle = Constants.getProValue(activity+"_wechatShareTitle"); //配置文件中的分享标题
String WechatShareDesc = Constants.getProValue(activity+"_wechatShareDesc"); //分享描述
String WechatSharePic = Constants.getProValue(activity+"_wechatSharePic"); //分享的图片链接
long timestamp = System.currentTimeMillis() / 1000; // 必填,生成签名的时间戳
String nonceStr = UUID.randomUUID().toString(); // 必填,生成签名的随机串
String beforeAuth = nonceStr +"$"+timestamp + "$" + WechatKey;
String auth = Tools.getBase64Code(Tools.GetMD5Codes(beforeAuth)); //身份验证
ServletContext sc = request.getSession().getServletContext();// 获取全局对象
String timestampStr = timestamp + "";
String getWxConfigUrl = "你的存储有微信凭证的项目地址?nonceStr="+nonceStr+"×tamp="+timestampStr+"&auth="+auth;
Log.info("requestParam"+"|"+"timestamp="+timestamp+",nonceStr="+nonceStr+",auth="+auth+",getWxConfigUrl="+getWxConfigUrl);
JSONObject WxConfigJson = WechatUtil.httpRequest(getWxConfigUrl,"GET",null);
Log.info("WxConfigJson:"+WxConfigJson);
Map<String, Object> shareMap = new HashMap<String, Object>();
if(WxConfigJson != null && WxConfigJson.getInt("resultCode") == 0){
String accessToken = WxConfigJson.getString("accessToken");
String jsapiTicket = WxConfigJson.getString("jsapiTicket");
String requestUrl = request.getRequestURL().toString();
String requestParam = request.getQueryString();//获取携带的参数
if(requestParam != null){
requestUrl = requestUrl +"?"+ requestParam; //组成真正的URL
/* requestUrl = requestUrl +"?param=2"; //组成真正的URL*/
}
String sign = "jsapi_ticket=" + jsapiTicket + "&noncestr=" + nonceStr + "×tamp=" + timestamp + "&url=" + requestUrl;
MessageDigest crypt = MessageDigest.getInstance("SHA-1");
crypt.reset();
crypt.update(sign.getBytes("UTF-8"));
String signature = byteToHex(crypt.digest());
shareMap.put("appId", AppId); // 注意这里参数名必须全部小写,且必须有序
shareMap.put("timestamp", timestamp);
shareMap.put("nonceStr", nonceStr);
shareMap.put("signature", signature);
shareMap.put("requestUrl", requestUrl);
shareMap.put("wechatShareTitle", WechatShareTitle);
shareMap.put("wechatShareDesc", WechatShareDesc);
shareMap.put("wechatSharePic", WechatSharePic);
shareMap.put("accessToken", accessToken);//微信凭证
sc.setAttribute("accessToken", accessToken);//将凭证缓存起来,方便获取
}
return shareMap;
}
/**
* 方法名:byteToHex</br> 详述:字符串加密辅助方法 </br> 开发人员:souvc </br> 创建时间:2016-1-5
* </br>
*
* @param hash
* @return 说明返回值含义
* @throws 说明发生此异常的条件
*/
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;
}
}
Tools.java 中涉及的MD5加密和base64位加密,看我分享的代码即可。
最后是jsp页面的写法,全部的写法可以参考分享接口:我这里的写法是在jsp加载的时候将页面中的变量赋值,值从前面的controller传递过来的。这些东西写在body 和 html 标签即可,当然写在哪里都可以。注意要记得引进
http://res.wx.qq.com/open/js/jweixin-1.2.0.js
</body>
<script type="text/javascript">
// 微信信息的以及调用的配置
wx.config({
debug: false,
appId: '${appId}',
timestamp: '${timestamp}',
nonceStr: '${nonceStr}',
signature: '${signature}',
jsApiList: ['onMenuShareTimeline', 'onMenuShareAppMessage']
});
wx.ready(function(){
// 获取“分享到朋友圈”按钮点击状态及自定义分享内容接口
wx.onMenuShareTimeline({
title: '${wechatShareTitle}', // 分享标题
desc: "${wechatShareDesc}", // 分享描述
link:"${requestUrl}",
imgUrl: "${wechatSharePic}", // 分享图标
type: 'link', // 分享类型,music、video或link,不填默认为link
// 用户确认分享后执行的回调函数
success: function () {
// index.sub("clickShare");
console.log("用户确认分享后执行的回调函数");
},
// 用户取消分享后执行的回调函数
cancel: function () {
console.log("用户取消分享后执行的回调函数");
}
});
// 获取“分享给朋友”按钮点击状态及自定义分享内容接口
wx.onMenuShareAppMessage({
title: '${wechatShareTitle}', // 分享标题
desc: "${wechatShareDesc}", // 分享描述
link:"${requestUrl}",
imgUrl: "${wechatSharePic}", // 分享图标
type: 'link', // 分享类型,music、video或link,不填默认为link
// 用户确认分享后执行的回调函数
success: function () {
// index.sub("clickShare");
console.log("用户确认分享后执行的回调函数");
},
// 用户取消分享后执行的回调函数
cancel: function () {
console.log("用户取消分享后执行的回调函数");
}
});
});
</script>
</html>
使用微信分享接口还有小地方注意下,就是当前的页面的url是什么,配置中的link就是什么,比如说我这里是http:xxxxx.com/wechat/share.do(springmvc的转发,地址栏就是显示这样的地址),那么link对应的就是这个,注意是在controller发请求,而不是到了jsp再写下面这两行java代码,因为这样的url实际上jsp的地址,有可能会导致分享失败或者暴露资源地址。
Map<String, Object> shareMap = WechatUtil.getWxConfig(activity,request);
至此,微信分享部分已经完成。不过我这里还有个小缺陷,我这里是使用jsp页面加载,然后将微信的配置信息进行渲染,然后实现分享,但是这样只能使用微信的分享按钮。感兴趣的朋友可以研究下是否可以做成自定义一个分享按钮,点击按钮发送请求来获取参数,再将网页分享出去。
结语
由于部分代码是贴上去的,可能看的时候不太方便,所以有不明白或者有好的建议的朋友,都可以给我留言。这个功能我已经实现,我就是给有需要的朋友一个参考,抛砖引玉,将代码改成符合自己业务需要的。
全局存储微信凭证源代码地址,maven项目:
http://download.csdn.net/download/qq_32574435/10196662