实现微信自定义分享网页(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

猜你喜欢

转载自blog.csdn.net/qq_32574435/article/details/79009775