Grails框架实现微信自定义分享页



微信分享说明文档

官方实例

微信web开发者工具可以在pc端进行测试开发


下面我来说说具体来实现这个自定义分享

需要在官方申请一个公众号

在Grails 2.4.4中需要安装rest-client-builder 2.1.1 插件


下面由代码来讲解这个分享过程

WeixinpayConfig.groovy 配置微信相关参数信息

package com.weixin.config;

/**
 * 
 * @author lvbaolin
 * @date 2016年5月27日上午11:10:50
 * @company 北京远中和科技
 *
 *          配置微信参数
 */

public class WeixinpayConfig {

	// 配置基本信息
	
	//微信分享使用
	//公众号appId
	public static String SHAREDAPPID = "*****";
	
	//微信分享使用
	//公众号secret
	public static String SHAREDSECRET = "****";
	
	//获取access_token 路径
	public static String ACCESS_TOKEN = "https://api.weixin.qq.com/cgi-bin/token";
	
	//获得jsapi_ticket 路径
	public static String TICKENT = "https://api.weixin.qq.com/cgi-bin/ticket/getticket";
	
	// 基本信息结束
}



WinxinUtils.groovy 请求微信工具类

package com.weixinpay.util


import grails.plugins.rest.client.RestBuilder
import org.springframework.http.HttpStatus
import business.BusinessException
import com.weixin.config.WeixinpayConfig


class WinxinUtils {
	
	/**
	 * 对微信发起get请求获取相关数据
	 * @param 
	 * @return
	 */
	private static getWeixinRequest(def path){
		RestBuilder rest = new RestBuilder()
		def resp = rest.get(path)
		if(resp.statusCode == HttpStatus.OK) {
			//println resp.body
			return resp.json
		} else
			throw new Exception(resp.text)
		return null
		
	}

}


WeixinsignUtils.groovy 生成随机字符串和SHA1加密

package com.weixin.sign;


import java.util.Map;
import com.weixin.config.WeixinpayConfig;
import com.weixin.util.MapUtil;

/**
 * 
 * @author lvbaolin
 * @date 2016年5月27日上午11:45:29
 * @company 北京远中和科技
 *
 *          
 */
public class WeixinsignUtils {


	/**
	 * 随机字符串,不长于32位。推荐随机数生成算法
		 * @return
	 */
	public static String createStr() {
		// TODO Auto-generated method stub
		String str = "";
		char[] ch = new char[] { '0', '1', '2', '3', '4', '5', '6', '7', '8',
				'9', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k',
				'l', 'm', 'n', 'o', 'p','q','r','s','t','u','v','w','x','y','z'};
		// int n = 1 + (int) (Math.random() * 25) ;
		int n = 20;
		for (int i = 0; i < n; i++) {
			int x = 1 + (int) (Math.random() * 33);
			str += ch[x];
		}
		return str;
	}

	/**
	 * create sign
		 * @param map
	 * @return
	 */
	public static String createSHA1_Sign(Map map) {
		
		String result = MapUtil.mapJoin(map, true, false);
		//System.out.println(result);
		try {
			result = SHA1.getSHA1HexString(result);
		} catch (Exception e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
			System.out.println("sign create error");
		}		
		return result;
	}


}



MapUtil.groovy Map工具类

package com.weixin.util;


import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.lang.reflect.Field;
import java.net.URLEncoder;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;


import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;


import org.w3c.dom.DOMException;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.NodeList;
import org.xml.sax.SAXException;


/**
 * 
 * @author lvbaolin
 * @date 2016年5月30日下午2:55:35
 * @company 北京远中和科技
 *
 */
public class MapUtil {


