返回博客

微信小程序页面权限守卫:五轮失败后发现 definitionFilter 拦截 onShow 是条死路

某个小程序有 30+ 个页面需要做访问权限控制——用户进入之前先校验权限缓存,无权限则拦截并展示提示。听起来很简单,用 Behavior 统一包一层 onShow 不就行了?

现实是:我在这条路上连续踩了五个坑,每次「修完」都以为解决了,测试一跑又炸。最后的结论是:definitionFilter 拦截 onShow 在 Page 上是一条死路,根因是框架内部未文档化的行为。

现象

以下几种做法都试过,表现各异,但最终结局相同——页面的业务逻辑静默失效:

  1. 全局 monkey-patch Page():部分 tabBar 页面骨架屏永远不消失,接口调用消失
  2. definitionFilter 延迟调用 originalOnShow:权限检查完了再 originalOnShow.apply(this, arguments),但页面的 checkLogin → loadData 链路从不执行
  3. definitionFilter 立即调用 originalOnShow:和上面一样,app.checkLogin 回调永远不触发
  4. Behavior({onShow}) 直接挂 lifecycle:在不同基础库版本下 onShow 有时根本不触发

最诡异的表现:页面正常渲染(有 DOM 输出),但接口一个没调,数据全是初始值。

根因

definitionFilter 是微信为 Component behavior 设计的钩子,并非为 Page() 设计。当用它替换 defFields.onShow 时:

  • 捕获的 originalOnShow 引用,在异步回调里通过 .apply(this, arguments) 调用时,上下文绑定出现了未文档化的异常——调用动作本身不报错,但 this 指向的内部状态链已经断了
  • Page 的内部生命周期链(store 绑定、checkLogin、数据加载)在 originalOnShow 被替换的瞬间就已脆化,即便最终「调了」也无效
坑:originalOnShow.apply() 不报错,但页面业务逻辑静默丢失

definitionFilter 在 Page 上替换 onShow 属于未定义行为区域。你看到调用栈里 originalOnShow 被执行了,但页面依赖的内部绑定(store、checkLogin 回调、setData 链路)在此时已不可靠。这不是偶现 bug,而是在生产环境下稳定复现的静默失效。

正解

把所有守卫逻辑移进 Component,让页面完全不做任何改动。

Component 有 pageLifetimes.show,可以感知宿主页面的 show 时机,而不需要碰 Page 的生命周期。页面正常跑它的 onShow,守卫组件在旁边独立做权限判断,通过全屏不透明遮罩实现视觉阻断。

架构简图

app.js login() → 预拉权限 → wx.setStorageSync('page_access')
                                        ↓
Page onShow → 正常执行(不动)           ↓
                                        ↓
<page-access-guard />(Component)      ↓
  └─ pageLifetimes.show
       → wx.getStorageSync() → 检查当前路由
           ├─ 有权限 → _granted=true → 遮罩消失
           ├─ 无权限 → _denied=true → 展示拦截 UI
           └─ 缓存未就绪 → 500ms × 10 重试,超时后 fail-open

核心组件实现

// components/page-access-guard/index.js
Component({
  data: { _granted: false, _denied: false },
 
  lifetimes: {
    attached: function () { this._checkAccess() }
  },
 
  pageLifetimes: {
    show: function () { this._checkAccess() }
  },
 
  methods: {
    _checkAccess: function () {
      var self = this
      var pages = getCurrentPages()
      var current = pages[pages.length - 1]
      var path = current ? current.route : ''
      if (!path) { this.setData({ _granted: true }); return }
 
      var data = null
      try { data = wx.getStorageSync('page_access') || null } catch (e) {}
 
      if (data && Array.isArray(data.accessible_pages)) {
        var ok = data.accessible_pages.indexOf(path) !== -1
        this.setData({ _granted: ok, _denied: !ok })
        return
      }
 
      // 缓存还没就绪,重试
      this._retryCount = (this._retryCount || 0) + 1
      if (this._retryCount > 10) {
        this.setData({ _granted: true }) // fail-open
        return
      }
      setTimeout(function () { self._checkAccess() }, 500)
    }
  }
})

WXSS 关键点:遮罩必须不透明

.guard-overlay {
  position: fixed;
  top: 0; left: 0;
  width: 100vw; height: 100vh;
  z-index: 99999;
  background: #fff; /* 必须不透明,否则页面内容透出来 */
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
}

页面侧零改动

<!-- 只在 WXML 顶部加这一行,其余页面逻辑完全不动 -->
<page-access-guard />
<page-container>...</page-container>
正解:把守卫做成 Component,用 pageLifetimes.show 感知页面,不碰 Page 生命周期

Component 的 pageLifetimes.show 是为此场景而生的正规 API。守卫逻辑与页面完全解耦:页面 onShow 照常跑,Component 在旁边独立做权限判断。30+ 个页面只需在 WXML 加一行标签,无需任何 JS 改动。

一句话外卖

在微信小程序里用 definitionFilter 拦截 Page 的 onShow 是未定义行为区域——originalOnShow 调了也会静默失效;要在 Page 层做统一横切逻辑,正路是独立 Component + pageLifetimes.show,让页面逻辑和守卫逻辑各管各的。