使用JS-SDK自定義微信分享效果
之前做的一個h5頁面,按照需求得分享到朋友圈he好友,預設分享連結的標題he內容以及圖示都是微信預設的,下面是一個大神進行自定義的分享連結的程式碼,看到了記錄下:
前言
剛進入一家新公司,接到的第一個任務就是需要需要自定義微信分享的效果(自定義縮圖,標題,摘要),一開始真是一臉懵逼,在網上搜索了半天之後大概有了方案。值得注意的是一開始搜尋到的解決方案全是呼叫微信的自帶的JS-SDK,然而騰訊是不會讓廣大吃瓜群眾這麼輕而易舉的呼叫他們的東西的。微信開發團隊已經把呼叫的許可權收回,現在無法直接在頁面直接呼叫JS-SDK了。話不多說,直接上乾貨。
預期效果
原始的分享效果:
使用微信JS-SDK的分享效果:
可以看出縮圖,標題,摘要樣式良好,給使用者的體驗很好。
準備工作
微信官方開發者文件地址:https://mp.weixin.qq.com/wiki?t=resource/res_main&id=mp1421141115
現在的思路已經很明確了,就是通過呼叫微信的JS-SDK實現自定義分享效果。但是這個呼叫過程比較繁瑣,需要提前準備如下東西:
(1)微信服務號一個,並且已經通過了實名認證;
沒有實名認證的話,一些介面沒有呼叫許可權。
(2)一個ICP備案的域名;
這個域名需要設定為微信公眾號後臺的JS介面安全域名,否則微信仍然不允許呼叫它的介面。
這時大家應該就犯難了,這樣的話豈不是不能在本地測試,只能部署到生產環境才能測試?不用著急,解決方案告訴大家:花生殼的內網穿透服務(收費,20元以內)
花生殼官網:http://hsk.oray.com/price/#personal
選擇個人免費版就可以了,雖然說是免費版,但是其實註冊過程中還是要收幾塊錢的,因為我自己買了域名和流量所以花的錢更多一些,但也在20元以內。不建議大家購買流量,送的流量可以用很久了。
當準備好上面提到的就可以開始敲程式碼了。
(3)安裝微信開發者工具,用於本地除錯。
下載地址:https://mp.weixin.qq.com/debug/cgi-bin/webdebugger/download?from=mpwiki&os=x64
官方使用教程:https://mp.weixin.qq.com/wiki?t=resource/res_main&id=mp1455784140
具體步驟
(1)檢視AppId,AppSecret以及繫結域名
進入微信後臺,找到下面的選單
獲取AppID和AppSecret
設定JS介面安全域名
注意第三步,如果微信伺服器不能在我們的伺服器上訪問到這個txt檔案,域名是無法設定成功的,這裡先告訴大家在哪裡設定,想要成功設定域名還需要使用花生殼的服務,讓微信伺服器訪問我們本地工程中的的txt檔案才行。
hkh3321313.vicp.io是在花生殼上購買的域名,免費送的域名是在太難記了,完全不能忍。
(2)引入JS檔案
這裡需要注意是http還是https,如果生產環境是https,務必字首是https,都則會出現mix content這樣的錯誤,導致引入失敗。
<script typet="text/javascript" src="https://res.wx.qq.com/open/js/jweixin-1.0.0.js"></script>
(3)通過AppId和AppSecret請求accessToken,然後通過accessToken獲取jsapi_ticket,生成config介面所需引數
因為獲取這兩個引數的次數是有限制的(accessToke 每日2000次,jsapi_ticket 每日100000次),有效期是7200秒,每兩小時請求一次就行啦,把獲取的accessToke和jsapi_ticket儲存在後臺,所以accessToken和jsapi_ticket這兩個引數的獲取是通過ajax方式請求後臺,而不是實時去獲取的。
config幾個引數需要詳細說明一下:
- timestamp 生成簽名的時間戳 create_nonce_str()
- nonceStr 隨機生成的字串 create_timestamp()
- signature 按照微信文件簽名演算法生成的簽名 makeWXTicket()
附上signature演算法的官方說明:
https://mp.weixin.qq.com/wiki?action=doc&id=mp1421141115&t=0.15697429783636763#buzhou3
在附錄1中可以找到詳細說明。
此外,官方提供了一個簽名演算法的校驗工具:https://mp.weixin.qq.com/debug/cgi-bin/sandbox?t=jsapisign
下面只附上了主要的方法:
//獲取accessToken
private JSONObject getAccessToken(){
//String accessTokenUrl= https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid=APPID&secret=APPSECRET
String requestUrl = accessTokenUrl.replace("APPID",appId).replace("APPSECRET",appSecret);
log.info("getAccessToken.requestUrl====>"+requestUrl);
JSONObject result = HttpUtil.doGet(requestUrl);
return result ;
}
//獲取ticket
private JSONObject getJsApiTicket(){
//String apiTicketUrl= https://api.weixin.qq.com/cgi-bin/ticket/getticket?access_token=ACCESS_TOKEN&type=jsapi
String requestUrl = apiTicketUrl.replace("ACCESS_TOKEN", accessToken);
log.info("getJsApiTicket.requestUrl====>"+requestUrl);
JSONObject result = HttpUtil.doGet(requestUrl);
return result;
}
//生成微信許可權驗證的引數
public Map<String, String> makeWXTicket(String jsApiTicket, String url) {
Map<String, String> ret = new HashMap<String, String>();
String nonceStr = createNonceStr();
String timestamp = createTimestamp();
String string1;
String signature = "";
//注意這裡引數名必須全部小寫,且必須有序
string1 = "jsapi_ticket=" + jsApiTicket +
"&noncestr=" + nonceStr +
"×tamp=" + timestamp +
"&url=" + url;
log.info("String1=====>"+string1);
try
{
MessageDigest crypt = MessageDigest.getInstance("SHA-1");
crypt.reset();
crypt.update(string1.getBytes("UTF-8"));
signature = byteToHex(crypt.digest());
log.info("signature=====>"+signature);
}
catch (NoSuchAlgorithmException e)
{
log.error("WeChatController.makeWXTicket=====Start");
log.error(e.getMessage(),e);
log.error("WeChatController.makeWXTicket=====End");
}
catch (UnsupportedEncodingException e)
{
log.error("WeChatController.makeWXTicket=====Start");
log.error(e.getMessage(),e);
log.error("WeChatController.makeWXTicket=====End");
}
ret.put("url", url);
ret.put("jsapi_ticket", jsApiTicket);
ret.put("nonceStr", nonceStr);
ret.put("timestamp", timestamp);
ret.put("signature", signature);
ret.put("appid", appId);
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 createNonceStr() {
return UUID.randomUUID().toString();
}
//生成時間戳
private static String createTimestamp() {
return Long.toString(System.currentTimeMillis() / 1000);
}
HttpUtil程式碼
public class HttpUtil {
public static Log logger = LogFactory.getLog(HttpUtil.class);
//get請求
public static com.alibaba.fastjson.JSONObject doGet(String requestUrl) {
CloseableHttpClient httpClient = HttpClients.createDefault();
CloseableHttpResponse response = null;
String responseContent = null;
com.alibaba.fastjson.JSONObject result = null;
try {
//建立Get請求,
HttpGet httpGet = new HttpGet(requestUrl);
//執行Get請求,
response = httpClient.execute(httpGet);
//得到響應體
HttpEntity entity = response.getEntity();
//獲取響應內容
responseContent = EntityUtils.toString(entity,"UTF-8");
//轉換為map
result = JSON.parseObject(responseContent);
} catch (IOException e) {
logger.error("HttpUtil=====Start");
logger.error(e.getMessage(),e);
logger.error("HttpUtil=====End");
}
return result;
}
}
(4)通過config介面注入許可權驗證配置
官方示例:
wx.config({
debug: true, // 開啟除錯模式,呼叫的所有api的返回值會在客戶端alert出來,若要檢視傳入的引數,可以在pc端開啟,引數資訊會通過log打出,僅在pc端時才會列印。
appId: '', // 必填,公眾號的唯一標識
timestamp: , // 必填,生成簽名的時間戳
nonceStr: '', // 必填,生成簽名的隨機串
signature: '',// 必填,簽名,見附錄1
jsApiList: [] // 必填,需要使用的JS介面列表,所有JS介面列表見附錄2
});
自己的程式碼:
其中的url不能硬編碼寫在後臺,必須通過動態傳遞。
$(function(){
var url = location.href.split('#').toString();//url不能寫死
$.ajax({
type : "get",
url : "/wechatParam",
dataType : "json",
async : false,
data:{url:url},
success : function(data) {
wx.config({
debug: false,////生產環境需要關閉debug模式
appId: data.appid,//appId通過微信服務號後臺檢視
timestamp: data.timestamp,//生成簽名的時間戳
nonceStr: data.nonceStr,//生成簽名的隨機字串
signature: data.signature,//簽名
jsApiList: [//需要呼叫的JS介面列表
'checkJsApi',//判斷當前客戶端版本是否支援指定JS介面
'onMenuShareTimeline',//分享給好友
'onMenuShareAppMessage'//分享到朋友圈
]
});
},
error: function(xhr, status, error) {
//alert(status);
//alert(xhr.responseText);
}
})
});
(5)通過ready介面處理成功驗證
官方示例:
wx.ready(function(){
// config資訊驗證後會執行ready方法,所有介面呼叫都必須在config介面獲得結果之後,config是一個客戶端的非同步操作,所以如果需要在頁面載入時就呼叫相關介面,則須把相關介面放在ready函式中呼叫來確保正確執行。對於使用者觸發時才呼叫的介面,則可以直接呼叫,不需要放在ready函式中。
});
自己的程式碼:
wx.ready(function () {
var link = window.location.href;
var protocol = window.location.protocol;
var host = window.location.host;
//分享朋友圈
wx.onMenuShareTimeline({
title: '這是一個自定義的標題!',
link: link,
imgUrl: protocol+'//'+host+'/resources/images/icon.jpg',// 自定義圖示
trigger: function (res) {
// 不要嘗試在trigger中使用ajax非同步請求修改本次分享的內容,因為客戶端分享操作是一個同步操作,這時候使用ajax的回包會還沒有返回.
//alert('click shared');
},
success: function (res) {
//alert('shared success');
//some thing you should do
},
cancel: function (res) {
//alert('shared cancle');
},
fail: function (res) {
//alert(JSON.stringify(res));
}
});
//分享給好友
wx.onMenuShareAppMessage({
title: '這是一個自定義的標題!', // 分享標題
desc: '這是一個自定義的描述!', // 分享描述
link: link, // 分享連結,該連結域名或路徑必須與當前頁面對應的公眾號JS安全域名一致
imgUrl: protocol+'//'+host+'/resources/images/icon.jpg', // 自定義圖示
type: 'link', // 分享型別,music、video或link,不填預設為link
dataUrl: '', // 如果type是music或video,則要提供資料鏈接,預設為空
success: function () {
// 使用者確認分享後執行的回撥函式
},
cancel: function () {
// 使用者取消分享後執行的回撥函式
}
});
wx.error(function (res) {
alert(res.errMsg);
});
});
到這裡所有的程式碼都已經分享完畢了。
(6)啟動花生殼的內網穿透服務,設定JS介面安全域名
這個基本是傻瓜式的,只要下載他們的客戶端就可以了。
官網教程:http://hsk.oray.com/news/4345.html
新增一個對映就可以了
把之前下載的txt檔案放在工程目錄webapp下,然後本地啟動工程,確定通過域名可以訪問本地專案後,設定JS安全域名
現在訪問 域名:埠號(例如:hkh3321313.vicp.io:8080)就可以訪問本地專案啦。
(7)使用微信開發者工具測試
微信開發者工具其實就是微信的瀏覽器,其中集成了chrome的除錯工具,前面提到wx.config中的debug模式這裡就發揮作用了,瀏覽器會自動彈出呼叫微信介面的返回結果。
成功返回的話結果應該是ok什麼的,圖就不上了。提醒大家,上生產環境一定要把debug改為false~
後記
雖然已經給了主要的程式碼,大家一定還是不想寫介面,下面附上完整的程式碼,如果你覺得解了燃眉之急
@Controller
public class WeChatController {
private final Logger log = LoggerFactory.getLogger(this.getClass());
//獲取相關的引數,在application.properties檔案中
@Value("${wechat.appId}")
private String appId;
@Value("${wechat.appSecret}")
private String appSecret;
@Value("${wechat.url.accessToken}")
private String accessTokenUrl;
@Value("${wechat.url.apiTicket}")
private String apiTicketUrl;
//微信引數
private String accessToken;
private String jsApiTicket;
//獲取引數的時刻
private Long getTiketTime = 0L;
private Long getTokenTime = 0L;
//引數的有效時間,單位是秒(s)
private Long tokenExpireTime = 0L;
private Long ticketExpireTime = 0L;
//獲取微信引數
@RequestMapping("/wechatParam")
@ResponseBody
public Map<String, String> getWechatParam(String url){
//當前時間
long now = System.currentTimeMillis();
log.info("currentTime====>"+now+"ms");
//判斷accessToken是否已經存在或者token是否過期
if(StringUtils.isBlank(accessToken)||(now - getTokenTime > tokenExpireTime*1000)){
JSONObject tokenInfo = getAccessToken();
if(tokenInfo != null){
log.info("tokenInfo====>"+tokenInfo.toJSONString());
accessToken = tokenInfo.getString("access_token");
tokenExpireTime = tokenInfo.getLongValue("expires_in");
//獲取token的時間
getTokenTime = System.currentTimeMillis();
log.info("accessToken====>"+accessToken);
log.info("tokenExpireTime====>"+tokenExpireTime+"s");
log.info("getTokenTime====>"+getTokenTime+"ms");
}else{
log.info("====>tokenInfo is null~");
log.info("====>failure of getting tokenInfo,please do some check~");
}
}
//判斷jsApiTicket是否已經存在或者是否過期
if(StringUtils.isBlank(jsApiTicket)||(now - getTiketTime > ticketExpireTime*1000)){
JSONObject ticketInfo = getJsApiTicket();
if(ticketInfo!=null){
log.info("ticketInfo====>"+ticketInfo.toJSONString());
jsApiTicket = ticketInfo.getString("ticket");
ticketExpireTime = ticketInfo.getLongValue("expires_in");
getTiketTime = System.currentTimeMillis();
log.info("jsApiTicket====>"+jsApiTicket);
log.info("ticketExpireTime====>"+ticketExpireTime+"s");
log.info("getTiketTime====>"+getTiketTime+"ms");
}else{
log.info("====>ticketInfo is null~");
log.info("====>failure of getting tokenInfo,please do some check~");
}
}
//生成微信許可權驗證的引數
Map<String, String> wechatParam= makeWXTicket(jsApiTicket,url);
return wechatParam;
}
//獲取accessToken
private JSONObject getAccessToken(){
//String accessTokenUrl = https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid=APPID&secret=APPSECRET
String requestUrl = accessTokenUrl.replace("APPID",appId).replace("APPSECRET",appSecret);
log.info("getAccessToken.requestUrl====>"+requestUrl);
JSONObject result = HttpUtil.doGet(requestUrl);
return result ;
}
//獲取ticket
private JSONObject getJsApiTicket(){
//String apiTicketUrl = https://api.weixin.qq.com/cgi-bin/ticket/getticket?access_token=ACCESS_TOKEN&type=jsapi
String requestUrl = apiTicketUrl.replace("ACCESS_TOKEN", accessToken);
log.info("getJsApiTicket.requestUrl====>"+requestUrl);
JSONObject result = HttpUtil.doGet(requestUrl);
return result;
}
//生成微信許可權驗證的引數
public Map<String, String> makeWXTicket(String jsApiTicket, String url) {
Map<String, String> ret = new HashMap<String, String>();
String nonceStr = createNonceStr();
String timestamp = createTimestamp();
String string1;
String signature = "";
//注意這裡引數名必須全部小寫,且必須有序
string1 = "jsapi_ticket=" + jsApiTicket +
"&noncestr=" + nonceStr +
"×tamp=" + timestamp +
"&url=" + url;
log.info("String1=====>"+string1);
try
{
MessageDigest crypt = MessageDigest.getInstance("SHA-1");
crypt.reset();
crypt.update(string1.getBytes("UTF-8"));
signature = byteToHex(crypt.digest());
log.info("signature=====>"+signature);
}
catch (NoSuchAlgorithmException e)
{
log.error("WeChatController.makeWXTicket=====Start");
log.error(e.getMessage(),e);
log.error("WeChatController.makeWXTicket=====End");
}
catch (UnsupportedEncodingException e)
{
log.error("WeChatController.makeWXTicket=====Start");
log.error(e.getMessage(),e);
log.error("WeChatController.makeWXTicket=====End");
}
ret.put("url", url);
ret.put("jsapi_ticket", jsApiTicket);
ret.put("nonceStr", nonceStr);
ret.put("timestamp", timestamp);
ret.put("signature", signature);
ret.put("appid", appId);
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 createNonceStr() {
return UUID.randomUUID().toString();
}
//生成時間戳
private static String createTimestamp() {
return Long.toString(System.currentTimeMillis() / 1000);
}
}
那 qq 裡分享網頁呢?標題、描述 和 圖片 怎麼設定?
<meta itemprop="name" content="這是分享的標題"/> <meta itemprop="image" content="http://imgcache.qq.com/qqshow/ac/v4/global/logo.png" /> <meta name="description" itemprop="description" content="這是要分享的內容" />
其他問題
問:直接在卡片上長按進行轉發的話,縮圖資訊會失效?
答:參考 微信分享給朋友的連結,再次被好友轉發之後,分享連結的圖示不見了,怎麼解決?求賜教!