如何更好的理解 beforeEach 全局前置守卫,在处理路由跳转前触发,怎么实现常用的全局权限校验、登录状态检查的呢?

以下将深入讲解 Vue Router 的全局前置守卫 beforeEach 在权限系统中的实现原理和实战应用,结合企业级项目代码进行拆解(基于 Vue 3 + TypeScript + Pinia)。


一、前置守卫核心机制

1.1 执行时机与特性

全局前置守卫在路由跳转前触发,具有以下特性:

  • 拦截所有导航:包括手动 URL 输入、router-link 跳转、编程式导航(router.push
  • 异步解析:支持 Promise 链式调用,适用于需要等待接口响应的场景
  • 拦截优先级:触发顺序为 组件内 beforeRouteLeave全局 beforeEach路由独享 beforeEnter
1.2 参数解析
router.beforeEach((to, from, next) => {
  // to: 目标路由对象(含 path/params/meta 等)
  // from: 来源路由对象
  // next: 导航控制函数
})

参数特性:

  • to.matched:获取嵌套路由的所有匹配记录(用于检查深层路由权限)
  • meta 字段扩展性:可在路由配置中自定义权限标识

二、权限校验实现步骤

2.1 路由元信息配置

在路由配置中声明权限标识:

// router.ts
const routes = [
  {
    path: '/dashboard',
    component: Dashboard,
    meta: {
      requiresAuth: true,      // 需要登录
      permissions: ['admin'], // 所需权限
      dynamicMenu: true        // 动态菜单标识
    }
  },
  {
    path: '/login',
    component: Login,
    meta: {
      guestOnly: true         // 仅允许未登录用户访问
    }
  }
]
2.2 权限校验主逻辑
// permission.ts
export const permissionGuard: NavigationGuard = async (to, from, next) => {
  const authStore = useAuthStore() // Pinia 状态管理
  
  // 1. 检查登录状态
  if (to.meta.requiresAuth && !authStore.isAuthenticated) {
    return next({ 
      path: '/login',
      query: { redirect: to.fullPath } // 记录原始目标路径
    })
  }

  // 2. 已登录用户访问登录页重定向
  if (to.meta.guestOnly && authStore.isAuthenticated) {
    return next(from.path || '/')
  }

  // 3. 动态权限加载(首次加载或Token刷新后)
  if (!authStore.permissionsLoaded) {
    try {
      await authStore.fetchPermissions()
    } catch (error) {
      next('/500') // 接口异常处理
      return
    }
  }

  // 4. 细粒度权限校验
  if (to.meta.permissions) {
    const hasPermission = authStore.userPermissions.some(perm => 
      to.meta.permissions!.includes(perm)
    )
    if (!hasPermission) {
      next('/403') // 无权限页面
      return
    }
  }

  // 5. 动态路由注入(异步加载权限路由)
  if (to.meta.dynamicMenu && !isRouteExists(to)) {
    const dynamicRoutes = await fetchDynamicRoutes()
    dynamicRoutes.forEach(route => router.addRoute(route))
    next(to.fullPath) // 重新触发导航
    return
  }

  next() // 放行
}

三、登录状态检查进阶方案

3.1 状态持久化方案
// stores/auth.ts (Pinia)
export const useAuthStore = defineStore('auth', {
  state: () => ({
    token: localStorage.getItem('token') || null,
    user: null as User | null,
    permissions: [] as string[]
  }),
  actions: {
    async login(credentials: LoginForm) {
      const { data } = await api.login(credentials)
      this.token = data.token
      this.user = data.user
      localStorage.setItem('token', data.token)
      router.push(data.redirect || '/')
    },
    logout() {
      this.token = null
      this.user = null
      localStorage.removeItem('token')
      router.replace('/login')
    }
  },
  getters: {
    isAuthenticated: state => !!state.token
  }
})
3.2 Token 自动续期
// axios 拦截器
instance.interceptors.response.use(
  response => response,
  async error => {
    const originalRequest = error.config
    if (error.response.status === 401 && !originalRequest._retry) {
      originalRequest._retry = true
      try {
        const newToken = await refreshToken()
        authStore.token = newToken
        originalRequest.headers.Authorization = `Bearer ${newToken}`
        return instance(originalRequest)
      } catch (refreshError) {
        authStore.logout()
        return Promise.reject(refreshError)
      }
    }
    return Promise.reject(error)
  }
)

四、企业级项目整合方案

4.1 路由守卫注册
// main.ts
import router from './router'
import { permissionGuard } from './permission'

router.beforeEach(async (to, from, next) => {
  // 页面加载进度条
  NProgress.start()
  
  // 执行权限校验链
  await permissionGuard(to, from, next)
  
  NProgress.done()
})

router.afterEach(() => {
  // 页面访问统计
  trackPageView(to.fullPath)
})
4.2 动态路由加载
// 获取动态路由
const fetchDynamicRoutes = async () => {
  const { data } = await api.get('/routes')
  return data.map(route => ({
    path: route.path,
    component: () => import(`@/views/${route.component}.vue`),
    meta: route.meta
  }))
}

// 处理动态路由的404情况
router.onError((error) => {
  if (/Loading chunk .+ failed/.test(error.message)) {
    window.location.reload()
  }
})

五、最佳实践与调试技巧

5.1 调试建议
// 开发环境打印导航日志
if (import.meta.env.DEV) {
  router.beforeEach((to, from) => {
    console.log('[Route]', from.path, '→', to.path)
    console.log('[Meta]', to.meta)
  })
}
5.2 常见问题处理
  1. 无限重定向问题
    检查重定向条件是否互斥:

    if (to.path === '/login' && authStore.isAuthenticated) {
      next(from.path || '/') // 避免循环
    }
    
  2. 路由切换白屏
    使用路由懒加载 + Suspense:

    <template>
      <Suspense>
        <RouterView />
      </Suspense>
    </template>
    
  3. 权限变更同步
    监听权限变化事件:

    watch(() => authStore.permissions, () => {
      router.replace(currentRoute.value.fullPath)
    })
    

六、扩展功能实现

6.1 多租户权限系统
// 路由配置增加租户标识
meta: {
  tenant: 'A',
  permissions: ['report:view']
}

// 守卫中校验租户
if (to.meta.tenant !== authStore.currentTenant) {
  next('/tenant-switch')
}
6.2 页面水印防护
router.afterEach((to) => {
  if (to.meta.sensitive) {
    applyWatermark(authStore.username)
  }
})

总结

全局前置守卫作为权限系统的核心入口,需要结合以下关键点实现:

  1. 状态管理:通过 Pinia/Vuex 实现跨组件状态共享
  2. 路由分层:静态路由 + 动态加载实现灵活权限分配
  3. 错误边界:完善的异常处理流程(401/403/404)
  4. 性能优化:路由懒加载、接口缓存、防抖处理
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

疯狂的沙粒

您的鼓励是我创作最大的动力!

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值