必须拦截 uni.navigateto、redirectto、relaunch、switchtab 四个 api 才生效,仅拦 navigateto 无效;需用 invoke 钩子同步干预,路径比对前要标准化(split('?')[0] 或补 pages/ 前缀),token 需类型+内容双重校验,tabbar 和分享场景需 onshow/onload 兜底校验。

uni.addInterceptor 必须拦截哪几个 API 才算真正生效
只拦 uni.navigateTo 是无效的——小程序里用户可能用 uni.redirectTo 跳转注销页,用 uni.reLaunch 回首页,甚至点 tabbar 触发 uni.switchTab。漏掉任意一个,权限逻辑就被绕过。
必须显式注册全部四个:
uni.addInterceptor('navigateTo', { invoke: checkAuth })uni.addInterceptor('redirectTo', { invoke: checkAuth })uni.addInterceptor('reLaunch', { invoke: checkAuth })uni.addInterceptor('switchTab', { invoke: checkAuth })
invoke 是唯一能同步干预跳转的钩子;success/fail 是跳完才触发,拦不住。
路径匹配为什么总失败?关键在 query 和前缀处理
常见错误是直接拿 args.url 去比对白名单,比如 /pages/user/profile?id=123 和白名单 ['/pages/user/profile'] 对不上——因为没截掉问号后的内容。
正确做法是先标准化路径:
const path = args.url.split('?')[0]
另外注意:uni.switchTab 的 args.url 不带 pages/ 前缀(如传入 '/tabBar/mine'),而你在 pages.json 里声明的是 "path": "pages/tabBar/mine"。建议统一白名单为绝对路径,并在比对前 normalize:
- 对
navigateTo/redirectTo/reLaunch:用split('?')[0] - 对
switchTab:手动补pages/前缀再比对,或白名单直接写成['/tabBar/mine'](需和实际 tabBar 配置一致)
token 校验不能只看“有没有”,要看“能不能用”
uni.getStorageSync('token') 返回 null、undefined、空字符串、甚至 JSON 字符串(比如存了 {"token":"xxx"})都算无效。必须做类型+内容双重判断:
const token = uni.getStorageSync('token')
if (typeof token !== 'string' || !token.trim()) {
// 未登录
}
如果后端返回的是对象,记得先 JSON.parse() 再取字段;过期时间字段如 exp 是秒级时间戳,别忘了乘 1000 转毫秒:
if (Date.now() > exp * 1000) {
// 已过期
}
tabbar 页面和分享卡片场景怎么补漏?
uni.addInterceptor 拦不住两件事:一是用户点击底部 tabbar(小程序底层不走 uni.switchTab 调用),二是从分享卡片、Scheme 或 Push 唤起的页面——这些场景下页面已开始渲染,invoke 根本不执行。
应对方式很务实:
- 所有 tabbar 页面的
onShow里加一次校验:检查 token,无效则uni.navigateTo({ url: '/pages/auth/login' }) - 登录页必须加入白名单,否则
invoke里跳转它时会死循环 - 分享卡片拉起的页面,靠
onLoad里的校验兜底——虽然有短暂白屏,但比放行更安全
真正的难点不在代码怎么写,而在路径字符串是否和 pages.json 完全一致:大小写、开头斜杠、无 .vue 后缀、无多余空格——错一个字符就报 errCode: -1, errMsg: "page is not found"。每次改路径,务必同步更新拦截白名单和 pages.json。











