微信小程序页面权限守卫:五轮失败后发现 definitionFilter 拦截 onShow 是条死路
某个小程序有 30+ 个页面需要做访问权限控制——用户进入之前先校验权限缓存,无权限则拦截并展示提示。听起来很简单,用 Behavior 统一包一层 onShow 不就行了?
现实是:我在这条路上连续踩了五个坑,每次「修完」都以为解决了,测试一跑又炸。最后的结论是:definitionFilter 拦截 onShow 在 Page 上是一条死路,根因是框架内部未文档化的行为。
现象
以下几种做法都试过,表现各异,但最终结局相同——页面的业务逻辑静默失效:
- 全局 monkey-patch
Page():部分 tabBar 页面骨架屏永远不消失,接口调用消失 definitionFilter延迟调用originalOnShow:权限检查完了再originalOnShow.apply(this, arguments),但页面的checkLogin → loadData链路从不执行definitionFilter立即调用originalOnShow:和上面一样,app.checkLogin回调永远不触发Behavior({onShow})直接挂 lifecycle:在不同基础库版本下onShow有时根本不触发
最诡异的表现:页面正常渲染(有 DOM 输出),但接口一个没调,数据全是初始值。
根因
definitionFilter 是微信为 Component behavior 设计的钩子,并非为 Page() 设计。当用它替换 defFields.onShow 时:
- 捕获的
originalOnShow引用,在异步回调里通过.apply(this, arguments)调用时,上下文绑定出现了未文档化的异常——调用动作本身不报错,但this指向的内部状态链已经断了 - Page 的内部生命周期链(store 绑定、checkLogin、数据加载)在
originalOnShow被替换的瞬间就已脆化,即便最终「调了」也无效
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 是为此场景而生的正规 API。守卫逻辑与页面完全解耦:页面 onShow 照常跑,Component 在旁边独立做权限判断。30+ 个页面只需在 WXML 加一行标签,无需任何 JS 改动。
一句话外卖
在微信小程序里用
definitionFilter拦截 Page 的onShow是未定义行为区域——originalOnShow调了也会静默失效;要在 Page 层做统一横切逻辑,正路是独立 Component +pageLifetimes.show,让页面逻辑和守卫逻辑各管各的。