最近因爲項目需要,所以瞭解了下微信JS-SDK的使用;可能有很多不太對的地方請大家多多指教!
參考文檔地址:https://mp.weixin.qq.com/wiki
進入--》微信網頁開發--》微信JS-SDK說明文檔。
首先:我們需要在官網申請微信公衆號,並且進行認證(如果不認證可也能會報{"errMsg":"onMenuShareAppMessage:fail, the permission value is offline verifying"}錯誤),最好是服務號,因爲服務號授權後好多權限纔可用。如下文官網公衆號接口授權說明:
公衆號接口權限說明
不同的公衆號類型具備不同的接口權限,具體如下表: 請注意:
1、微博認證視作未認證,因此微博認證的公衆號不會擁有微信認證公衆號特有的接口。
2、微信認證分爲資質認證和名稱認證兩部分,只需要資質認證通過,就可獲得接口。
接口名稱 | 未認證訂閱號 | 微信認證訂閱號 | 未認證服務號 | 微信認證服務號 |
---|---|---|---|---|
基礎支持-獲取access_token | 有 | 有 | 有 | 有 |
基礎支持-獲取微信服務器IP地址 | 有 | 有 | 有 | 有 |
接收消息-驗證消息真實性、接收普通消息、接收事件推送、接收語音識別結果 | 有 | 有 | 有 | 有 |
發送消息-被動回覆消息 | 有 | 有 | 有 | 有 |
發送消息-客服接口 | 有 | 有 | ||
發送消息-羣發接口 | 有 | 有 | ||
發送消息-模板消息接口(發送業務通知) | 有 | |||
發送消息-一次性訂閱消息接口 | 有 | 有 | ||
用戶管理-用戶分組管理 | 有 | 有 | ||
用戶管理-設置用戶備註名 | 有 | 有 | ||
用戶管理-獲取用戶基本信息 | 有 | 有 | ||
用戶管理-獲取用戶列表 | 有 | 有 | ||
用戶管理-獲取用戶地理位置 | 有 | |||
用戶管理-網頁授權獲取用戶openid/用戶基本信息 | 有 | |||
推廣支持-生成帶參數二維碼 | 有 | |||
推廣支持-長鏈接轉短鏈接口 | 有 | |||
界面豐富-自定義菜單 | 有 | 有 | 有 | |
素材管理-素材管理接口 | 有 | 有 | ||
智能接口-語義理解接口 | 有 | |||
多客服-獲取多客服消息記錄、客服管理 | 有 | |||
微信支付接口 | 需申請 | |||
微信小店接口 | 需申請 | |||
微信卡券接口 | 需申請 | 需申請 | ||
微信設備功能接口 | 需申請 | |||
微信發票接口 | 有 | 有 | ||
微信JS-SDK-基礎接口 | 有 | 有 | 有 | 有 |
微信JS-SDK-分享接口 | 有 | 有 | ||
微信JS-SDK-圖像接口 | 有 | 有 | 有 | 有 |
微信JS-SDK-音頻接口 | 有 | 有 | 有 | 有 |
微信JS-SDK-智能接口(網頁語音識別) | 有 | 有 | 有 | 有 |
微信JS-SDK-設備信息 | 有 | 有 | 有 | 有 |
微信JS-SDK-地理位置 | 有 | 有 | 有 | 有 |
微信JS-SDK-界面操作 | 有 | 有 | 有 | 有 |
微信JS-SDK-微信掃一掃 | 有 | 有 | 有 | 有 |
微信JS-SDK-微信小店 | 有 | |||
微信JS-SDK-微信卡券 | 有 | 有 | ||
微信JS-SDK-微信支付 | 有 |
第一可以獲取AppID、APPSecret(這個是開發獲取access_token注意這裏的access_token和獲取個人信息獲取的access_token不是同一個;)
access_token是公衆號的全局唯一接口調用憑據,公衆號調用各接口時都需使用access_token。開發者需要進行妥善保存。access_token的存儲至少要保留512個字符空間。access_token的有效期目前爲2個小時,需定時刷新,重複獲取將導致上次獲取的access_token失效。
第二下面的IP白名單是和域名映射的IP一致,JS接口安全域名是給我們的域名授權,可以讓我們的域名下的jsp有權限調用微信的JS(細節建議以微信官方開發文檔爲準,經常更新)。
其次。接下來就是去實現了,實現步驟先說下:
JSSDK使用步驟
步驟一:綁定域名
先登錄微信公衆平臺進入“公衆號設置”的“功能設置”裏填寫“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可以在這裏更新簽名。
});
好了,這就不多說了,我們直接貼代碼。
前端代碼(比較全的實現DOME參考--http://203.195.235.76/jssdk/)--可以通過開發者模式把代碼宕下來看,啊哈哈:
在看代碼之前,給大家說下需要注意地方:
(1)wx.config中的所有參數都需要異步獲取,如果非異步獲取,實現後會報錯;經常遇到的錯誤請參考(微信開發者文檔--》微信網頁開發--》微信JS-SDK說明文檔--》附錄5-常見錯誤及解決方法)
(2)url地址需要動態獲取,使用的獲取方法爲var url=location.href.split('#')[0];
(3)wx.config中開發時配置debug: true,這樣可以有助於找問題,驗證成功後會返回ok;上線後修改爲debug: false;
(4)wx.checkJsApi 判斷當前版本是否支持指定 JS 接口,支持批量判斷;這樣保證在不支持是及時提醒
<script type="text/javascript" src="http://res.wx.qq.com/open/js/jweixin-1.2.0.js"></script>
<script type="text/javascript">
(function(window){
var url=location.href.split('#')[0];
$.ajax({
type: "POST",
url: "./jsSDKDoor",
data: {
"url": url
},
dataType: "json",
async:false,
success: function (data) {
wx.config({
debug: true, // 開啓調試模式,調用的所有api的返回值會在客戶端alert出來,若要查看傳入的參數,可以在pc端打開,參數信息會通過log打出,僅在pc端時纔會打印。
appId: data.appId, // 必填,公衆號的唯一標識
timestamp: data.timestamp, // 必填,生成簽名的時間戳
nonceStr: data.nonceStr, // 必填,生成簽名的隨機串
signature: data.signature,// 必填,簽名,見附錄1
jsApiList: [
'checkJsApi',
'onMenuShareTimeline',
'onMenuShareAppMessage',
'onMenuShareQQ',
'onMenuShareWeibo',
'onMenuShareQZone',
'hideMenuItems',
'showMenuItems',
'hideAllNonBaseMenuItem',
'showAllNonBaseMenuItem',
'translateVoice',
'startRecord',
'stopRecord',
'onVoiceRecordEnd',
'playVoice',
'onVoicePlayEnd',
'pauseVoice',
'stopVoice',
'uploadVoice',
'downloadVoice',
'chooseImage',
'previewImage',
'uploadImage',
'downloadImage',
'getNetworkType',
'openLocation',
'getLocation',
'hideOptionMenu',
'showOptionMenu',
'closeWindow',
'scanQRCode',
'chooseWXPay',
'openProductSpecificView',
'addCard',
'chooseCard',
'openCard'
] // 必填,需要使用的JS接口列表,所有JS接口列表見附錄2
});
}
});
wx.ready(function(){
// wx.checkJsApi1 判斷當前版本是否支持指定 JS 接口,支持批量判斷
wx.checkJsApi({
jsApiList: [
'getNetworkType',
'previewImage'
],
success: function (res) {
alert(JSON.stringify(res)+"支持JS SDK接口");
}
});
//隱藏瀏覽器中右上角的三個點內的菜單
wx.hideMenuItems({
menuList: [
'menuItem:readMode', // 閱讀模式
'menuItem:copyUrl', // 複製鏈接
],
success: function (res) {
alert('已隱藏“閱讀模式”,“複製鏈接”等按鈕');
},
fail: function (res) {
alert(JSON.stringify(res));
}
});
wx.onMenuShareTimeline({
title: '互聯網之子',
desc: '在長大的過程中,我才慢慢發現,我身邊的所有事,別人跟我說的所有事,那些所謂本來如此,註定如此的事,它們其實沒有非得如此,事情是可以改變的。更重要的是,有些事既然錯了,那就該做出改變。',
link: 'http://movie.douban.com/subject/25785114/',
imgUrl: 'http://demo.open.weixin.qq.com/jssdk/images/p2166127561.jpg',
trigger: function (res) {
// 不要嘗試在trigger中使用ajax異步請求修改本次分享的內容,因爲客戶端分享操作是一個同步操作,這時候使用ajax的回包會還沒有返回
alert('用戶點擊發送給朋友圈111');
},
success: function (res) {
alert('已分享11');
},
cancel: function (res) {
alert('已取消11');
},
fail: function (res) {
alert(JSON.stringify(res));
}
});
wx.onMenuShareAppMessage({
title: '12211', // 分享標題
desc: '11', // 分享描述
link: '1', // 分享鏈接,該鏈接域名或路徑必須與當前頁面對應的公衆號JS安全域名一致
imgUrl: 'http://demo.open.weixin.qq.com/jssdk/images/p2166127561.jpg', // 分享圖標
type: 'video', // 分享類型,music、video或link,不填默認爲link
dataUrl: 'http://bf0d6df4.ngrok.io/1.flv', // 如果type是music或video,則要提供數據鏈接,默認爲空
trigger: function (res) {
// 不要嘗試在trigger中使用ajax異步請求修改本次分享的內容,因爲客戶端分享操作是一個同步操作,這時候使用ajax的回包會還沒有返回
alert('用戶點擊發送給朋友111');
},
success: function (res) {
alert('已分享11');
},
cancel: function (res) {
alert('已取消11');
},
fail: function (res) {
alert(JSON.stringify(res));
}
});
// 2.3 監聽“分享到QQ”按鈕點擊、自定義分享內容及分享結果接口
$('#onMenuShareQQ').click(function () {
wx.onMenuShareQQ({
title: '互聯網之子',
desc: '在長大的過程中,我才慢慢發現,我身邊的所有事,別人跟我說的所有事,那些所謂本來如此,註定如此的事,它們其實沒有非得如此,事情是可以改變的。更重要的是,有些事既然錯了,那就該做出改變。',
link: 'http://movie.douban.com/subject/25785114/',
imgUrl: 'http://img3.douban.com/view/movie_poster_cover/spst/public/p2166127561.jpg',
trigger: function (res) {
alert('用戶點擊分享到QQ');
},
complete: function (res) {
alert(JSON.stringify(res));
},
success: function (res) {
alert('已分享');
},
cancel: function (res) {
alert('已取消');
},
fail: function (res) {
alert(JSON.stringify(res));
}
});
alert('已註冊獲取“分享到 QQ”狀態事件');
});
// 5 圖片接口
// 5.1 拍照、本地選圖
var images = {
localId: [],
serverId: []
};
$('#chooseImage').click (function () {
wx.chooseImage({
success: function (res) {
images.localId = res.localIds;
alert('已選擇 ' + res.localIds.length + ' 張圖片');
}
});
});
// 2.4 監聽“分享到微博”按鈕點擊、自定義分享內容及分享結果接口
$('#onMenuShareWeibo').click(function () {
wx.onMenuShareWeibo({
title: '互聯網之子',
desc: '在長大的過程中,我才慢慢發現,我身邊的所有事,別人跟我說的所有事,那些所謂本來如此,註定如此的事,它們其實沒有非得如此,事情是可以改變的。更重要的是,有些事既然錯了,那就該做出改變。',
link: 'http://movie.douban.com/subject/25785114/',
imgUrl: 'http://img3.douban.com/view/movie_poster_cover/spst/public/p2166127561.jpg',
trigger: function (res) {
alert('用戶點擊分享到微博');
},
complete: function (res) {
alert(JSON.stringify(res));
},
success: function (res) {
alert('已分享');
},
cancel: function (res) {
alert('已取消');
},
fail: function (res) {
alert(JSON.stringify(res));
}
});
alert('已註冊獲取“分享到微博”狀態事件');
});
// 3 智能接口
var voice = {
localId: '',
serverId: ''
};
// 3.1 識別音頻並返回識別結果
$('#translateVoice').click ( function () {
if (voice.localId == '') {
alert('請先使用 startRecord 接口錄製一段聲音');
return;
}
wx.translateVoice({
localId: voice.localId,
complete: function (res) {
if (res.hasOwnProperty('translateResult')) {
alert('識別結果:' + res.translateResult);
} else {
alert('無法識別');
}
}
});
});
// 4 音頻接口
// 4.2 開始錄音
$('#startRecord').click(function () {
wx.startRecord({
cancel: function () {
alert('用戶拒絕授權錄音');
}
});
});
// 4.3 停止錄音
$('#stopRecord').click ( function () {
wx.stopRecord({
success: function (res) {
voice.localId = res.localId;
},
fail: function (res) {
alert(JSON.stringify(res));
}
});
});
});
})(this);
</script>
公衆號可以使用AppID和AppSecret調用本接口來獲取access_token。AppID和AppSecret可在“微信公衆平臺-開發-基本配置”頁中獲得(需要已經成爲開發者,且帳號沒有異常狀態)。調用接口時,請登錄“微信公衆平臺-開發-基本配置”提前將服務器IP地址添加到IP白名單中,點擊查看設置方法,否則將無法調用成功
後臺代碼:
需要注意的地方:
(1)由於經常使用代碼調用微信接口來實現獲取access_token這樣可以會被微信的限制攔截(微信接口的被調用次數有上限),因爲access_token的有效期也給我們返回了,暫時是7200秒,所有儘量使用緩存實現
(2) url是有前端獲取的。
@RequestMapping(value = "/jsSDKDoor", method = RequestMethod.POST)
@ResponseBody
public InitJSSDK jsSDKDoor(ModelMap modelMap,HttpServletRequest req, HttpServletResponse resp) throws Exception {
//獲取到用戶信息後就可以進行重定向,走自己的業務邏輯了。。。。。。
String url = req.getParameter("url");
/*
*接下來獲取JS SDK的票據;供前端使用
*/
//副本第四步:通過access_token獲取jsapi_ticket
/**
* 注意這個access_token和用戶授權登錄的access_token定義不一樣,獲取方式也不一樣。
*
*/
//access_token是公衆號的全局唯一接口調用憑據,公衆號調用各接口時都需使用access_token。開發者需要進行妥善保存。access_token的存儲至少要保留512個字符空間。access_token的有效期目前爲2個小時,需定時刷新,重複獲取將導致上次獲取的access_token失效。
// 微信開發文檔強調:access_token、jsapi_ticket有效期7200秒,開發者必須在自己的服務全局緩存jsapi_ticket。
InitJSSDK initJSSDK = new InitJSSDK();
String access_token = WeChatAccessToken.getAccessToken();
System.out.println("access_token:"+access_token);
String jsapi_ticket = JSSDKTicket.getTicket();
System.out.println("jsapi_ticket:"+jsapi_ticket);
//副本第五步:獲取簽名
String signature = getSignature(jsapi_ticket,url,initJSSDK);
initJSSDK.setSignature(signature);
initJSSDK.setAppId( WXAuthUtil.APPID);
return initJSSDK;
}
/*
* 簽名生成規則如下:參與簽名的字段包括noncestr(隨機字符串), 有效的jsapi_ticket, timestamp(時間戳), url(當前網頁的URL,不包含#及其後面部分) 。
* 對所有待簽名參數按照字段名的ASCII 碼從小到大排序(字典序)後,使用URL鍵值對的格式(即key1=value1&key2=value2…)拼接成字符串string1。這裏需要注意的是所有參數名均爲小寫字符。
* 對string1作sha1加密,字段名和字段值都採用原始值,不進行URL 轉義。
*/
public static String getSignature(String jsapi_ticket,String url, InitJSSDK initJSSDK) {
//2、獲取Ticket
//3、時間戳和隨機字符串
String noncestr = UUID.randomUUID().toString().replace("-", "").substring(0, 16);//隨機字符串
String timestamp = String.valueOf(System.currentTimeMillis() / 1000);//時間戳
System.out.println("\njsapi_ticket:"+jsapi_ticket+"\n時間戳:"+timestamp+"\n隨機字符串:"+noncestr);
//4、獲取url
//5、將參數排序並拼接字符串
String str = "jsapi_ticket="+jsapi_ticket+"&noncestr="+noncestr+"×tamp="+timestamp+"&url="+url;
//6、將字符串進行sha1加密
String signature =SHA1.encode(str);
System.out.println("參數:"+str+"\n簽名:"+signature);
initJSSDK.setTimestamp(timestamp);
initJSSDK.setNonceStr(noncestr);
return signature;
}
(一)單例實現JSSDKTicket;獲取ticket緩存,這樣要比使用緩存工具成本要小
public class JSSDKTicket {
public static Logger logger=Logger.getLogger(JSSDKTicket.class);
private String ticket;
private int expires_in;
private long timeStamp=System.currentTimeMillis()/1000;
//單例模式實現
private static JSSDKTicket JSAPI_TICKET;
public static String getTicket(){
if (JSAPI_TICKET==null) {
try {
JSAPI_TICKET=getJsTicket();
} catch (Exception e) {
e.printStackTrace();
return null;
}
}
if(JSAPI_TICKET.isExpire()){
try {
JSAPI_TICKET=getJsTicket();
} catch (Exception e) {
e.printStackTrace();
return null;
}
return getTicket();
}else{
return JSAPI_TICKET.ticket;
}
}
private JSSDKTicket(){
super();
}
public int getExpires_in() {
return expires_in;
}
public void setExpires_in(int expires_in) {
this.expires_in = expires_in;
}
public long getTimeStamp() {
return timeStamp;
}
public void setTimeStamp(long timeStamp) {
this.timeStamp = timeStamp;
}
public void setTicket(String ticket) {
this.ticket = ticket;
}
public boolean isExpire(){
return System.currentTimeMillis()/1000<(timeStamp+expires_in)?false:true;
}
private static JSSDKTicket getJsTicket() throws Exception{
String ticktUrl = "https://api.weixin.qq.com/cgi-bin/ticket/getticket?access_token="+ WeChatAccessToken.getAccessToken() +"&type=jsapi";//這個url鏈接和參數不能變
JSONObject demoJson = WXAuthUtil.doGetJson(ticktUrl);
/*
* {
"errcode":0,
"errmsg":"ok",
"ticket":"bxLdikRXVbTPdHSM05e5u5sUoXNKd8-41ZO3MhKoyN5OfkWITDGgnr2fwJ0m9E8NYzWKVZvdVtaUgWvsdshFKA",
"expires_in":7200
}
*/
System.out.println("單例獲取jsapi_ticket"+demoJson.toString());
if(demoJson.getInteger("errcode")==0){
return JSON.parseObject(demoJson.toString(), JSSDKTicket.class);
}else{
throw new Exception(demoJson.getString("access_token missing"));
}
}
}
(二)單例設計模式實現,緩存access_token:
public class WeChatAccessToken {
private String access_token;
private int expires_in;
private Date time = new Date();
public boolean isExpires() {
long secends = System.currentTimeMillis() - this.time.getTime();
return secends > this.expires_in * 1000 ? true : false;
}
//類靜態對象
private static WeChatAccessToken accessToken;
//單例模式實現
public static String getAccessToken() throws Exception {
if (accessToken == null) {
try {
getWeChatAccessToken();
} catch (IOException e) {
e.printStackTrace();
}
} else if (accessToken.isExpires()) {
try {
getWeChatAccessToken();
} catch (IOException e) {
e.printStackTrace();
}
}
return accessToken.getAccess_token();
}
@Override
public String toString() {
return "WeChatAccessToken [access_token=" + access_token
+ ", expires_in=" + expires_in + ", time=" + time
+ ", isExpires()=" + isExpires() + "]";
}
private static void getWeChatAccessToken() throws Exception {
String grant_type = "client_credential";//獲取access_token填寫client_credential
//這個url鏈接地址和參數皆不能變
String url = "https://api.weixin.qq.com/cgi-bin/token?grant_type="+grant_type+"&appid="+WXAuthUtil.APPID+"&secret="+WXAuthUtil.APPSECRET;
JSONObject accessInfo = WXAuthUtil.doGetJson(url);
/*
* {"access_token":"ACCESS_TOKEN","expires_in":7200}
*/
System.out.println("單例獲取access_token"+accessInfo.toString());
if(accessInfo.toString().contains("access_token")){
accessToken = JSON.parseObject(accessInfo.toString(), WeChatAccessToken.class);
}else{
throw new Exception(accessInfo.getString("errcode"));
}
}
public String getAccess_token() {
return access_token;
}
public void setAccess_token(String access_token) {
this.access_token = access_token;
}
public int getExpires_in() {
return expires_in;
}
public void setExpires_in(int expires_in) {
this.expires_in = expires_in;
}
}