微信JS-SDK的使用

最近因爲項目需要,所以瞭解了下微信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+"&timestamp="+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;
 }
}


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章