自定義分享是微信分享的自我定製化實現,需要了解JSSDK的開發規範
一、自定義分享簡介
先看官方文檔
微信JS-SDK是微信公衆平臺面向網頁開發者提供的基於微信內的網頁開發工具包。
通過使用微信JS-SDK,網頁開發者可藉助微信高效地使用拍照、選圖、語音、位置等手機系統的能力,同時可以直接使用微信分享、掃一掃、卡券、支付等微信特有的能力,爲微信用戶提供更優質的網頁體驗。
此文檔面向網頁開發者介紹微信JS-SDK如何使用及相關注意事項。步驟一:綁定域名
先登錄微信公衆平臺進入“公衆號設置”的“功能設置”裏填寫“JS接口安全域名”。
備註:登錄後可在“開發者中心”查看對應的接口權限。步驟二:引入JS文件
在需要調用JS接口的頁面引入如下JS文件,(支持https):
http://res.wx.qq.com/open/js/jweixin-1.2.0.js
備註:支持使用 AMD/CMD 標準模塊加載方法加載步驟三:通過config接口注入權限驗證配置
所有需要使用JS-SDK的頁面必須先注入配置信息,否則將無法調用(同一個url僅需調用一次,對於變化url的SPA的web app可在每次url變化時進行調用,目前Android微信客戶端不支持pushState的H5新特性,所以使用pushState來實現web app的頁面會導致簽名失敗,此問題會在Android6.2中修復)。
wx.config({
debug: true, // 開啓調試模式,調用的所有api的返回值會在客戶端alert出來,若要查看傳入的參數,可以在pc端打開,參數信息會通過log打出,僅在pc端時纔會打印。
appId: ”, // 必填,公衆號的唯一標識
timestamp: , // 必填,生成簽名的時間戳
nonceStr: ”, // 必填,生成簽名的隨機串
signature: ”,// 必填,簽名,見附錄1
jsApiList: [] // 必填,需要使用的JS接口列表,所有JS接口列表見附錄2
});步驟四:通過ready接口處理成功驗證
wx.ready(function(){
// config信息驗證後會執行ready方法,所有接口調用都必須在config接口獲得結果之後,config是一個客戶端的異步操作,所以如果需要在頁面加載時就調用相關接口,則須把相關接口放在ready函數中調用來確保正確執行。對於用戶觸發時才調用的接口,則可以直接調用,不需要放在ready函數中。
});步驟五:通過error接口處理失敗驗證
wx.error(function(res){
// config信息驗證失敗會執行error函數,如簽名過期導致驗證失敗,具體錯誤信息可以打開config的debug模式查看,也可以在返回的res參數中查看,對於SPA可以在這裏更新簽名。
});接口調用說明
所有接口通過wx對象(也可使用jWeixin對象)來調用,參數是一個對象,除了每個接口本身需要傳的參數之外,還有以下通用參數:
1.success:接口調用成功時執行的回調函數。
2.fail:接口調用失敗時執行的回調函數。
3.complete:接口調用完成時執行的回調函數,無論成功或失敗都會執行。
4.cancel:用戶點擊取消時的回調函數,僅部分有用戶取消操作的api纔會用到。
5.trigger: 監聽Menu中的按鈕點擊時觸發的方法,該方法僅支持Menu中的相關接口。
備註:不要嘗試在trigger中使用ajax異步請求修改本次分享的內容,因爲客戶端分享操作是一個同步操作,這時候使用ajax的回包會還沒有返回。以上幾個函數都帶有一個參數,類型爲對象,其中除了每個接口本身返回的數據之外,還有一個通用屬性errMsg,其值格式如下:
調用成功時:”xxx:ok” ,其中xxx爲調用的接口名
用戶取消時:”xxx:cancel”,其中xxx爲調用的接口名
調用失敗時:其值爲具體錯誤信息
基礎接口
判斷當前客戶端版本是否支持指定JS接口
wx.checkJsApi({
jsApiList: [‘chooseImage’], // 需要檢測的JS接口列表,所有JS接口列表見附錄2,
success: function(res) {
// 以鍵值對的形式返回,可用的api值true,不可用爲false
// 如:{“checkResult”:{“chooseImage”:true},”errMsg”:”checkJsApi:ok”}
}
});
備註:checkJsApi接口是客戶端6.0.2新引入的一個預留接口,第一期開放的接口均可不使用checkJsApi來檢測。分享接口
請注意不要有誘導分享等違規行爲,對於誘導分享行爲將永久回收公衆號接口權限,詳細規則請查看:朋友圈管理常見問題 。獲取“分享到朋友圈”按鈕點擊狀態及自定義分享內容接口
wx.onMenuShareTimeline({
title: ”, // 分享標題
link: ”, // 分享鏈接,該鏈接域名或路徑必須與當前頁面對應的公衆號JS安全域名一致
imgUrl: ”, // 分享圖標
success: function () {
// 用戶確認分享後執行的回調函數
},
cancel: function () {
// 用戶取消分享後執行的回調函數
}
});獲取“分享給朋友”按鈕點擊狀態及自定義分享內容接口
wx.onMenuShareAppMessage({
title: ”, // 分享標題
desc: ”, // 分享描述
link: ”, // 分享鏈接,該鏈接域名或路徑必須與當前頁面對應的公衆號JS安全域名一致
imgUrl: ”, // 分享圖標
type: ”, // 分享類型,music、video或link,不填默認爲link
dataUrl: ”, // 如果type是music或video,則要提供數據鏈接,默認爲空
success: function () {
// 用戶確認分享後執行的回調函數
},
cancel: function () {
// 用戶取消分享後執行的回調函數
}
});
以上是官方文檔的描述,前提條件有兩個(1)配置js安全域名;(2)在使用分享的頁面中引入js文件,有了這是前提條件,我們就可以實現下面的開發操作了。
二、開發
先定義一個分享的bean保存可能用到的分享字段,bean定義名字爲WechatJSShareBean:
package com.wtp.wechat.bean;
/**
* @ClassName: WechatJSShareBean
* @Description: 微信分享 微信好友/朋友圈分享...模型
* @author tianpengw
* @date 2017年10月11日 上午10:49:31
*
*/
public class WechatJSShareBean {
/**
* 微信公衆號ID
*/
private String appId;
/**
* 時間戳
*/
private String timestamp;
/**
* 指定長度的隨機字符串
*/
private String nonceStr;
/**
* 簽名
*/
private String signature;
/**
* 分享標題
*/
private String title;
/**
* 分享描述 【分享給朋友有此字段】
*/
private String desc;
/**
* 分享鏈接,該鏈接域名或路徑必須與當前頁面對應的公衆號JS安全域名一致
*/
private String link;
/**
* 分享圖標
*/
private String imgUrl;
/**
* 分享類型 music、video或link,不填默認爲link 【分享給朋友有此字段】
*/
private String type;
/**
* 如果type是music或video,則要提供數據鏈接,默認爲空 【分享給朋友有此字段】
*/
private String dataUrl;
public String getAppId() {
return appId;
}
public void setAppId(String appId) {
this.appId = appId;
}
public String getTimestamp() {
return timestamp;
}
public void setTimestamp(String timestamp) {
this.timestamp = timestamp;
}
public String getNonceStr() {
return nonceStr;
}
public void setNonceStr(String nonceStr) {
this.nonceStr = nonceStr;
}
public String getSignature() {
return signature;
}
public void setSignature(String signature) {
this.signature = signature;
}
public String getTitle() {
return title;
}
public void setTitle(String title) {
this.title = title;
}
public String getDesc() {
return desc;
}
public void setDesc(String desc) {
this.desc = desc;
}
public String getLink() {
return link;
}
public void setLink(String link) {
this.link = link;
}
public String getImgUrl() {
return imgUrl;
}
public void setImgUrl(String imgUrl) {
this.imgUrl = imgUrl;
}
public String getType() {
return type;
}
public void setType(String type) {
this.type = type;
}
public String getDataUrl() {
return dataUrl;
}
public void setDataUrl(String dataUrl) {
this.dataUrl = dataUrl;
}
}
不用多講,這些字段都提取自兩類分享所需的數據裏,再看下前提展示
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>
<script src="https://res.wx.qq.com/open/js/jweixin-1.2.0.js"> </script>
<script type="text/javascript">
wx.config({
debug: false, // 開啓調試模式,調用的所有api的返回值會在客戶端alert出來,若要查看傳入的參數,可以在pc端打開,參數信息會通過log打出,僅在pc端時纔會打印。
appId: '${commonShare.appId}', //必填,公衆號的唯一標識
timestamp: '${commonShare.timestamp}', //必填,生成簽名的時間戳
nonceStr: '${commonShare.nonceStr}', // 必填,生成簽名的隨機串
signature: '${commonShare.signature}', // 必填,簽名,見附錄1
jsApiList: [ // 必填,需要使用的JS接口列表,所有JS接口列表見附錄2
'checkJsApi',
'onMenuShareTimeline',
'onMenuShareAppMessage'
]
});
wx.ready(function(){
// config信息驗證後會執行ready方法,所有接口調用都必須在config接口獲得結果之後,config是一個客戶端的異步操作,所以如果需要在頁面加載時就調用相關接口,則須把相關接口放在ready函數中調用來確保正確執行。對於用戶觸發時才調用的接口,則可以直接調用,不需要放在ready函數中。
wx.checkJsApi({
jsApiList: [
'onMenuShareTimeline',
'onMenuShareAppMessage'
],
success: function (res) {
//alert(JSON.stringify(res));
}
});
// 監聽“分享給朋友”,按鈕點擊、自定義分享內容及分享結果接口
wx.onMenuShareAppMessage({
title: '${commonShare.title}', // 分享標題
desc: '${commonShare.desc}', // 分享描述
link: '${commonShare.link}', // 分享鏈接
imgUrl: '${commonShare.imgUrl}', // 分享圖標
type: 'link', // 分享類型,music、video或link,不填默認爲link
dataUrl: '', // 如果type是music或video,則要提供數據鏈接,默認爲空
trigger: function (res) {
// 不要嘗試在trigger中使用ajax異步請求修改本次分享的內容,因爲客戶端分享操作是一個同步操作,這時候使用ajax的回包會還沒有返回
//alert('用戶點擊發送給朋友');
},
success: function (res) {
//alert('已分享');
},
cancel: function (res) {
//alert('已取消');
},
fail: function (res) {
//alert(JSON.stringify(res));
}
});
// 監聽“分享到朋友圈”按鈕點擊、自定義分享內容及分享結果接口
wx.onMenuShareTimeline({
title: '${commonShare.title}', // 分享標題
link: '${commonShare.link}', // 分享鏈接
imgUrl: '${commonShare.imgUrl}', // 分享圖標
trigger: function (res) {
// 不要嘗試在trigger中使用ajax異步請求修改本次分享的內容,因爲客戶端分享操作是一個同步操作,這時候使用ajax的回包會還沒有返回
//alert('用戶點擊分享到朋友圈');
},
success: function (res) {
//alert('已分享');
},
cancel: function (res) {
//alert('已取消');
},
fail: function (res) {
//alert(JSON.stringify(res));
}
});
});
wx.error(function(res){
// config信息驗證失敗會執行error函數,如簽名過期導致驗證失敗,具體錯誤信息可以打開config的debug模式查看,也可以在返回的res參數中查看,對於SPA可以在這裏更新簽名。
jQAlert('微信分享異常:'+res);
});
</script>
這裏的commonShare就是WechatJSShareBean對象實例化的內容,我將此代碼生成單獨jsp文件(名爲wechatShare.jsp),在需要的時候調用它,部分代碼如下:
<c:if test="${isWechat }">
<%@ include file="wechatShare.jsp"%>
</c:if>
再來看下後臺實現代碼片段:
if (HttpHelper.isWechatBrowser(req)) {// 是微信瀏覽器
WechatJSShareBean wjsb = new WechatJSShareBean();
String title = MyConstants.project_title;
wjsb.setTitle(title);
wjsb.setImgUrl(p.getProductImgStr());
wjsb.setDesc(p.getProductName());
WechatUtil.wechatShareConfig(wjsb, req);
mv.addObject("commonShare", wjsb);
mv.addObject("isWechat", true);
}
判斷是否微信瀏覽器如果是且有自定義分享需求則初始化分享bean,最後返回到前臺頁面,讓我們在回顧下這個wechatShareConfig方法(在公衆號支付那章我已經展示過代碼,不過沒有講解)
/**
*
* @Description: 獲取微信jsapi_ticket
* @author tianpengw
* @return
*/
private static JSAPITicket getJsApiTicket(){
JSAPITicket ticket = new JSAPITicket();
BaseAccessToken token = getBaseToken();
if(wechatCacheMap.containsKey("jsApiTicket") && null != wechatCacheMap.get("jsApiTicket")){
Long expireTime = (Long) wechatCacheMap.get("ticketExpireMillis");
if(new Date().getTime() <= expireTime){//ticket 仍然有效
ticket.setTicket((String)wechatCacheMap.get("jsApiTicket"));
ticket.setErrcode("0");
ticket.setErrmsg("ok");
ticket.setExpires_in(7200);
return ticket;
}
}
String url = jsApiTicketUrl.replace("ACCESS_TOKEN", token.getAccess_token());
String ticketResp = HttpHelper.httpsRequest(url, "POST", null);
if(!CommonUtil.isEmpty(ticketResp)){
Gson json=new Gson();
ticket = json.fromJson(ticketResp,JSAPITicket.class);
if(!"".equals(token.getAccess_token())){
wechatCacheMap.put("jsApiTicket", ticket.getTicket());
wechatCacheMap.put("ticketExpireMillis", (new Date().getTime() + expiresInMillisecond));
}
}
return ticket;
}
/**
*
* @Description: 微信自定義分享配置
* @author tianpengw
* @param commonShare
* @param req
*/
public static void wechatShareConfig(WechatJSShareBean commonShare, HttpServletRequest req){
String timestamp = Long.toString(System.currentTimeMillis() / 1000);
commonShare.setAppId(WechatUtil.appid);
commonShare.setTimestamp(timestamp);//獲取時間戳
commonShare.setNonceStr(CommonUtil.getUUID());//獲取指定長度的隨機字符串
commonShare.setLink(HttpHelper.getFullRequestUrl(req));//簽名用的url必須是調用JS接口頁面的完整URL
JSAPITicket ticket = getJsApiTicket();
Map<String, String> params = new HashMap<String, String>();
params.put("noncestr", commonShare.getNonceStr());
params.put("timestamp", commonShare.getTimestamp());
params.put("url", HttpHelper.getFullRequestUrl(req));//簽名用的url必須是調用JS接口頁面的完整URL
params.put("jsapi_ticket", ticket.getTicket());
commonShare.setSignature(SignatureUtil.signatures(params,"SHA1"));
}
獲得全地址工具方法
/**
*
* @Description: 根據req對象獲得當前請求地址,帶參數
* @author tianpengw
* @param req
* @return
*/
public static String getFullRequestUrl(HttpServletRequest req){
String url = req.getRequestURL().toString();
String queryStr = req.getQueryString();
if(CommonUtil.isEmpty(queryStr)){
return url;
}
return url + "?" + queryStr;
}
簽名工具方法
/**
*
* @Description: 微信簽名算法
* @author tianpengw
* @param params 參數格式
* @param signatureType 加密類型 SHA1/MD5,默認MD5
* @return
*/
public static String signatures(Map<String, String> params, String signatureType){
String str="";
try {
List<String> paramsStr = new ArrayList<String>();
for (String key : params.keySet()) {
paramsStr.add(key);
}
Collections.sort(paramsStr);
StringBuilder sbff = new StringBuilder();
for (String kk : paramsStr) {
String value = params.get(kk);
if (CommonUtil.isEmpty(sbff.toString())) {
sbff.append(kk + "=" + value);
} else {
sbff.append("&" + kk + "=" + value);
}
}
if("SHA1".equals(signatureType)){
str = SHA1(sbff.toString());
}else{
str = MD5(sbff.toString());
}
} catch (Exception e) {
e.printStackTrace();
}
return str;
}
好了代碼寫完了,開始介紹關鍵字段,先看下官方文檔吧
附錄1-JS-SDK使用權限簽名算法
jsapi_ticket
生成簽名之前必須先了解一下jsapi_ticket,jsapi_ticket是公衆號用於調用微信JS接口的臨時票據。正常情況下,jsapi_ticket的有效期爲7200秒,通過access_token來獲取。由於獲取jsapi_ticket的api調用次數非常有限,頻繁刷新jsapi_ticket會導致api調用受限,影響自身業務,開發者必須在自己的服務全局緩存jsapi_ticket 。
1.參考以下文檔獲取access_token(有效期7200秒,開發者必須在自己的服務全局緩存access_token):../15/54ce45d8d30b6bf6758f68d2e95bc627.html
2.用第一步拿到的access_token 採用http GET方式請求獲得jsapi_ticket(有效期7200秒,開發者必須在自己的服務全局緩存jsapi_ticket):https://api.weixin.qq.com/cgi-bin/ticket/getticket?access_token=ACCESS_TOKEN&type=jsapi
成功返回如下JSON:
{
“errcode”:0,
“errmsg”:”ok”,
“ticket”:”bxLdikRXVbTPdHSM05e5u5sUoXNKd8-41ZO3MhKoyN5OfkWITDGgnr2fwJ0m9E8NYzWKVZvdVtaUgWvsdshFKA”,
“expires_in”:7200
}
獲得jsapi_ticket之後,就可以生成JS-SDK權限驗證的簽名了。簽名算法
簽名生成規則如下:參與簽名的字段包括noncestr(隨機字符串), 有效的jsapi_ticket, timestamp(時間戳), url(當前網頁的URL,不包含#及其後面部分) 。對所有待簽名參數按照字段名的ASCII 碼從小到大排序(字典序)後,使用URL鍵值對的格式(即key1=value1&key2=value2…)拼接成字符串string1。這裏需要注意的是所有參數名均爲小寫字符。對string1作sha1加密,字段名和字段值都採用原始值,不進行URL 轉義。
即signature=sha1(string1)。 示例:
noncestr=Wm3WZYTPz0wzccnW
jsapi_ticket=sM4AOVdWfPE4DxkXGEs8VMCPGGVi4C3VM0P37wVUCFvkVAy_90u5h9nbSlYy3-Sl-HhTdfl2fzFy1AOcHKP7qg
timestamp=1414587457
url=http://mp.weixin.qq.com?params=value步驟1. 對所有待簽名參數按照字段名的ASCII 碼從小到大排序(字典序)後,使用URL鍵值對的格式(即key1=value1&key2=value2…)拼接成字符串string1:
jsapi_ticket=sM4AOVdWfPE4DxkXGEs8VMCPGGVi4C3VM0P37wVUCFvkVAy_90u5h9nbSlYy3-Sl-HhTdfl2fzFy1AOcHKP7qg&noncestr=Wm3WZYTPz0wzccnW×tamp=1414587457&url=http://mp.weixin.qq.com?params=value步驟2. 對string1進行sha1簽名,得到signature:
0f9de62fce790f9a083d5c99e95740ceb90c27ed
注意事項
1.簽名用的noncestr和timestamp必須與wx.config中的nonceStr和timestamp相同。
2.簽名用的url必須是調用JS接口頁面的完整URL。
3.出於安全考慮,開發者必須在服務器端實現簽名的邏輯。
如出現invalid signature 等錯誤詳見附錄5常見錯誤及解決辦法。
上面提到兩個關鍵的地方 1)jsapi_ticket的獲取方法;2)sign簽名方式需採用SHA1方式。對應到上面我貼出的代碼片段,相信有Java基礎的網友們肯定一目瞭然,通過以上的配置,我們將實現自定義微信分享,至於微信提供的其他的一批批接口,按照API的提示,相信你也都能完全實現的
至此微信自定義分享的方法介紹完畢,希望你能從其中獲得收益。下一張將詳細展示下我整理的jar包,如果感興趣的網友,請繼續閱讀