微信分享说明文档
官方实例
微信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);
});
这样就完成了,微信页面的分享了,其中包含了后台生成的分享配置信息,和页面的自定义的信息。
分享成功的效果图:
希望这个能够帮助到你。