	public static Map toMap(Object object){
        Map map = new HashMap();
        Field[] fields = object.getClass().getDeclaredFields();
        for (Field field : fields) {
            Object obj;
            try {
                obj = field.get(object);
                if(obj!=null){
                    map.put(field.getName(), obj);
                }
            } catch (IllegalArgumentException e) {
                e.printStackTrace();
            } catch (IllegalAccessException e) {
                e.printStackTrace();
            }
        }
        return map;
    }
	/**
	 * Map key 排序(ASCII字典序排序)
	 * @param map
	 * @return
	 */
	public static Map order(Map map){
		HashMap tempMap = new LinkedHashMap();
		List> infoIds = new ArrayList>(	map.entrySet());


		Collections.sort(infoIds, new Comparator>() {
			public int compare(Map.Entry o1,Map.Entry o2) {
				return (o1.getKey()).toString().compareTo(o2.getKey());
			}
		});


		for (int i = 0; i < infoIds.size(); i++) {
			Map.Entry item = infoIds.get(i);
			tempMap.put(item.getKey(), item.getValue());
		}
		
		return tempMap;
	}


	
	/**
	 * 转换对象为map
	 * @param object
	 * @param ignore
	 * @return
	 */
	public static Map objectToMap(Object object,String... ignore){
		Map tempMap = new LinkedHashMap();
		for(Field f : object.getClass().getDeclaredFields()){
			if(!f.isAccessible()){
				f.setAccessible(true);
			}
			boolean ig = false;
			if(ignore!=null&&ignore.length>0){
				for(String i : ignore){
					if(i.equals(f.getName())){
						ig = true;
						break;
					}
				}
			}
			if(ig){
				continue;
			}else{
				Object o = null;
				try {
					o = f.get(object);
				} catch (IllegalArgumentException e) {
					e.printStackTrace();
				} catch (IllegalAccessException e) {
					e.printStackTrace();
				}
				tempMap.put(f.getName(), o==null?"":o.toString());
			}
		}
		return tempMap;
	}


	/**
	 * url 参数串连
	 * @param map
	 * @param keyLower
	 * @param valueUrlencode
	 * @return
	 */
	public static String mapJoin(Map map,boolean keyLower,boolean valueUrlencode){
		StringBuilder stringBuilder = new StringBuilder();
		for(String key :map.keySet()){
			if(map.get(key)!=null&&!"".equals(map.get(key))){
				try {
					String temp = (key.endsWith("_")&&key.length()>1)?key.substring(0,key.length()-1):key;
					stringBuilder.append(keyLower?temp.toLowerCase():temp)
								 .append("=")
								 .append(valueUrlencode?URLEncoder.encode(map.get(key),"utf-8").replace("+", "%20"):map.get(key))
								 .append("&");
				} catch (UnsupportedEncodingException e) {
					e.printStackTrace();
				}
			}
		}
		if(stringBuilder.length()>0){
			stringBuilder.deleteCharAt(stringBuilder.length()-1);
		}
		return stringBuilder.toString();
	}


	/**
	 * 简单 xml 转换为 Map
	 * @param reader
	 * @return
	 */
	public static Map xmlToMap(String xml){
		try {
			DocumentBuilder documentBuilder = DocumentBuilderFactory.newInstance().newDocumentBuilder();
			Document document = documentBuilder.parse(new ByteArrayInputStream(xml.getBytes()));
			Element element = document.getDocumentElement();
			NodeList nodeList = element.getChildNodes();
			Map map = new LinkedHashMap();
			for(int i=0;i				Element e = (Element) nodeList.item(i);
				map.put(e.getNodeName(),e.getTextContent());
			}
			return map;
		} catch (DOMException e) {
			e.printStackTrace();
		} catch (ParserConfigurationException e) {
			e.printStackTrace();
		} catch (SAXException e) {
			e.printStackTrace();
		} catch (IOException e) {
			e.printStackTrace();
		}
		return null;
	}
}



SHA1.groovy 把字符串进行SHA1加密

package com.weixin.sign;

import java.security.MessageDigest;

public class SHA1 {


