后台系统总结,登录权限篇

186 阅读1分钟

前言

后台系统是基于vue-tao-admin开发的,从这个项目里学到了很多东西,正好也测试一下自己的后台系统功能,所以写一篇博客记录一下。

登录

登录功能的逻辑基本和原系统一样,首先前端获取用户名密码然后后端返回一个唯一token用于识别用户(这里我是直接用mongdb的_id充当token),之后把token存储到本地cookie的同时根据token向服务器获取用户信息,这个信息每次刷新浏览器就要重新获取。

handleLogin () {
      this.$refs.loginForm.validate(valid => {
        if (valid) {
          this.loading = true
          this.$store.dispatch('user/login', this.loginForm).then(() => {
            this.$router.push({ path: this.redirect || '/' })
            this.loading = false
          }).catch(() => {
            this.loading = false
          })
        } else {
          console.log('error submit!!')
          return false
        }
      })
    }

权限


权限对于后台系统来说很重要,本系统的实现思路和原系统也是相同的。

用户登录之后,通过 token 获取用户的 role ,动态根据用户的 role 算出其对应有权限的路由,再通过router.addRoutes动态挂载路由。

addroutes()

addroutes在最新版的vue-router里已经被弃用了,但是因为懒这里还是使用addroutes而不是最新的addroute。

在使用addroutes的时候遇到了一些问题,之后我去看了addroutes的源码才解决。

function addRoutes (routes) {
    createRouteMap(routes, pathList, pathMap, nameMap)
  }
function createRouteMap (
  routes,
  oldPathList,
  oldPathMap,
  oldNameMap
) {
  // the path list is used to control path matching priority
  var pathList = oldPathList || [];
  // $flow-disable-line
  var pathMap = oldPathMap || Object.create(null);
  // $flow-disable-line
  var nameMap = oldNameMap || Object.create(null);

  routes.forEach(function (route) {
    addRouteRecord(pathList, pathMap, nameMap, route);
  });

  // ensure wildcard routes are always at the end
  for (var i = 0, l = pathList.length; i < l; i++) {
    if (pathList[i] === '*') {
      pathList.push(pathList.splice(i, 1)[0]);
      l--;
      i--;
    }
  }

  return {
    pathList: pathList,
    pathMap: pathMap,
    nameMap: nameMap
  }
}

根据上面源码可以看出来addroutes方法只是根据你传入的routes数组生成pathlist然后和之前的pathlist进行合并。它并没有修改router对象里的options对象,即没有把新的路由数组和之前的路由数组合并,但是可以通过路由信息访问这几个路由。 所以在后续动态生成可访问路由时不能根据routes里的options对象直接生成,而是要手动添加。

addRoute()

最后还是用了addRoute()重构,addRoute除了一次只能传入一个路由以为和addRoutes没有太大区别

具体实现

  1. 首先路由表里要有两种数组,constantRoutesasyncRoutes,分别存储无权限路由和有权限路由,区别在于有权限路由里多了roles这一meta信息。

    export const constantRoutes = [
      {
    	path: '/login',
    	component: () => import('@/views/login/index'),
    	hidden: true
      },
    ]
    export const asyncRoutes = [
      {
    	path: '/article',
    	component: Layout,
    	meta: {
    	  title: '文章管理'
    	},
    	children: [
    	  {
    		path: 'article-publish',
    		component: () => import('@/views/article/ArticleEdit.vue'),
    		name: 'article-publish',
    		meta: { title: '文章发布', roles: ['admin','*****'] }
    	  },
    	]
      },
    ]
    
  2. 之后在设置beforeEach全局守卫,在进入每一个路由之前都进行权限判定

    判定逻辑是:如果有token说明已经登录,如果有Info说明已经获取了信息,则直接跳转路由。否则去获取Info或者登录。

beforeEach的两个参数to,from分别代码要前往的路由和当前路由。 return true代表直接跳转,false代表拒绝跳转,return path则与router.replace(path)等方法类似。

```js
//permission.js
router.beforeEach(async(to, from) => {
  const hasToken = getToken()

  if (hasToken) {
    console.log(to,from);
    if (to.path === '/login') {
    //重定向至首页
      return '/'
    } else {
      const hasGetUserInfo = store.getters.roles || []
      if (hasGetUserInfo.length) {
        return true
      } else {
        try {
          // get user info
          await store.dispatch('user/getInfo').then(res=>{
            const roles = res.data.roles
            store.dispatch('permission/generateRoutes',roles).then((accessedRoutes)=>{
              accessedRoutes.map(item=>{
                router.addRoute(item)
              })
              return to // hack方法 确保addRoutes已完成
            })
          })
        } catch (error) {
          // remove token and go to login page to re-login
          await store.dispatch('user/resetToken')
          NProgress.done()
          return `/login?redirect=${to.path}`
        }
      }
    }
  } else {
  //即使没有登录也要有基础路由
    store.dispatch('permission/initRoutes')
    if (whiteList.indexOf(to.path) !== -1) {
      return true
    } else {
      return true
    }
  }
})
```

3. 根据权限生成路由数组并存储在vuex中 ```js //store/permission.js function hasPermission(roles, route) { if (route.meta && route.meta.roles) { return roles.some(role => route.meta.roles.includes(role)) } else { return true } }

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
}

const state = {
  routes: [],
}

const mutations = {
  SET_ROUTES: (state, routes) => {
    state.routes = constantRoutes.concat(routes)
  },
  INIT_ROUTES:(state)=>{
    state.routes = constantRoutes
  }
}

const actions = {
  generateRoutes({commit},roles){
    return new Promise((resolve)=>{
      let addRoutes = filterAsyncRoutes(asyncRoutes, roles) || []
      commit('SET_ROUTES',addRoutes)
      resolve(addRoutes)
    })
  },

  initRoutes({commit}){
    return new Promise((resolve)=>{
      commit('INIT_ROUTES')
      resolve()
    })
  }
}

```

4. 侧边栏加载

侧边栏是根据路由数组动态生成的,所以只需要把上一步生成的权限数组,根据计算方法返回即可。

(ps:引用vuex数据一定要用computed包裹,否则会失去响应性)

`const routes = computed(() => store.getters.routes)`

结语

到这里登录权限功能也就基本完成了,还有很多其他的功能没有实现,有时间慢慢来吧。