问题与需求:h5页面的内容在微信分享后分享的内容无法定制,要自定义分享内容为该h5页面的标题,内容,图片等。
实现与排错:由于本人技术有限,基本都是参照网络上大神们提供的源码,在部分地方做了简单修改。感谢大神们的无私奉献!主要参考链接和实现代码如下:
java开发微信分享到朋友圈功能 http://www.jb51.net/article/88690.htm
微信公众平台开发:JS-SDK之分享功能整理(Java)http://blog.csdn.net/dcb_ripple/article/details/52066708
实现】步骤一:参考微信公众平台的开发者文档https://mp.weixin.qq.com/wiki/7/aaa137b55fb2e0456bf8dd9148dd613f.html了解开发步骤
步骤二:为了描述方便,我先把h5页面的js代码贴出,再描述其实现所依赖的java代码。
1.1h5页面的js代码
//获取签名
$.ajax({
type: "GET",
url: "wshare/getSignature",
data:{url:url},
success: function(data){
console.log("success");
var objData=JSON.parse(data);
timestamp=objData.timestamp;
noncestr=objData.noncestr;
signature=objData.signature;
console.log(objData);
wxShare();
},
statusCode: {404: function(){
alert('page not found');
}}
});
function wxShare(){
wx.config({
debug: false, // 开启调试模式,调用的所有api的返回值会在客户端alert出来,若要查看传入的参数,可以在pc端打开,参数信息会通过log打出,仅在pc端时才会打印。
appId: 'wxca57e6589ccebff0', // 和获取Ticke的必须一样------必填,公众号的唯一标识
timestamp:timestamp, // 必填,生成签名的时间戳
nonceStr: noncestr, // 必填,生成签名的随机串
signature: signature,// 必填,签名,见附录1
jsApiList: [
'onMenuShareAppMessage','onMenuShareTimeline','onMenuShareTimeline','onMenuShareQQ','onMenuShareQZone'
] // 必填,需要使用的JS接口列表,所有JS接口列表见附录2
});
}
wx.ready(function(){
//config信息验证后会执行ready方法,所有接口调用都必须在config接口获得结果之后,
//config是一个客户端的异步操作,所以如果需要在页面加载时就调用相关接口,则须把相关
//接口放在ready函数中调用来确保正确执行。对于用户触发时才调用的接口,则可以直接调用,不需要放在ready函数中。
//----------“分享给朋友”
wx.onMenuShareAppMessage({
title:title, // 分享标题
desc:discuss, // 分享描述
link: url, // 分享链接
imgUrl: shareImgUrl, // 分享图标
success: function () {
// 用户确认分享后执行的回调函数、
},
cancel: function () {
// 用户取消分享后执行的回调函数
}
});
//------------"分享到朋友圈"
wx.onMenuShareTimeline({
title: title, // 分享标题
desc: discuss, // 分享描述
link: url, // 分享链接
imgUrl: shareImgUrl, // 分享图标
type: '', // 分享类型,music、video或link,不填默认为link
dataUrl: '', // 如果type是music或video,则要提供数据链接,默认为空
success: function () {
// 用户确认分享后执行的回调函数、
},
cancel: function () {
// 用户取消分享后执行的回调函数
}
});
//-------------分享到QQ
wx.onMenuShareQQ({
title: title, // 分享标题
desc: discuss, // 分享描述
link: url, // 分享链接
imgUrl: shareImgUrl, // 分享图标
type: '', // 分享类型,music、video或link,不填默认为link
dataUrl: '', // 如果type是music或video,则要提供数据链接,默认为空
success: function () {
// 用户确认分享后执行的回调函数、
},
cancel: function () {
// 用户取消分享后执行的回调函数
}
});
//-------------分享到QQ空间
wx.onMenuShareQZone({
title: title, // 分享标题
desc: discuss, // 分享描述
link: url, // 分享链接
imgUrl: shareImgUrl, // 分享图标
type: '', // 分享类型,music、video或link,不填默认为link
dataUrl: '', // 如果type是music或video,则要提供数据链接,默认为空
success: function () {
// 用户确认分享后执行的回调函数、
},
cancel: function () {
// 用户取消分享后执行的回调函数
}
});
});
wx.error(function(res){
// config信息验证失败会执行error函数,如签名过期导致验证失败,具体错误信息可以打开config的debug模式查看,也可以在返回的res参数中查看,对于SPA可以在这里更新签名。
console.log(res);
});
package com.weixin.controller;
import java.io.IOException;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Arrays;
import java.util.Date;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import com.weixin.util.WeixinUtil;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import com.google.gson.Gson;
import com.weixin.model.Ticket;
import com.weixin.util.GetRandomStr;
import com.weixin.util.SignatureBean;
/**
* Created by Administrator on 2017/1/9.
*/
@Controller
@RequestMapping("/wshare")
public class WeixinshareController {
private static Ticket aticket = null;
@RequestMapping("/getSignature")
public String getSignature( HttpServletRequest request,
HttpServletResponse response) throws IOException, ParseException{
//获取签名页面链接
String url = request.getParameter("url");
SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
//获取标签,并检查标签是否过期
if(aticket==null){//第一次访问,标签不存在。
aticket = executeTicket(response,"1",url,format);
return null;
}else{//标签存在,判断标签是否超时
String oldAcquiretime = aticket.getAcquiretime();
long difference=format.parse(format.format(new Date())).getTime()-format.parse(oldAcquiretime).getTime();
if(difference>7100000){//标签超时,重新到微信服务器请求标签超时时间为7200秒(7200000毫秒)
aticket = executeTicket(response,"2",url,format);
return null;
}else{//标签未超时
/**
* 注意事项
* 1.签名用的noncestr和timestamp必须与wx.config中的nonceStr和timestamp相同。
* 2.签名用的url必须是调用JS接口页面的完整URL。
* 3.出于安全考虑,开发者必须在服务器端实现签名的逻辑。
*
****根据第1点要求 signature 配置的时候很容易出错,需要把生成 Ticket的 noncestr和 timestamp传给客户端***
*/
System.out.print("ticket:"+aticket.getTicket());
String signature = signature(aticket.getTicket(),aticket.getTimestamp(),aticket.getNoncestr(),url);
SignatureBean signatureBean = new SignatureBean();
signatureBean.setNoncestr(aticket.getNoncestr());
signatureBean.setSignature(signature);
signatureBean.setTimestamp(aticket.getTimestamp());
signatureBean.setUrl(url);
response.setContentType("text/html;charset=UTF-8");
response.getWriter().print(new Gson().toJson(signatureBean));
return null;
}
}
}
/**
更新和获取ticket的方法,因为用的solr所以更新和新增是一样的ID无则添加,有则更新
*/
public Ticket executeTicket(HttpServletResponse response,String flag,String url,SimpleDateFormat format) throws IOException{
//获取签名随机字符串
GetRandomStr randomStr = new GetRandomStr();
String noncestr = randomStr.getRandomString(15);
//获取签名时间戳
String timestamp = Long.toString(System.currentTimeMillis());
//请求accessToken
String accessTokenUrl ="https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid=wxca57e6589ccebff0&secret=b7d7c5df24644563be32bd84b90c8bf6";
String tokenJson = WeixinUtil.httpRequest(accessTokenUrl, "GET", null);
Gson gson = new Gson();
ShareAccess_Token token = gson.fromJson(tokenJson, ShareAccess_Token.class);
String to= token.getAccess_token();
//获取标签
String urlTicket ="https://api.weixin.qq.com/cgi-bin/ticket/getticket?access_token="+to+"&type=jsapi";
String ticketJson = WeixinUtil.httpRequest(urlTicket, "GET", null);
Ticket ticket = gson.fromJson(ticketJson, Ticket.class);
String t = ticket.getTicket();
//String uuid = UUID.randomUUID().toString().trim().replaceAll("-", "");
//我的Ticket ID是写死的 20160114wiimediamrylsong1152
String acquiretime = format.format(new Date());
ticket.setTid("20160114wiimediamrylsong1152");
ticket.setAcquiretime(acquiretime);
ticket.setTimestamp(timestamp);
ticket.setNoncestr(noncestr);
/**
* 注意事项
* 1.签名用的noncestr和timestamp必须与wx.config中的nonceStr和timestamp相同。
* 2.签名用的url必须是调用JS接口页面的完整URL。
* 3.出于安全考虑,开发者必须在服务器端实现签名的逻辑。
*
*根据第1点要求 signature 配置的时候很容易出错,需要把生成 Ticket的 noncestr和 timestamp传给客户端*
*/
String signature = signature(t,timestamp,noncestr,url);
System.out.print("ticket: "+t);
SignatureBean signatureBean = new SignatureBean();
signatureBean.setNoncestr(noncestr);
signatureBean.setSignature(signature);
signatureBean.setTimestamp(timestamp);
signatureBean.setUrl(url);
response.setContentType("text/html;charset=UTF-8");
response.getWriter().print(new Gson().toJson(signatureBean));
return ticket;
}
/**
*
*
Project:mryl_phone_v2
* *:mryl_phone_v2
* *Description:根据标签,时间戳,密匙,URL进行签名
* *Company:Wiimedia
* *@Athor:SongJia * *@Date:2016-7-15 上午09:37:13 * */ private String signature(String jsapi_ticket, String timestamp, String noncestr, String url) { jsapi_ticket = "jsapi_ticket=" + jsapi_ticket; timestamp = "timestamp=" + timestamp; noncestr = "noncestr=" + noncestr; url = "url=" + url; String[] arr = new String[] { jsapi_ticket, timestamp, noncestr, url }; // 将token、timestamp、nonce,url参数进行字典序排序 Arrays.sort(arr); StringBuilder content = new StringBuilder(); for (int i = 0; i < arr.length; i++) { content.append(arr[i]); if (i != arr.length - 1) { content.append("&"); } } MessageDigest md = null; String tmpStr = null; try { md = MessageDigest.getInstance("SHA-1"); // 将三个参数字符串拼接成一个字符串进行sha1加密 byte[] digest = md.digest(content.toString().getBytes()); tmpStr = byteToStr(digest); } catch (NoSuchAlgorithmException e) { e.printStackTrace(); } content = null; return tmpStr; } /** * 将字节转换为十六进制字符串 * * @param mByte * @return */ private static String byteToHexStr(byte mByte) { char[] Digit = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F' }; char[] tempArr = new char[2]; tempArr[0] = Digit[(mByte >>> 4) & 0X0F]; tempArr[1] = Digit[mByte & 0X0F]; String s = new String(tempArr); return s; } /** * 将字节数组转换为十六进制字符串 * * @param byteArray * @return */ private static String byteToStr(byte[] byteArray) { String strDigest = ""; for (int i = 0; i < byteArray.length; i++) { strDigest += byteToHexStr(byteArray[i]); } return strDigest; } class ShareAccess_Token{ private String access_token; private String expires_in; public String getAccess_token() { return access_token; } public void setAccess_token(String accessToken) { access_token = accessToken; } public String getExpires_in() { return expires_in; } public void setExpires_in(String expiresIn) { expires_in = expiresIn; } } }package com.weixin.util;
/**
* Created by Administrator on 2017/1/9.
*/
public class SignatureBean {
private String noncestr;
private String url;
private String timestamp;
private String signature;
public String getNoncestr() {
return noncestr;
}
public void setNoncestr(String noncestr) {
this.noncestr = noncestr;
}
public String getUrl() {
return url;
}
public void setUrl(String url) {
this.url = url;
}
public String getTimestamp() {
return timestamp;
}
public void setTimestamp(String timestamp) {
this.timestamp = timestamp;
}
public String getSignature() {
return signature;
}
public void setSignature(String signature) {
this.signature = signature;
}
}
package com.weixin.util;
import java.util.Random;
/**
* Created by Administrator on 2017/1/9.
* 获取随机字符串
*/
public class GetRandomStr {
public String getRandomString(int length) {
String base = "abcdefghijklmnopqrstuvwxyz0123456789";
Random random = new Random();
StringBuffer sb = new StringBuffer();
for (int i = 0; i < length; i++) {
int number = random.nextInt(base.length());
sb.append(base.charAt(number));
}
return sb.toString();
}
}
package com.weixin.util;
import javax.net.ssl.X509TrustManager;
import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;
/**
* Created by Administrator on 2017/1/10.
*/
public class MyX509TrustManager implements X509TrustManager {
@Override
public void checkClientTrusted(X509Certificate[] x509Certificates, String s) throws CertificateException {
}
@Override
public void checkServerTrusted(X509Certificate[] x509Certificates, String s) throws CertificateException {
}
@Override
public X509Certificate[] getAcceptedIssuers() {
return new X509Certificate[0];
}
}
package com.weixin.model;
/**
* Created by Administrator on 2017/1/9.
*/
public class Ticket {
private String tid;
private String ticket;
private String errcode;
private String errmsg;
private String expires_in;
private String acquiretime;
private String noncestr;
private String timestamp;
public Ticket(String tid, String ticket, String errcode, String errmsg,
String expiresIn, String acquiretime, String noncestr,
String timestamp) {
super();
this.tid = tid;
this.ticket = ticket;
this.errcode = errcode;
this.errmsg = errmsg;
expires_in = expiresIn;
this.acquiretime = acquiretime;
this.noncestr = noncestr;
this.timestamp = timestamp;
}
public String getTid() {
return tid;
}
public void setTid(String tid) {
this.tid = tid;
}
public String getTicket() {
return ticket;
}
public void setTicket(String ticket) {
this.ticket = ticket;
}
public String getErrcode() {
return errcode;
}
public void setErrcode(String errcode) {
this.errcode = errcode;
}
public String getErrmsg() {
return errmsg;
}
public void setErrmsg(String errmsg) {
this.errmsg = errmsg;
}
public String getExpires_in() {
return expires_in;
}
public void setExpires_in(String expiresIn) {
expires_in = expiresIn;
}
public String getAcquiretime() {
return acquiretime;
}
public void setAcquiretime(String acquiretime) {
this.acquiretime = acquiretime;
}
public String getNoncestr() {
return noncestr;
}
public void setNoncestr(String noncestr) {
this.noncestr = noncestr;
}
public String getTimestamp() {
return timestamp;
}
public void setTimestamp(String timestamp) {
this.timestamp = timestamp;
}
}
@ComponentScan(basePackages = {"com.mzqadmin","com.baidu","com.weixin"})另外,调试时把debug:true如果debug:ok却始终无法完成定制,一定是你的js代码有问题,后台验证已经通过了,看看你的js代码里参数是否带“”什么的。别问我怎么知道的,低级错误害死人呐~