拦截小程序onLoad和onShow实现系统的token登录校验

411 阅读2分钟

背景

小程序中,有些页面需要先登录,拿到token再调用其他接口,实现页面逻辑。一般做法是在onLoad里面判断是否登录,没有登录就跳转登录页面,已经登录就执行逻辑,onShow里面也是没有登录就不执行逻辑。登录返回来以后,不会再走onLoad,就需要再onShow里面调用接口,这样会造成几个问题

  • 如果第一次进来就是登录了的,onLoad和nShow都会调用接口,造成多次调用
  • 如果onLoad里面不调用,只做登录判断,onShow里面调用,确实可以避免进到页面多次调用的问题,但是每次onShow都会调用,有时我们希望在只在onLoad时调用一次

拦截实现思路

我们希望在需要登录的页面的onLoad和onShow执行之前,先执行我们的登录逻辑,登录完成后再执行页面的onLoad和onShow

  • 收集要监听的方法和回调
  • 重写Page的onLoad和onShow,在重写的onLoad里面先执行回调函数,再依次执行原始的onLoad和onShow

实现

interceptor.js

type callBackFun = () => Promise<any>;
export default class interceptor {
  interceptorMaps: Map<string, callBackFun> // 要拦截的函数和回调
  constructor() {
    this.interceptorMaps = new Map()
  }
  on(key: string, callBack:callBackFun) {
    this.interceptorMaps.set(key, callBack)
    return this
  }
  apply() {
    const self = this
    const originPage = Page // 存放原始的Page
    Page = function (option) { // 重写Page,当小程序的Page执行的时候,相当于执行了我们重写的Page
      const originOload = option.onLoad
      const originOshow = option.onShow
      option.onLoad = function (opt) {
        const callBack = self.interceptorMaps.get('onLoad')
        if (!callBack || !option.needLogin) { // 没有回调函数或者不需要登录
          originOload && originOload.call(this,opt)
          return
        }
        // 需要登录且有回调函数
          new Promise(async (resolve, reject) => {
            this.__proto__.execute = true // 正在执行onLoad的异步函数,防止onShow提前执行
            const res = await callBack().catch(err => {
              reject(err)
            })
            resolve(res)
          }).then(() => {
            if (originOload) {
              originOload.call(this,opt)
              originOshow?.call(this)
              this.__proto__.execute = false // onLoad的异步函数执行完成,需重置,防止返回页面不重新执行onShow
            }
          }).catch(err => {
            console.log('登录失败', err)
          })
      }
      option.onShow = function () {
        if (!this.__proto__.execute) {
          originOshow?.call(this)
        }
      }
      originPage(option) // 需要手动执行原始的Page,防止原始方法丢失
    }
    return this
  }

}

使用

在app.ts里面

import interceptor from './interceptor'
const interceptorFun = new interceptor()
interceptorFun.on('onLoad', () => {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      resolve(true) // 登录成功
      // reject('登录失败')
    }, 6000)
  })
}).apply()

注意

在需要登录的页面page里面配置needLogin:true

Page({
  needLogin: true
})

扩展

这段代码和设计模式的关系

  1. 装饰器模式:在这段代码中,类 interceptor 通过 on 方法向 interceptorMaps 中注册了要拦截的函数和对应的回调函数。然后在 apply 方法中,对 Page 函数进行了重写,增加了额外的功能,这部分类似于装饰器模式的作用。
  2. 代理模式:在 apply 方法中,对 Page 函数进行了代理,通过重写 onLoad 和 onShow 方法,实现了对原始函数的代理,并在代理中加入了额外的逻辑。