====== 微信分享实现 ======
===== 需求 =====
在微信客户端中打开页面,点击右上角**分享到朋友圈**或者**分享给朋友**时候,希望可以自定义【抬头+描述+图片+链接】,而不是微信去截取当前页面的链接地址(有可能带一堆参数,而且每个整个活动中每个页面的地址可能都不一样),截取当前页面的title等。
示意图,如下:(上半部分为**分享给朋友**的样式,下半部分为**分享到朋友圈**的样式)
{{:分享:技术:微信:1.png?500|}}
===== 准备 =====
- 服务号账号:cinvestors@tebon.com.cn
- 登录微信服务号后台,查看基础设置中,获得:AppID(应用ID)+AppSecret(应用密钥)
- 确认已经申请以下接口
- 获取“分享到朋友圈”按钮点击状态及自定义分享内容接口
- 获取“分享给朋友”按钮点击状态及自定义分享内容接口
===== 基础开发 =====
==== wx-share.js ====
这个js将被需要的页面依赖,上线前可以修改这个js中的【抬头+描述+图片+链接】
//微信分享
var wxShare = (function () {
var appId = 'wx2767b3767363f90e';//公众号APPID
var title = '德邦证券放“大招”猴年春晚发红包';//抬头
var desc = '没有模糊玻璃,无需花钱即可抢红包咯~';//描述
var imgUrl = 'http://test.tebon.com.cn/trans/images/award/img_hb.gif';//图片
var link = 'http://test.tebon.com.cn/trans/award/manager/index.htm';//链接
var timestamp = '';//时间戳 后台生成
var nonceStr = '';//随机串 后台生成
var signature = '';//签名 后台生成
var _init = function (_title, _desc) {
getSign();
};
var getSign = function () {
var reurl = location.href.split('#')[0];
var para = {appid: appId, url: reurl};
$.ajax({
type: "post",
data: para,
async: true,
url: contextPath + "/award/wxShare/wxShareSign.htm",
dataType: "json",
success: function (data) {
if (data.result == "true") {
timestamp = data.timestamp;
nonceStr = data.noncestr;
signature = data.signature;
wxConfig();
wxReady();
wxError();
} else {
alert(data.message);
}
}
,
error: function (xhr, errorType, error) {
}
}
)
;
};
var wxConfig = function () {
wx.config({
debug: false,
appId: appId,
timestamp: timestamp,
nonceStr: nonceStr,
signature: signature,
jsApiList: [
'onMenuShareTimeline',
'onMenuShareAppMessage'
]
});
};
var wxReady = function () {
wx.ready(function () {
//发送给朋友
wx.onMenuShareAppMessage({
title: title,
desc: desc,
link: link,
imgUrl: imgUrl,
trigger: function (res) {
//alert('用户点击发送给朋友');
},
success: function (res) {
//alert('已分享');跳转到排行榜页面
//window.location.href = 'revenue_ranking.html';
},
cancel: function (res) {
//alert('已取消');
},
fail: function (res) {
//alert(JSON.stringify(res));
}
});
//分享到朋友圈
wx.onMenuShareTimeline({
title: title,
desc: desc,
link: link,
imgUrl: imgUrl,
trigger: function (res) {
//alert('用户点击分享到朋友圈');
},
success: function (res) {
//alert('已分享');
//window.location.href = 'revenue_ranking.html';
},
cancel: function (res) {
//alert('已取消');
},
fail: function (res) {
//alert(JSON.stringify(res));
}
});
});
};
var wxError = function () {
wx.error(function (res) {
alert(res.errMsg);
});
};
return {
init: _init
};
})();
//执行init
wxShare.init();
==== AwardWxShareController.java ====
微信要求,生成签名,需要在服务端生成,所以这个服务端类的用途是用于生成签名。
逻辑如下:
- 如果缓存中有ticket,而且没有过期,则直接用ticket
- 否则获取token
- 再根据token获取ticket
- 设置ticket缓存,设置失效的毫秒数,当前时间+7200-100秒,以免正好差个几秒钟的误差
- 生成签名,返回前端
/*
* @(#)GateInfoAction.java 1.0 2014-7-25下午4:17:49
*
* 德邦证券股份有限公司
* Copyright (c) 2012-2014 Tebon, Inc. All rights reserved.
*/
package com.tebon.trans.web.award;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import javax.servlet.http.HttpServletRequest;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import com.tebon.sirius.cache.CacheFactoryProvider;
import com.tebon.sirius.cache.ICache.CacheType;
import com.tebon.sirius.utils.HttpClientUtils;
import com.tebon.sirius.utils.JSonUtils;
import com.tebon.sirius.utils.RandomGUIDUtils;
import com.tebon.trans.web.BaseAbstractController;
/**
* 大转盘抽奖接口相关功能
*
* @author liyb
*/
@Controller
@RequestMapping("/award/wxShare/")
public class AwardWxShareController extends BaseAbstractController {
/**
* 放redis缓存微信分享ticket 并且设置失效时间(毫秒数)
*/
private static final String REDIS_WX_SHARE_TICKET = "redis.wx.share.ticket";
private static final String REDIS_WX_SHARE_TICKET_DEAD_DATETIME = "redis.wx.share.ticket.dead.datetime";
@Autowired
CacheFactoryProvider cacheFactoryProvider;
/**
* 微信分享生成签名逻辑
* @return
*/
@RequestMapping(value = "/wxShareSign")
public @ResponseBody Map testWxShareSign(HttpServletRequest request) throws Exception {
/**
* 微信分享生成签名逻辑
* 0.准备结果MAP
*/
Map map = new HashMap();
/**
* 1.如果缓存中有ticket,而且没有过期,则直接用ticket
*/
String ticket = (String)cacheFactoryProvider.getCacheProvider(CacheType.REDIES).get(REDIS_WX_SHARE_TICKET);
Long deadDateTime = (Long)cacheFactoryProvider.getCacheProvider(CacheType.REDIES).get(REDIS_WX_SHARE_TICKET_DEAD_DATETIME);
log.info("从redis获取ticket:" + ticket);
if(StringUtils.isBlank(ticket) || new Date().getTime() > deadDateTime.longValue()){
/**
* 2.否则获取token
* https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid=wx2767b3767363f90e&secret=e42c1933553c1da62187d76379284fd1
* {
* "access_token": "E-zdrtVh4qgrNO5n0f2NWmM0z0_XxYey_2X6jHaiDFp7SQl-sMK4aoZT1FDAKO-_U9EF2h7pHe7i0WIuKsVp8RA9qaN7n-zZ9wJx91PB-RMARLdAIALCW",
* "expires_in": 7200
* }
*/
String url = "https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid=wx2767b3767363f90e&secret=e42c1933553c1da62187d76379284fd1";
String jsonString = HttpClientUtils.getWebContentByGet(url);
log.info("获取access_token返回:" + jsonString);
WxShareResponse wxShareResponse = JSonUtils.nonDefaultMapper().fromJson(jsonString, WxShareResponse.class);
if(StringUtils.isBlank(wxShareResponse.getAccess_token())){
map.put("result", "false");
map.put("message", "微信分享准备失败!");
return map;
}
/**
* 3.再根据token获取ticket
* https://api.weixin.qq.com/cgi-bin/ticket/getticket?access_token=E-zdrtVh4qgrNO5n0f2NWmM0z0_XxYey_2X6jHaiDFp7SQl-sMK4aoZT1FDAKO-_U9EF2h7pHe7i0WIuKsVp8RA9qaN7n-zZ9wJx91PB-RMARLdAIALCW&type=jsapi
* {
* "errcode": 0,
* "errmsg": "ok",
* "ticket": "sM4AOVdWfPE4DxkXGEs8VDqK83ldYtSiGPegbeUXAElohlSKVPXjtbn6JqV5T2Nta1pqIl2b-uh1sapcge3SfQ",
* "expires_in": 7200
* }
*/
url = "https://api.weixin.qq.com/cgi-bin/ticket/getticket?access_token=" + wxShareResponse.getAccess_token() + "&type=jsapi";
jsonString = HttpClientUtils.getWebContentByGet(url);
log.info("获取ticket返回:" + jsonString);
wxShareResponse = JSonUtils.nonDefaultMapper().fromJson(jsonString, WxShareResponse.class);
if(StringUtils.isBlank(wxShareResponse.getTicket())){
map.put("result", "false");
map.put("message", "微信分享准备失败!");
return map;
}
/**
* 4.设置ticket缓存,设置失效的毫秒数,当前时间+7200-100秒,以免正好差个几秒钟的误差
*/
ticket = wxShareResponse.getTicket();
long expiresIn = wxShareResponse.getExpires_in();
cacheFactoryProvider.getCacheProvider(CacheType.REDIES).put(REDIS_WX_SHARE_TICKET, ticket);
cacheFactoryProvider.getCacheProvider(CacheType.REDIES).put(REDIS_WX_SHARE_TICKET_DEAD_DATETIME, new Date().getTime() + (expiresIn - 100)*1000);
}
/**
* 5.生成签名,返回前端
*/
long timestamp = new Date().getTime();
String appid = request.getParameter("appid");
String url = request.getParameter("url");
String noncestr = new RandomGUIDUtils().toString();
log.info("timestamp=" + timestamp + ",appid=" + appid + ",url=" + url + ",noncestr=" + noncestr + ",ticket=" + ticket);
String signString = "jsapi_ticket=" + ticket + "&noncestr=" + noncestr + "×tamp=" + timestamp + "&url=" + url;
String sign = sha1Digest(signString);
map.put("result", "true");
map.put("timestamp", StringUtils.EMPTY + timestamp);
map.put("noncestr", noncestr);
map.put("signature", sign);
return map;
}
public String sha1Digest(String signString) {
try {
MessageDigest digest = java.security.MessageDigest
.getInstance("SHA");
digest.update(signString.getBytes());
byte messageDigest[] = digest.digest();
// Create Hex String
StringBuffer hexString = new StringBuffer();
// 字节数组转换为 十六进制 数
for (int i = 0; i < messageDigest.length; i++) {
String shaHex = Integer.toHexString(messageDigest[i] & 0xFF);
if (shaHex.length() < 2) {
hexString.append(0);
}
hexString.append(shaHex);
}
return hexString.toString();
} catch (NoSuchAlgorithmException e) {
e.printStackTrace();
}
return "";
}
}
/**
*
* - Title:
* -
* none
*
* - Description:微信返回数据对象
* -
*
none
*
*
*
* @author Administrator
* @version 1.0, 2016年1月28日
* @since trans
*
*/
class WxShareResponse {
String access_token;
long expires_in;
String errcode;
String errmsg;
String ticket;
public String getAccess_token() {
return access_token;
}
public void setAccess_token(String access_token) {
this.access_token = access_token;
}
public long getExpires_in() {
return expires_in;
}
public void setExpires_in(long expires_in) {
this.expires_in = expires_in;
}
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 getTicket() {
return ticket;
}
public void setTicket(String ticket) {
this.ticket = ticket;
}
}
===== 分享页开发 =====
需要分享的页面的开发,按道理整个活动过程中的每个页面,都要按如下方法来开发,达到自定义分享【抬头+描述+图片+链接】的效果。
在页面的head头部加入以下代码
===== 效果 =====
==== 分享给朋友 ====
{{:分享:技术:微信:3.jpg?400|}}
{{:分享:技术:微信:2.jpg?400|}}
==== 分享到朋友圈 ====
{{:分享:技术:微信:4.jpg?400|}}
{{:分享:技术:微信:5.jpg?400|}}