	public static String getSHA1HexString(String str) throws Exception {
        // SHA1签名生成
        MessageDigest md = MessageDigest.getInstance("SHA-1");
        md.update(str.getBytes());
        byte[] digest = md.digest();


        StringBuffer hexstr = new StringBuffer();
        String shaHex = "";
        for (int i = 0; i < digest.length; i++) {
            shaHex = Integer.toHexString(digest[i] & 0xFF);
            if (shaHex.length() < 2) {
                hexstr.append(0);
            }
            hexstr.append(shaHex);
        }
        return hexstr.toString();
    }
	
	
	private final int[] abcde = {   
            0x67452301, 0xefcdab89, 0x98badcfe, 0x10325476, 0xc3d2e1f0   
        };   
    // 摘要数据存储数组   
    private int[] digestInt = new int[5];   
    // 计算过程中的临时数据存储数组   
    private int[] tmpData = new int[80];   
    // 计算sha-1摘要   
    private int process_input_bytes(byte[] bytedata) {   
        // 初试化常量   
        System.arraycopy(abcde, 0, digestInt, 0, abcde.length);   
        // 格式化输入字节数组,补10及长度数据   
        byte[] newbyte = byteArrayFormatData(bytedata);   
        // 获取数据摘要计算的数据单元个数   
        int MCount = newbyte.length / 64;   
        // 循环对每个数据单元进行摘要计算   
        for (int pos = 0; pos < MCount; pos++) {   
            // 将每个单元的数据转换成16个整型数据,并保存到tmpData的前16个数组元素中   
            for (int j = 0; j < 16; j++) {   
                tmpData[j] = byteArrayToInt(newbyte, (pos * 64) + (j * 4));   
            }   
            // 摘要计算函数   
            encrypt();   
        }   
        return 20;   
    }   
    // 格式化输入字节数组格式   
    private byte[] byteArrayFormatData(byte[] bytedata) {   
        // 补0数量   
        int zeros = 0;   
        // 补位后总位数   
        int size = 0;   
        // 原始数据长度   
        int n = bytedata.length;   
        // 模64后的剩余位数   
        int m = n % 64;   
        // 计算添加0的个数以及添加10后的总长度   
        if (m < 56) {   
            zeros = 55 - m;   
            size = n - m + 64;   
        } else if (m == 56) {   
            zeros = 63;   
            size = n + 8 + 64;   
        } else {   
            zeros = 63 - m + 56;   
            size = (n + 64) - m + 64;   
        }   
        // 补位后生成的新数组内容   
        byte[] newbyte = new byte[size];   
        // 复制数组的前面部分   
        System.arraycopy(bytedata, 0, newbyte, 0, n);   
        // 获得数组Append数据元素的位置   
        int l = n;   
        // 补1操作   
        newbyte[l++] = (byte) 0x80;   
        // 补0操作   
        for (int i = 0; i < zeros; i++) {   
            newbyte[l++] = (byte) 0x00;   
        }   
        // 计算数据长度,补数据长度位共8字节,长整型   
        long N = (long) n * 8;   
        byte h8 = (byte) (N & 0xFF);   
        byte h7 = (byte) ((N >> 8) & 0xFF);   
        byte h6 = (byte) ((N >> 16) & 0xFF);   
        byte h5 = (byte) ((N >> 24) & 0xFF);   
        byte h4 = (byte) ((N >> 32) & 0xFF);   
        byte h3 = (byte) ((N >> 40) & 0xFF);   
        byte h2 = (byte) ((N >> 48) & 0xFF);   
        byte h1 = (byte) (N >> 56);   
        newbyte[l++] = h1;   
        newbyte[l++] = h2;   
        newbyte[l++] = h3;   
        newbyte[l++] = h4;   
        newbyte[l++] = h5;   
        newbyte[l++] = h6;   
        newbyte[l++] = h7;   
        newbyte[l++] = h8;   
        return newbyte;   
    }   
    private int f1(int x, int y, int z) {   
        return (x & y) | (~x & z);   
    }   
    private int f2(int x, int y, int z) {   
        return x ^ y ^ z;   
    }   
    private int f3(int x, int y, int z) {   
        return (x & y) | (x & z) | (y & z);   
    }   
    private int f4(int x, int y) {   
        return (x << y) | x >>> (32 - y);   
    }   
    // 单元摘要计算函数   
    private void encrypt() {   
        for (int i = 16; i <= 79; i++) {   
            tmpData[i] = f4(tmpData[i - 3] ^ tmpData[i - 8] ^ tmpData[i - 14] ^   
                    tmpData[i - 16], 1);   
        }   
        int[] tmpabcde = new int[5];   
        for (int i1 = 0; i1 < tmpabcde.length; i1++) {   
            tmpabcde[i1] = digestInt[i1];   
        }   
        for (int j = 0; j <= 19; j++) {   
            int tmp = f4(tmpabcde[0], 5) +   
                f1(tmpabcde[1], tmpabcde[2], tmpabcde[3]) + tmpabcde[4] +   
                tmpData[j] + 0x5a827999;   
            tmpabcde[4] = tmpabcde[3];   
            tmpabcde[3] = tmpabcde[2];   
            tmpabcde[2] = f4(tmpabcde[1], 30);   
            tmpabcde[1] = tmpabcde[0];   
            tmpabcde[0] = tmp;   
        }   
        for (int k = 20; k <= 39; k++) {   
            int tmp = f4(tmpabcde[0], 5) +   
                f2(tmpabcde[1], tmpabcde[2], tmpabcde[3]) + tmpabcde[4] +   
                tmpData[k] + 0x6ed9eba1;   
            tmpabcde[4] = tmpabcde[3];   
            tmpabcde[3] = tmpabcde[2];   
            tmpabcde[2] = f4(tmpabcde[1], 30);   
            tmpabcde[1] = tmpabcde[0];   
            tmpabcde[0] = tmp;   
        }   
        for (int l = 40; l <= 59; l++) {   
            int tmp = f4(tmpabcde[0], 5) +   
                f3(tmpabcde[1], tmpabcde[2], tmpabcde[3]) + tmpabcde[4] +   
                tmpData[l] + 0x8f1bbcdc;   
            tmpabcde[4] = tmpabcde[3];   
            tmpabcde[3] = tmpabcde[2];   
            tmpabcde[2] = f4(tmpabcde[1], 30);   
            tmpabcde[1] = tmpabcde[0];   
            tmpabcde[0] = tmp;   
        }   
        for (int m = 60; m <= 79; m++) {   
            int tmp = f4(tmpabcde[0], 5) +   
                f2(tmpabcde[1], tmpabcde[2], tmpabcde[3]) + tmpabcde[4] +   
                tmpData[m] + 0xca62c1d6;   
            tmpabcde[4] = tmpabcde[3];   
            tmpabcde[3] = tmpabcde[2];   
            tmpabcde[2] = f4(tmpabcde[1], 30);   
            tmpabcde[1] = tmpabcde[0];   
            tmpabcde[0] = tmp;   
        }   
        for (int i2 = 0; i2 < tmpabcde.length; i2++) {   
            digestInt[i2] = digestInt[i2] + tmpabcde[i2];   
        }   
        for (int n = 0; n < tmpData.length; n++) {   
            tmpData[n] = 0;   
        }   
    }   
    // 4字节数组转换为整数   
    private int byteArrayToInt(byte[] bytedata, int i) {   
        return ((bytedata[i] & 0xff) << 24) | ((bytedata[i + 1] & 0xff) << 16) |   
        ((bytedata[i + 2] & 0xff) << 8) | (bytedata[i + 3] & 0xff);   
    }   
    // 整数转换为4字节数组   
    private void intToByteArray(int intValue, byte[] byteData, int i) {   
        byteData[i] = (byte) (intValue >>> 24);   
        byteData[i + 1] = (byte) (intValue >>> 16);   
        byteData[i + 2] = (byte) (intValue >>> 8);   
        byteData[i + 3] = (byte) intValue;   
    }   
    // 将字节转换为十六进制字符串   
    private static String byteToHexString(byte ib) {   
        char[] Digit = {   
                '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C',   
                'D', 'E', 'F'   
            };   
        char[] ob = new char[2];   
        ob[0] = Digit[(ib >>> 4) & 0X0F];   
        ob[1] = Digit[ib & 0X0F];   
        String s = new String(ob);   
        return s;   
    }   
    // 将字节数组转换为十六进制字符串   
    private static String byteArrayToHexString(byte[] bytearray) {   
        String strDigest = "";   
        for (int i = 0; i < bytearray.length; i++) {   
            strDigest += byteToHexString(bytearray[i]);   
        }   
        return strDigest;   
    }   
    // 计算sha-1摘要,返回相应的字节数组   
    public byte[] getDigestOfBytes(byte[] byteData) {   
        process_input_bytes(byteData);   
        byte[] digest = new byte[20];   
        for (int i = 0; i < digestInt.length; i++) {   
            intToByteArray(digestInt[i], digest, i * 4);   
        }   
        return digest;   
    }   
    // 计算sha-1摘要,返回相应的十六进制字符串   
    public String getDigestOfString(byte[] byteData) {   
        return byteArrayToHexString(getDigestOfBytes(byteData));   
    }   
    
