1. 程式人生 > >實現微信自定義分享網頁(java)

實現微信自定義分享網頁(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

,具體可以檢視獲取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+"&timestamp="+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 + "&timestamp=" + 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