一、前言
關於微信自定義分享,在微信官方的公衆平臺上有詳細的介紹,
鏈接 : 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>