微信网页分享
背景:
鉴于微信的流行,很多的web app都会依赖于微信浏览器进行传播和推广。微信浏览器有自带的分享功能。但是若不做任何改动,直接使用此功能只会将当前页的链接分享出去,不会有自定义特色的文案。如果想要在分享的链接中加上某些参数也做不到。
平台需要一个邀请功能,用户通过点击分享按钮可以在分享的链接中带上邀请人的信息,分享出去的文案有介绍当前平台的特色,且可以不定时的更换。
那么直接分享是不能满足的,微信开发者们考虑到这方面的问题已经给出了接口供我们进行调用
时序图:
具体逻辑流程
微信之所以能够识别并正确的分享你想分享的内容,是因为我们调用了微信提供的js代码,并且给到了能够识别我们身份的信息,如唯一识别平台的appid,识别分享者信息有效的票据(加密后)signature。
1 请求后端前往微信服务端或者票据,并进行加密,然后返回给前端
1.1 带上当前的时间戳和当前平台的域名前往后端
1.2 后端调用微信提供的工具类进行票据的获取
1.3 工具类方法会封装获取token的链接,然后请求微信服务端获取token
1.4 根据获取的token,封装获取ticket的链接,然后请求微信服务的获取ticket
1.5 将拿到的ticke和传到后端的时间戳,url封装好进行加密,将加密好的数据返给前端
2 前端拿到票据加密后的数据对微信浏览器的分享按钮进行事件的初始化和绑定
实践操作
1 获取appid 和 appsecret
登录微信公众号,找到开发下的基本配置:
AppSecret可能你已经忘记了,得找此微信公众号的管理员要,或者重置.
注: AppSecret必须保留一份,因为微信不为你保留
2 获取token
获取token的api接口:
返回结果: {"access_token":"ACCESS_TOKEN","expires_in":7200}
Java代码:
HttpClient httpClient =new DefaultHttpClient();
HttpGet httpGet = new HttpGet(url);
HttpResponse response = httpClient.execute(httpGet);
HttpEntity entity = response.getEntity();
String string = EntityUtils.toString(entity, ContentType.getOrDefault(entity).getCharset());
JSONObject obj = JSONObject.fromObject(string);
String token = obj.get("access_token").toString();
3 获取jsapi_ticket
获取ticket的api接口:
返回结果:{"errcode":0,"errmsg":"ok","ticket":"ticket","expires_in":7200}
HttpClient httpClient =new DefaultHttpClient();
HttpGet httpGet = new HttpGet(url);
HttpResponse response = httpClient.execute(httpGet);
HttpEntity entity = response.getEntity();
String string = EntityUtils.toString(entity, ContentType.getOrDefault(entity).getCharset());
JSONObject obj = JSONObject.fromObject(string);
String token = obj.get("ticket").toString();
4 获取signature
网上找一个SHA1方法的代码,用来签名
这是我找到的网上的代码,使用getsha1(string)方法即可得到signature
String string = "jsapi_ticket="+ticket+"&noncestr=shike×tamp="+timestamp+"&url="+url;
String signature = Sha1Util.getSha1(string);
注: 参数的顺序必须是自然顺序(微信开发文档中有提到)
5 引用微信js
在需要调用JS接口的页面引入如下JS文件,(支持https):
6 编写js
1 定义基本变量
var imgUrl = 'http://test.shike8888.com/img/share_240.jpg';//这里是分享的时候的那个图片
var descContent = "提升权重--爆款打造专家";
var appid = 'wx9d88b98****56'; //这里写开发者接口里的appid
var timestamp = parseInt(new Date().getTime()/1000);
var url = window.location.href;
var shareTitle = '';
var signature='';
var lineLink = '';//这个是分享的网址
2 配置config
wx.config({
debug: false, // 开启调试模式,调用的所有api的返回值会在客户端alert出来,若要查看传入的参数,可以在pc端打开,参数信息会通过log打出,仅在pc端时才会打印。
appId: appid, // 必填,公众号的唯一标识
timestamp: timestamp, // 必填,生成签名的时间戳
nonceStr: 'shike', // 必填,生成签名的随机串
signature: signature,// 必填,签名,见附录1
jsApiList: ['onMenuShareTimeline',
'onMenuShareAppMessage',
'onMenuShareQQ',
'onMenuShareWeibo',
'onMenuShareQZone']//必填,需要使用的JS接口列表,所有JS接口列表见附录2
});
3 定义各个share函数
/* 分享到朋友圈 */
wx.onMenuShareTimeline({
title: shareTitle, // 分享标题
link: lineLink, // 分享链接,该链接域名或路径必须与当前页面对应的公众号JS安全域名一致
imgUrl: imgUrl, // 分享图标
success: function (res) {
// 用户确认分享后执行的回调函数
console.log(res)
},
cancel: function () {
// 用户取消分享后执行的回调函数
}
});
/* 分享给朋友 */
wx.onMenuShareAppMessage({
title: shareTitle, // 分享标题
desc: descContent, // 分享描述
link: lineLink, // 分享链接,该链接域名或路径必须与当前页面对应的公众号JS安全域名一致
imgUrl: imgUrl, // 分享图标
// type: '', // 分享类型,music、video或link,不填默认为link
// dataUrl: '', // 如果type是music或video,则要提供数据链接,默认为空
success: function () {
// 用户确认分享后执行的回调函数
},
cancel: function () {
// 用户取消分享后执行的回调函数
}
});
/*分享到qq */
wx.onMenuShareQQ({
title: shareTitle, // 分享标题
desc: descContent, // 分享描述
link: lineLink, // 分享链接
imgUrl: imgUrl, // 分享图标
success: function () {
// 用户确认分享后执行的回调函数
},
cancel: function () {
// 用户取消分享后执行的回调函数
}
});
/* 分享到qq空间 */
wx.onMenuShareQZone({
title: shareTitle, // 分享标题
desc: descContent, // 分享描述
link: lineLink, // 分享链接
imgUrl: imgUrl, // 分享图标
success: function () {
// 用户确认分享后执行的回调函数
},
cancel: function () {
// 用户取消分享后执行的回调函数
}
});
wx.onMenuShareWeibo({
title: shareTitle, // 分享标题
desc: descContent, // 分享描述
link: lineLink, // 分享链接
imgUrl: imgUrl, // 分享图标
success: function () {
// 用户确认分享后执行的回调函数
},
cancel: function () {
// 用户取消分享后执行的回调函数
}
});
分析上面的代码不难发现,除了方法名不一样外其他的都是大同小异,所以可以将参数抽取出来作为变量定义起来使用.
4 获取signature
由于获取signature必须先获取jsapi_ticket、token、appid和appsecret,appsecret属于保密信息,不应该放在js中显示,所以建议这些代码写在后台,然后通过ajax获取signature
$.ajax({
url:'/auth/getWeixinShareInfos',
data:{timestamp:timestamp,url:url},
dataType:'json',
success:function(ret){
//coding...........
initWX();
} } })
7 测试
这块需要大家自行测试
注意点:
1 js中的timestamp和nonceStr必须和后台的保持一致,微信会做校验,不一致是会报异常
2 sha1方法中的param含有一个url,这个url必须是调用分享的页面的url,建议从页面的js中传入,这样不会因为页面url有后缀而报错
3 linkLine和imaUrl的格式必须是https://...格式不然会读不到
完整代码:
wx.js
var timestamp = parseInt(new Date().getTime()/1000);
var url = window.location.href;
function WX_share(){
$.ajax({
url:'/auth/getWeixinShareInfos',
data:{timestamp:timestamp,url:url},
dataType:'json',
success:function(ret){
if (ret.code ==1 ) {
var rootPath='http://'+document.domain; // 域名
var descContent = "试客巴试用平台用效果和服务说话,帮助商家早赚钱,赚大钱"; // 分享的内容描述
var shareTitle = '2018一站式权重优化专家,7天手淘搜索破千'; // 分享的标题
var appid = 'wx9d88b*******56'; //这里写开发者接口里的appid
var imgUrl = rootPath+'/img/share_240.jpg'; //这里是分享出去的图片,一般是logo图片
var lineLink = rootPath; // 分享出去的根域名
var signature = ret.signature; // 加密后的数据
if (ret.invitecode) {
lineLink=rootPath+'/?ic='+ret.invitecode;
}
initWX(appid,timestamp,signature,lineLink,descContent,imgUrl,shareTitle);
}
}
})
}
function initWX(appid,timestamp,signature,lineLink,descContent,imgUrl,shareTitle){
wx.config({
debug: false, // 开启调试模式,调用的所有api的返回值会在客户端alert出来,若要查看传入的参数,可以在pc端打开,参数信息会通过log打出,仅在pc端时才会打印。
appId: appid, // 必填,公众号的唯一标识
timestamp: timestamp, // 必填,生成签名的时间戳
nonceStr: 'shike', // 必填,生成签名的随机串
signature: signature,// 必填,签名,见附录1
jsApiList: ['onMenuShareTimeline',
'onMenuShareAppMessage',
'onMenuShareQQ',
'onMenuShareWeibo',
'onMenuShareQZone'] // 必填,需要使用的JS接口列表,所有JS接口列表见附录2
});
wx.ready(function(){
/* 分享到朋友圈 */
wx.onMenuShareTimeline({
title: descContent, // 分享标题
link: lineLink, // 分享链接,该链接域名或路径必须与当前页面对应的公众号JS安全域名一致
imgUrl: imgUrl, // 分享图标
success: function (res) {
// 用户确认分享后执行的回调函数
},
cancel: function () {
// 用户取消分享后执行的回调函数
}
});
/* 分享给朋友 */
wx.onMenuShareAppMessage({
title: shareTitle, // 分享标题
desc: descContent, // 分享描述
link: lineLink, // 分享链接,该链接域名或路径必须与当前页面对应的公众号JS安全域名一致
imgUrl: imgUrl, // 分享图标
// type: '', // 分享类型,music、video或link,不填默认为link
// dataUrl: '', // 如果type是music或video,则要提供数据链接,默认为空
success: function () {
// 用户确认分享后执行的回调函数
},
cancel: function () {
// 用户取消分享后执行的回调函数
}
});
/*分享到qq */
wx.onMenuShareQQ({
title: shareTitle, // 分享标题
desc: descContent, // 分享描述
link: lineLink, // 分享链接
imgUrl: imgUrl, // 分享图标
success: function () {
// 用户确认分享后执行的回调函数
},
cancel: function () {
// 用户取消分享后执行的回调函数
}
});
/* 分享到qq空间 */
wx.onMenuShareQZone({
title: shareTitle, // 分享标题
desc: descContent, // 分享描述
link: lineLink, // 分享链接
imgUrl: imgUrl, // 分享图标
success: function () {
// 用户确认分享后执行的回调函数
},
cancel: function () {
// 用户取消分享后执行的回调函数
}
});
wx.onMenuShareWeibo({
title: shareTitle, // 分享标题
desc: descContent, // 分享描述
link: lineLink, // 分享链接
imgUrl: imgUrl, // 分享图标
success: function () {
// 用户确认分享后执行的回调函数
},
cancel: function () {
// 用户取消分享后执行的回调函数
}
});
});
}
WXTicketUtils.java
package com.yancheng.fission.utils;
import java.io.IOException;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import org.apache.commons.lang3.StringUtils;
import org.apache.http.HttpEntity;
import org.apache.http.HttpResponse;
import org.apache.http.client.HttpClient;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.entity.ContentType;
import org.apache.http.impl.client.DefaultHttpClient;
import org.apache.http.util.EntityUtils;
import com.yanchengtech.tools.wxpay.utils.Sha1Util;
import net.sf.json.JSONObject;
/**
* 获取微信jsapi_ticket
* Administrator */
@SuppressWarnings("all")
public class WXTicketUtils {
private static String APPID ="wx9d88b98******56"; // 平台对应的微信公众的appid
private static String APPSECRET="dab3deb57cd5b388*********883a6"; // 平台对应的微信公众的密钥
private static Map<String, String> TICKET = new HashMap<String,String>();
public static String getTicket(){
Long now = new Date().getTime();
if (TICKET.get("ticket")==null ||(TICKET.get("time")!=null && (now - Long.parseLong(TICKET.get("time")))>7200*1000)) {
//当没有ticket或者,ticket的时间超过7200秒,重新获取ticket,存放进map
String url ="https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid="+APPID+"&secret="+APPSECRET;
String getTicket = getToken_getTicket(url);
if (StringUtils.isNotBlank(getTicket)) {
TICKET.put("time", now.toString());
TICKET.put("ticket", getTicket);
}
}
return TICKET.get("ticket");
}
public static String getToken_getTicket(String url) {
try {
HttpClient httpClient =new DefaultHttpClient();
HttpGet httpGet = new HttpGet(url);
HttpResponse response = httpClient.execute(httpGet);
HttpEntity entity = response.getEntity();
String string = EntityUtils.toString(entity, ContentType.getOrDefault(entity).getCharset());
JSONObject obj = JSONObject.fromObject(string);
String token = obj.get("access_token").toString();
if (StringUtils.isNotBlank(token)) {
String url1="https://api.weixin.qq.com/cgi-bin/ticket/getticket?access_token="+token+"&type=jsapi";
httpGet = new HttpGet(url1);
response = httpClient.execute(httpGet);
entity = response.getEntity();
string = EntityUtils.toString(entity, ContentType.getOrDefault(entity).getCharset());
obj = JSONObject.fromObject(string);
if (StringUtils.isNotBlank(obj.get("ticket").toString())) {
return obj.get("ticket").toString();
}
}
EntityUtils.consume(entity);
} catch (IOException e) {
e.printStackTrace();
}
return null;
}
}