微信小程式之頁面攔截器

marginyu發表於2017-09-06

場景

  1. 小程式有52個頁面,其中13個頁面無需任何身份,另外39個頁面需要系統角色。對於這39個頁面,如果微信使用者沒有系統角色,則跳轉到登入頁。
  2. 是否有系統角色資訊需要通過非同步請求來獲取。

需求分析&實現

對需求進行抽象,其實要的就是一個過濾器,對小程式頁面的訪問進行過濾,符合條件的通過,不符合條件進行其他處理。

使用過php的laravel框架的童鞋,肯定一下子就聯想到了laravel框架的http中介軟體:
HTTP 中介軟體提供一個方便的機制來過濾進入應用程式的 HTTP 請求,例如,Laravel 預設包含了一箇中介軟體來檢驗使用者身份驗證,如果使用者沒有經過身份驗證,中介軟體會將使用者導向登入頁面,然而,如果使用者通過身份驗證,中介軟體將會允許這個請求進一步繼續前進。當然,除了身份驗證之外,中介軟體也可以被用來執行各式各樣的任務,CORS 中介軟體負責替所有即將離開程式的響應加入適當的響應頭,一個日誌中介軟體可以記錄所有傳入應用程式的請求。

令人憂桑的是,微信小程式並沒有提供針對Page例項的中介軟體機制。所以只能從Page例項的生命週期處下手。

mina-lifecycle.png
mina-lifecycle.png

對於onLoad,一個頁面只會呼叫一次;對於onShow,每次開啟頁面(比如小程式從後臺轉到前臺)都會呼叫一次。

在onLoad或者onShow鉤子函式裡,對使用者身份進行校驗,通過後則拉取該頁面需要的資料,否則跳轉到登入頁。

//orderDetail.js
onShow: function () {
    let that = this;
    //身份校驗
    service.identityCheck(() => {
          //跳轉到登入頁
          wx.redirectTo({
            url: "/pages/common/login/login"
          });
        }, () => {    
          //獲取頁面資料等等      
          that.getDetail(this.orderId);
          ...
        }
   );
  },複製程式碼

不過,每個頁面都要這樣寫,重複程式碼好多啊,侵入性也強。不如用裝飾函式(高大上的說法是裝飾者模式)來包裝一下:

//filter.js
function identityFilter(pageObj){
    if(pageObj.onShow){
        let _onShow = pageObj.onShow;
        pageObj.onShow = function(){
            service.identityCheck(()=>{
                //跳轉到登入頁
                wx.redirectTo({
                    url: "/pages/common/login/login"
                });
            },()=>{
                //獲取頁面例項,防止this劫持
                let currentInstance = getPageInstance();
                _onShow.call(currentInstance);
            });
        }
    }
    return pageObj;
}

function getPageInstance(){
    var pages = getCurrentPages();
    return  pages[pages.length - 1];
}

exports.identityFilter = identityFilter;複製程式碼

filter.js用以提供過濾器方法,除了現有的使用者身份攔截,後續如果需要其他攔截,可以在這個檔案增加。然後,在需要使用者身份攔截的小程式頁面程式碼裡,用filter.identityFilter處理一下就可以了:

//orderDetail.js
let filter = require('filter.js');
Page(filter.identityFilter({
    ...
    onShow: function () {
        //獲取頁面資料等等
        this.getDetail(this.orderId);
        //...
    },
    ...
}));複製程式碼

使用Promise進行優化

上面的實現中,每次訪問頁面,都會執行一次獲取使用者身份的方法(就是上面程式碼裡的service. identityCheck)。其實沒有必要,在小程式啟動的時候獲取一次就行了。也就是說,放在app.js的onLaunch方法裡執行。

每個小程式頁面例項化時,一般也會執行非同步方法,用來獲取頁面需要的資料。關鍵在於,我們需要保證,頁面的非同步方法 必須在 獲取使用者身份的非同步請求 之後執行。

毋容置疑,Promise最擅長處理非同步請求的執行順序了。主子,快放程式碼粗來:

//app.js
App({
    onLaunch:function(){
        let p = new Promise(function(resolve,reject){
            service.identityCheck(resolve,reject);
        });
        this.globalData.promise = p; 
    },
    ...
    globalData: {
        promise:null,
    }   
});複製程式碼
//filter.js
const appData = getApp().globalData;
function identityFilter(pageObj){
    if(pageObj.onShow){
        let _onShow = pageObj.onShow;
        pageObj.onShow = function(){
            //改動點
            appData.promise.then(()=>{
                //跳轉到登入頁
                wx.redirectTo({
                    url: "/pages/common/login/login"
                });
            },()=>{
                //獲取頁面例項,防止this劫持
                let currentInstance = getPageInstance();
                _onShow.call(currentInstance);
            });
        }
    }
    return pageObj;
}複製程式碼

小結

基本實現了小程式頁面的使用者身份攔截器,但是比起laravel的http中介軟體還是遜色一些:

  1. 需要對每個頁面程式碼包裝一層。
  2. 即使使用者身份校驗不通過,小程式也並不會阻塞頁面的渲染。假如獲取使用者身份的非同步方法一分鐘才執行完,小程式頁面還是會展示出來,一分鐘之後才跳轉到登入頁。需要自己增加邏輯,比如在這一分鐘內,頁面展示空白內容。

嗯,對小程式的新特性保持關注,後面看看怎麼改進~

相關文章