一、前言
关于微信自定义分享,在微信官方的公众平台上有详细的介绍,
链接 : https://mp.weixin.qq.com/wiki?t=resource/res_main&id=mp1421141115&token=&lang=zh_CN 点击打开链接
二、调用接口思路
引用官方的一句话:微信公众平台是运营者通过公众号为微信用户提供资讯和服务的平台,而公众平台开发接口则是提供服务的基础,开发者在公众平台网站中创建公众号、获取接口权限后,可以通过阅读本接口文档来帮助开发。 所以对于微信开发我们都要调用他的接口。
为了调用自定义分享接口,首先需要获取SDK签名,签名是接口中的必要参数,而签名来自jsapi票据,票据又必须通过access_token获取,access_token需要appId和secrect得到。关系图如下:
其中每个名词的意义为:
appId: 微信公众平台服务号才具有
secrect: 微信公众平台服务号才具有
access_token: access_token是公众号的全局唯一票据,公众号调用各接口时都需使用access_token。开发者需要进行妥善保存。access_token的存 储至少要保留512个字符空间。access_token的有效期目前为2个小时,需定时刷新,重复获取将导致上次获取的access_token失效。
String get_jssdk_access_token_url = "https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid=APPID&secret=APPSECRET";
jsapi_ticket: jsapi_ticket是公众号用于调用微信JS接口的临时票据,通过access_token来获取(所以同access_token一样,有效期为2个小时, 最好缓存起来)
// 获得jsapi_ticket 有效期7200秒
String get_jsapi_ticket_url = "https://api.weixin.qq.com/cgi-bin/ticket/getticket?access_token=ACCESS_TOKEN&type=jsapi";
signature: config中需要用到签名的多个属性,获取签名
Map<String, String> signObj = Sign.sign(jsapi_ticket, basePath1);
request.setAttribute("signObj", signObj);
三、具体代码实现:
1、通过appId和secrect获取access_token ,再获取ticket
/**
* 投票活动业务逻辑处理
*/
@Controller("WechatVoteAction")
@Scope("prototype")
public class WechatVoteAction extends BaseAction {
//测试用
// private static String APPID ="xxxxxxxxxxxx";
// private static String SECRET = "xxxxxxxxxxxxxxxxxxxxx";
..........//省略代码
/**
* 获取jsapi票据
*/
private void genTicket() {
try {
// 取得用于生成JS-SDK签名的ticket,先从缓存中读取(缓存有效期7200秒,为防止误差程序设置失效时间为3600秒)
String cache_jsapi_ticket = MemcacheUtil.get(MemcacheUtil.WECHAT_JSAPI_TICKET_STR);
// 若缓存有数据且未超时则直接设置到request中,
// 若超时或无数据则重新调用微信API进行获取之后设置到request中并且进行缓存及记录缓存时间
if (StringUtil.isNotEmpty(cache_jsapi_ticket)) {
getRequest().setAttribute("jsapi_ticket", cache_jsapi_ticket);
} else {
// 获得jsapi_ticket 有效期7200秒
String get_jsapi_ticket_url = "https://api.weixin.qq.com/cgi-bin/ticket/getticket?access_token=ACCESS_TOKEN&type=jsapi";
String access_token = getTokenStr();
get_jsapi_ticket_url = get_jsapi_ticket_url.replace("ACCESS_TOKEN", access_token);
String ticketJson = new String(HttpUtil.doGet(get_jsapi_ticket_url), "UTF-8");
// 解析返回数据
JSONObject jsonObject2 = JSONObject.fromObject(ticketJson);
cache_jsapi_ticket = jsonObject2.getString("ticket");
// 设置缓存数据3600秒过期
MemcacheUtil.set(MemcacheUtil.WECHAT_JSAPI_TICKET_STR, 3600, cache_jsapi_ticket);
getRequest().setAttribute("jsapi_ticket", cache_jsapi_ticket);
}
} catch (Exception e) {
logger.error("获取jsapi_ticket时出现异常!!!");
logger.error(e.getMessage());
e.printStackTrace();
}
}
/**
* 描述: 获取access_token
*/
public String getTokenStr(){
try {
String cache_access_token = MemcacheUtil.get(MemcacheUtil.WECHAT_ACCESS_TOKEN_STR);
if (StringUtil.isNotEmpty(cache_access_token)) {
return cache_access_token;
}else {
String get_jssdk_access_token_url = "https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid=APPID&secret=APPSECRET";
// 发送请求
get_jssdk_access_token_url = get_jssdk_access_token_url.replace("APPID", APPID);
get_jssdk_access_token_url = get_jssdk_access_token_url.replace("APPSECRET", SECRET);
String accessTokenJson = new String(HttpUtil.doGet(get_jssdk_access_token_url), "UTF-8");
// 解析返回数据
JSONObject jsonObject = JSONObject.fromObject(accessTokenJson);
cache_access_token = jsonObject.getString("access_token");
MemcacheUtil.set(MemcacheUtil.WECHAT_ACCESS_TOKEN_STR, 3600, cache_access_token);
return cache_access_token;
}
} catch (Exception e) {
logger.error("获取access_token时出现异常!!!");
logger.error(e.getMessage());
e.printStackTrace();
return "";
}
}
}
2、通过ticket获取signature
public class Sign {
@SuppressWarnings("rawtypes")
public static void main(String[] args) {
String jsapi_ticket = "jsapi_ticket";
// 注意 URL 一定要动态获取,不能 hardcode
String url = "http://example.com";
Map<String, String> ret = sign(jsapi_ticket, url);
for (Map.Entry entry : ret.entrySet()) {
System.out.println(entry.getKey() + ", " + entry.getValue());
}
};
public static Map<String, String> sign(String jsapi_ticket, String url) {
Map<String, String> ret = new HashMap<String, String>();
String nonce_str = create_nonce_str();
String timestamp = create_timestamp();
String string1;
String signature = "";
//注意这里参数名必须全部小写,且必须有序
string1 = "jsapi_ticket=" + jsapi_ticket +
"&noncestr=" + nonce_str +
"×tamp=" + timestamp +
"&url=" + url;
System.out.println(string1);
try {
MessageDigest crypt = MessageDigest.getInstance("SHA-1");
crypt.reset();
crypt.update(string1.getBytes("UTF-8"));
signature = byteToHex(crypt.digest());
} catch (NoSuchAlgorithmException e){
e.printStackTrace();
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
}
ret.put("url", url);
ret.put("jsapi_ticket", jsapi_ticket);
ret.put("nonceStr", nonce_str);
ret.put("timestamp", timestamp);
ret.put("signature", signature);
return ret;
}
private static String byteToHex(final byte[] hash) {
Formatter formatter = new Formatter();
for (byte b : hash) {
formatter.format("%02x", b);
}
String result = formatter.toString();
formatter.close();
return result;
}
private static String create_nonce_str() {
return UUID.randomUUID().toString();
}
private static String create_timestamp() {
return Long.toString(System.currentTimeMillis() / 1000);
}
}
3、在jsp页面调用接口
<%
String basePath1 = request.getScheme()+"://"+request.getServerName()+ request.getContextPath()+"/";
if (StringUtil.isEmpty(request.getQueryString())) {
basePath1 += "wechat/vote/index.htm";
} else {
basePath1 += "wechat/vote/index.htm" + "?" + request.getQueryString();
}
%>
<!DOCTYPE html>
<html>
<head>
<%-- 引用微信js-sdk相关处理 --%>
<jsp:include page="/WEB-INF/pages/topic/wechat_vote/wechat_js_sdk.jsp">
<jsp:param name="basePath1" value="<%=basePath1%>"/>
</jsp:include>
</head>
.....
</html>
4、提取出来的接口处理
<%@page import="com.clbus.common.util.StringUtil"%>
<%@page import="com.clbus.web.Sign"%>
<%@page import="java.util.Map"%>
<%@page import="java.util.*"%>
<%@page pageEncoding="UTF-8"%>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core"%>
<%@ taglib prefix="fn" uri="http://java.sun.com/jsp/jstl/functions"%>
<script src="http://res.wx.qq.com/open/js/jweixin-1.0.0.js"></script>
<%
String serverPort = "";
if (request.getServerPort() != 80) {
serverPort = ":" + request.getServerPort();
}
String serverName = request.getServerName();
String basePath = request.getScheme() + "://" + serverName
+ serverPort + request.getContextPath() + "/";
request.setAttribute("basePath", basePath);
%>
<script type="text/javascript">
var basePath = "<%=basePath%>";
<%
String basePath1 = request.getParameter("basePath1");
String jsapi_ticket = String.valueOf(request.getAttribute("jsapi_ticket"));
Map<String, String> signObj = Sign.sign(jsapi_ticket, basePath1);
request.setAttribute("signObj", signObj);
%>
wx.config({
// 开启调试模式,调用的所有api的返回值会在客户端alert出来,若要查看传入的参数,可以在pc端打开,参数信息会通过log打出,仅在pc端时才会打印。
debug: false,
appId: 'wx69ac9c3faf07df50', // 必填,公众号的唯一标识
timestamp: '${signObj.timestamp}', // 必填,生成签名的时间戳
nonceStr: '${signObj.nonceStr}', // 必填,生成签名的随机串
signature: '${signObj.signature}',// 必填,签名,见附录1
jsApiList: ['onMenuShareAppMessage', 'onMenuShareTimeline'] // 必填,需要使用的JS接口列表,所有JS接口列表见附录2
});
wx.ready(function(){
// config信息验证后会执行ready方法,所有接口调用都必须在config接口获得结果之后,
// config是一个客户端的异步操作,所以如果需要在页面加载时就调用相关接口,则须把相关接口放在ready函数中调用来确保正确执行。.
// 对于用户触发时才调用的接口,则可以直接调用,不需要放在ready函数中。
wx.onMenuShareTimeline({
title: '分享到朋友圈!大家参与赢大奖!',
link: '<%=basePath%>wechat/vote/index.htm',
imgUrl: '<%=basePath%>static/topic/wechat_vote/images/OP.jpg',
success: function () {},
cancel: function () {}
});
wx.onMenuShareAppMessage({
title: '分享给好友!不能只便宜你一个人!',
desc: '我的青春我做主!小伙伴们快来帮我投一票!',
link: '<%=basePath%>wechat/vote/index.htm',
imgUrl: '<%=basePath%>static/topic/wechat_vote/images/OP.jpg',
type: 'link',
dataUrl: '',
success: function () {},
cancel: function () {}
});
});
</script>