    /**/
    public static void main(String[] args) {   
        String data = "123456";   
        System.out.println(data);   
        String digest = new SHA1().getDigestOfString(data.getBytes());   
        System.out.println(digest);  
         
       // System.out.println( ToMD5.convertSHA1(data).toUpperCase());  
    }   
}



SharedController.groovy 这个就是请求分享的入口controller

package business


import grails.converters.JSON

import com.weixin.config.WeixinpayConfig
import com.weixin.sign.WeixinsignUtils
import com.weixin.util.MapUtil
import com.weixinpay.util.WinxinUtils

class TopicController {

	def grailsApplication
	def show(){
		def result = [:]
		//分享出去的数据
		result.data = [data:data]
		
		//获取access_token 路径
		def access_token_path = "${WeixinpayConfig.ACCESS_TOKEN }?appid=${WeixinpayConfig.SHAREDAPPID}&grant_type=client_credential&secret=${WeixinpayConfig.SHAREDSECRET }"
		def token_data = WinxinUtils.getWeixinRequest(access_token_path)
		
		//print "token_data=$token_data"
		
		//获取jsapi_ticket 路径
		def ticket_path = "${WeixinpayConfig.TICKENT }?access_token=${token_data.access_token }&type=jsapi"
		def ticket_data = WinxinUtils.getWeixinRequest(ticket_path)
		//print "ticket_data=$ticket_data"
		//
		def requestMap = [:]
		
		//有效的jsapi_ticket
		requestMap.jsapi_ticket = ticket_data.ticket
		
		//随机字符串
		requestMap.nonceStr = WeixinsignUtils.createStr()
		
		//timestamp生成签名的时间戳
		requestMap.timestamp = new Date().time + ""
		
		//当前网页的url
		requestMap.url = grailsApplication.config.topic.shareUrl+"/topic/show?tid="+params.tid
		
		//请求map进行排序
		Map map = MapUtil.order(requestMap)
		
		//生成签名
		requestMap.signature = WeixinsignUtils.createSHA1_Sign(map).toLowerCase()
		
		//必填,公众号的唯一标识
		requestMap.appId=WeixinpayConfig.SHAREDAPPID
		
		//print requestMap
		
		result.weixin = requestMap
		//render result as JSON
		[result: result]
	}



下面的js代码是在分享页面增加的微信





shared.js 分享出去时配置相关自定义信息

/*
 * 注意:
 * 1. 所有的JS接口只能在公众号绑定的域名下调用,公众号开发者需要先登录微信公众平台进入“公众号设置”的“功能设置”里填写“JS接口安全域名”。
 * 2. 如果发现在 Android 不能分享自定义内容,请到官网下载最新的包覆盖安装,Android 自定义分享接口需升级至 6.0.2.58 版本及以上。
 * 3. 完整 JS-SDK 文档地址:http://mp.weixin.qq.com/wiki/7/aaa137b55fb2e0456bf8dd9148dd613f.html
 *
 * 如有问题请通过以下渠道反馈:
 * 邮箱地址:[email protected]
 * 邮件主题:【微信JS-SDK反馈】具体问题
 * 邮件内容说明:用简明的语言描述问题所在,并交代清楚遇到该问题的场景,可附上截屏图片,微信团队会尽快处理你的反馈。
 */


function divHide(){
	$(".yes_box").hide();
}

function sharedSuccess(){
	$(".yes_box").show();
	window.setTimeout("divHide()",1000);//使用字符串执行方法 
}

//sharedSuccess();

wx.ready(function() {
	
	// 2. 分享接口
	// 2.1 自定义分享内容及分享结果接口
	wx.onMenuShareAppMessage({
		title : $("#title").val(), // 分享标题
		desc : $("#desc").val(), // 分享描述
		link : $("#link").val(), // 分享链接
		imgUrl : $("#imgUrl").val(), // 分享图标
		// type: '', // 分享类型,music、video或link,不填默认为link
		// dataUrl: '', // 如果type是music或video,则要提供数据链接,默认为空
		trigger : function(res) {
			//alert('用户点击分享给朋友');
		},
		complete : function(res) {
			//alert(JSON.stringify(res));
		},
		success : function(res) {
			sharedSuccess();
			//alert('已分享');
		},
		cancel : function(res) {
			//alert('已取消');
		},
		fail : function(res) {
			alert(JSON.stringify(res));
		}
	});
	
	
	
	// 2.2 监听“分享到朋友圈”按钮点击、自定义分享内容及分享结果接口
	    wx.onMenuShareTimeline({
	      title: $("#title").val(),
	        link: $("#link").val(),
	        imgUrl: $("#imgUrl").val(),
	      trigger: function (res) {
	        // 不要尝试在trigger中使用ajax异步请求修改本次分享的内容,因为客户端分享操作是一个同步操作,这时候使用ajax的回包会还没有返回
	        //alert('用户点击分享到朋友圈');
	      },
	      success: function (res) {
	    	  sharedSuccess();
	        //alert('已分享');
	      },
	      cancel: function (res) {
	        //alert('已取消');
	      },
	      fail: function (res) {
	        alert(JSON.stringify(res));
	      }
	    });


	  // 2.3 监听“分享到QQ”按钮点击、自定义分享内容及分享结果接口
	    wx.onMenuShareQQ({
	      title: $("#title").val(),
	        desc: $("#desc").val(),
	        link: $("#link").val(),
	        imgUrl: $("#imgUrl").val(),
	      trigger: function (res) {
	        //alert('用户点击分享到QQ');
	      },
	      complete: function (res) {
	        //alert(JSON.stringify(res));
	      },
	      success: function (res) {
	    	  sharedSuccess();
	        //alert('已分享');
	      },
	      cancel: function (res) {
	        //alert('已取消');
	      },
	      fail: function (res) {
	        alert(JSON.stringify(res));
	      }
	    });
	  
	  // 2.4 监听“分享到微博”按钮点击、自定义分享内容及分享结果接口
	    wx.onMenuShareWeibo({
	      title: $("#title").val(),
	        desc: $("#desc").val(),
	        link: $("#link").val(),
	        imgUrl: $("#imgUrl").val(),
	      trigger: function (res) {
	        //alert('用户点击分享到微博');
	      },
	      complete: function (res) {
	        //alert(JSON.stringify(res));
	      },
	      success: function (res) {
	    	  sharedSuccess();
	        //alert('已分享');
	      },
	      cancel: function (res) {
	        //alert('已取消');
	      },
	      fail: function (res) {
	        alert(JSON.stringify(res));
	      }
	    });


	  // 2.5 监听“分享到QZone”按钮点击、自定义分享内容及分享接口
	    wx.onMenuShareQZone({
	      title: $("#title").val(),
	        desc: $("#desc").val(),
	        link: $("#link").val(),
	        imgUrl: $("#imgUrl").val(),
	      trigger: function (res) {
	        //alert('用户点击分享到QZone');
	      },
	      complete: function (res) {
	        //alert(JSON.stringify(res));
	      },
	      success: function (res) {
	    	  sharedSuccess();
	        //alert('已分享');
	      },
	      cancel: function (res) {
	        //alert('已取消');
	      },
	      fail: function (res) {
	        alert(JSON.stringify(res));
	      }
	    });
	
});


wx.error(function(res) {
	// alert("验证失败。");
	alert(res.errMsg);
});






这样就完成了,微信页面的分享了,其中包含了后台生成的分享配置信息,和页面的自定义的信息。

分享成功的效果图:

Grails框架实现微信自定义分享页_第1张图片

希望这个能够帮助到你。






你可能感兴趣的:(Grails框架,java,微信分享)