實現微信自定義分享網頁(java)
前言
網頁實現微信分享功能,這個其實在百度上是有很多例子的,而且寫得也都還不錯。不過我這個跟他們的不大一樣。一般的部落格會將分享需要的微信憑證這些寫進一個專案中,本專案獲取,本專案實現分享功能。而我是獲取微信憑證是單獨的一個專案,這樣一個服務號的獲取的微信憑證,可以提供給很多個專案使用,拓展性還是可以的,有需要的朋友可以參考下。
方案的選擇
其實我剛開始做的時候想到兩個方案:
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
然後使用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