【Vue-Element-Admin 分析】- 03 权限管理是怎么实现的?

1,031 阅读3分钟

前言

想必大家都知道,在 vue 中实现权限管理的核心方法是 vue-router 中的路由守卫,那么这里我们全局搜索一下 vue-router 的钩子看看有什么线索:

图片1
图片1

显然,在 vue-element-admin 中,实现权限管理的地方是 src/permission.js 中。

分析

可以看到,这里的宏观架构如下所示:

router.beforeEach(async (tofromnext) => {
 /* do something */
})

router.afterEach(() => {
  /* do something */
})

其中 afterEach 中做的事很简单:NProgress.done(),作用是结束顶部的 router 跳转进度条。

接下来我们看看 beforeEach 中具体的逻辑是怎么样的。

router.beforeEach(async (tofromnext) => {
  NProgress.start()
  document.title = getPageTitle(to.meta.title)
  const hasToken = getToken()

  if (hasToken) {
    if (to.path === '/login') {
      next({ path: '/' })
      NProgress.done()
    } else {
      const hasRoles = store.getters.roles && store.getters.roles.length > 0
      if (hasRoles) {
        next()
      } else {
        try {
          const { roles } = await store.dispatch('user/getInfo')

          const accessRoutes = await store.dispatch(
            'permission/generateRoutes',
            roles
          )

          router.addRoutes(accessRoutes)

          next({ ...to, replace: true })
        } catch (error) {
          await store.dispatch('user/resetToken')
          Message.error(error || 'Has Error')
          next(`/login?redirect=${to.path}`)
          NProgress.done()
        }
      }
    }
  } else {
    if (whiteList.indexOf(to.path) !== -1) {
      next()
    } else {
      next(`/login?redirect=${to.path}`)
      NProgress.done()
    }
  }
})

乍一眼似乎有很多的 if-else 嵌套,实际上这里的逻辑已经写的相当清晰了,仔细看一下就能分析出其中的逻辑:

图片2
图片2

这里有一些小细节值得一提:

  • 在重定向 next 之后要手动的结束 NProgress,具体原因可以看看这个 issue
  • next({ ...to, replace: true })中 ,设置 replace: true 的作用是清除导航记录

接下来我们顺着上面的思路,深入去看看这里是如何动态生成路由的:

generateRoutes

generateRoutes({ commit }, roles) {
  return new Promise(resolve => {
    let accessedRoutes
    if (roles.includes('admin')) {
      accessedRoutes = asyncRoutes || []
    } else {
      accessedRouteszuizho  = filterAsyncRoutes(asyncRoutes, roles)
    }
    commit('SET_ROUTES', accessedRoutes)
    resolve(accessedRoutes)
  })
}de   

显然,accessedRoutes 就是最终生成的路由表。这里的逻辑也很简单,根据权限确定是否需要过滤 asyncRoutes 中的部分路由即可,而 asyncRoutes,我们可以看到,它来自 router 中:

export const asyncRoutes = [
 /* ... */
]

是一个定义好的路由表,再结合上面的逻辑,不难看出,当权限为 admin 的时候,能访问所有路由,否则讲有部分路由根据对应权限被过滤掉。

于是我们来看看过滤逻辑:

export function filterAsyncRoutes(routes, roles) {
  const res = []

  routes.forEach(route => {
    const tmp = { ...route }
    if (hasPermission(roles, tmp)) {
      if (tmp.children) {
        tmp.children = filterAsyncRoutes(tmp.children, roles)
      }
      res.push(tmp)
    }
  })

  return res
}

显然,这里通过递归遍历整个路由表,如果有权限,就将其存入路由表中,否则直接过滤掉。

最后再回到 src/permission.js 中,再获取到动态生成的路由表后,通过 router.addRoutes() 将其添加到路由中,这样就完成了整个动态路由生成的流程。

最后补上脑图:

图片3
图片3