vue

vue前端权限管理和动态路由

内容纲要

在毕设中使用了vue做为前端,不免会遇到权限管理的问题,以及动态路由和动态页面问题

vue使用

vue很适合开发的单页网页应用,并且官网有详细的文档教程,入门友好

权限管理

先说下设计思路,使用vue-router路由拦截请求。用户登录时记录下服务器返回的token或者session,保存到cookie中。然后每次路由跳转时,先获取登录状态,然后再对比权限路径。但是这样做有个缺点,应用特别大的时候,会变慢。至于后端的数据都是进行权限拦截的,就算前端越过拦截,还是无法获取数据。

具体实现

  • 占坑,有时间填坑,
  • 占坑,前端权限已经实现

更新

利用 Vue Router

Vue Router 中提供了路由导航守卫,在每次链接转跳的时候可以进行检查

全局前置守卫 router.beforeEach(),当一个导航触发时,全局前置守卫按照创建顺序调用。守卫是异步解析执行,此时导航在所有守卫 resolve 完之前一直处于 等待中。

具体用法:

const router = new VueRouter({ ... })

router.beforeEach((to, from, next) => {
  // ...
})

每个守卫方法接收三个参数:

  • to: Route: 即将要进入的目标 路由对象
  • from: Route: 当前导航正要离开的路由
  • next: Function: 一定要调用该方法来 resolve 这个钩子。执行效果依赖 next 方法的调用参数。
  • next(): 进行管道中的下一个钩子。如果全部钩子执行完了,则导航的状态就是 confirmed (确认的)。
  • next(false): 中断当前的导航。如果浏览器的 URL 改变了 (可能是用户手动或者浏览器后退按钮),那么 URL 地址会重置到 from 路由对应的地址。
  • next('/') 或者 next({ path: '/' }): 跳转到一个不同的地址。当前的导航被中断,然后进行一个新的导航。你可以向 next 传递任意位置对象,且允许设置诸如 replace: truename: 'home' 之类的选项以及任何用在 router-link 的 to prop 或 router.push 中的选项。
  • next(error): (2.4.0+) 如果传入 next 的参数是一个 Error 实例,则导航会被终止且该错误会被传递给 router.onError() 注册过的回调。

确保要调用 next 方法,否则钩子就不会被 resolved。 如果这个next方法没有调用,网页无法完成跳转。

使用 Vue Router 路由元信息

我在meta字段里面定义了roles字段完成动态路由的生成,具体用法如下。

const router = new VueRouter({
  routes: [
    {
      path: '/foo',
      component: Foo,
      children: [
        {
          path: 'bar',
          component: Bar,
          // a meta 
          meta: { roles: ['ROLE_ADMIN', 'ROLE_TEA'] }
        }
      ]
    }
  ]
})

具体路由守卫代码。

import router from './router'
import store from './store'
import {Message} from 'element-ui'
import NProgress from 'nprogress'
import 'nprogress/nprogress.css'
import {getToken} from './utils/auth'
import getPageTitle from './utils/get-page-title'

import {whiteList} from './utils/validate'
NProgress.configure({showSpinner: false});



router.beforeEach(async (to, from, next) => {

    NProgress.start();

    document.title = getPageTitle(to.meta.title);

    //确定用户是否已登录
    const hasToken = getToken();

    if (hasToken) {
        /*检查登录状态*/
        if (to.path === '/login') {
            //如果已登录,请重定向到主页

            next({path: '/home'});

            NProgress.done()

        } else {
            const hasRoles = store.getters.roles && store.getters.roles.length > 0;

            if (hasRoles) {

                next()

            } else {
                try {
                    //获取用户信息

                    const {role} = await store.dispatch('user/getInfo');

                    /*加载权限路径*/

                    const authorityRouter = await store.dispatch('permission/generateRoutes', role);

                    router.addRoutes(authorityRouter);

                    next({...to, replace: true})

                } catch (error) {

                    //删除令牌并转到登录页面以重新登录
                    await store.dispatch('user/resetToken');

                    Message.error(error || 'Has Error');

                    next(`/login?redirect=${to.path}`);
                    NProgress.done()
                }
            }
        }
    } else {
        /* has no token*/
        //

        if (whiteList(to.path)) {

        //在登录白名单中,直接进入
          next();
          NProgress.done();

        } else {

         //将其他无权访问的页面重定向到登录页面.
          next(`/public?redirect=${to.path}`);
          NProgress.done()
        }

    }
})

router.afterEach(() => {
    // 完成进度栏
    NProgress.done()
})

这里使用到了js-cookie保存token信息,并且使用了 Vuex 状态管理。前者直接调用,后者使用如下:

store.dispatch() 调用路径位于store 是全局注册Vuex.Store 中的Module部分(具体用法)。

这里使用到的:

  • store.dispatch('user/getInfo');是获取当前用户角色信息,保存到Vuex中进行全局管理。
  • store.dispatch('permission/generateRoutes', role),根据角色动态加载路由。
  • store.dispatch('user/resetToken'),移除状态信息。

Vuex模块注册

代码:

import Vue from 'vue'
import Vuex from 'vuex'
Vue.use(Vuex)
export default new Vuex.Store({
  state: {/*保存数据,全局*/
  },
  getters,
  mutations: {/*修改state*/
  },
  /**
   * Action 提交的是 mutation,而不是直接变更状态。
   * Action 可以包含任意异步操作
   */
  actions: {
  },
  /**
   * 模块
   */
  modules: {
    user,
    permission,
  }
})

user 模块:

import {getInfo, logout, postFrom} from '../../api/api'
import { getToken, setToken, removeToken } from '../../utils/auth'
import { resetRouter } from '../../router'


const state = {
  token: getToken(),
  name: '',
  introduction: '',
  roles: []/*做权限管理*/
}

const mutations = {
  SET_TOKEN: (state, token) => {
    state.token = token
  },
  SET_INTRODUCTION: (state, introduction) => {
    state.introduction = introduction
  },
  SET_NAME: (state, name) => {
    state.name = name
  },
  SET_AVATAR: (state, avatar) => {
    state.avatar = avatar
  },
  SET_ROLES: (state, roles) => {
    state.roles = roles
  }
}

const actions = {
  // 用户登录 异步
  login({commit}, userInfo) {


    return new Promise((resolve, reject) => {

      postFrom("/login", userInfo)
          .then(response => {
            const {data} = response
            commit('SET_TOKEN', data.token)//使用用户名
            setToken(data.token)
            resolve(response)
            // this.getInfo(commit)
          })
          .catch(errors => {
            reject(errors)
          })
    })
  },

  // 获取用户信息
  getInfo({ commit, state }) {
    return new Promise((resolve, reject) => {

      getInfo(state.token).then(response => {
        const { data } = response;
        if (!data) {
          reject('验证失败,请重新登录.')
        }else if(data.code === 0 ){
          reject(data.message);
          this.resetToken(commit)
        }

      const { role, message} = data;

        // 角色必须是非空数组

        if (!role || role.length <= 0) {
          reject(message)
        }

        commit('SET_ROLES', role);
        commit('SET_NAME', data.data.name);
        // commit('SET_AVATAR', avatar)
        // commit('SET_INTRODUCTION', introduction)

        resolve(data)
      }).catch(error => {
        reject(error)
      })
    })
  },

  // 用户注销
  logout({ commit, state, dispatch }) {
    return new Promise((resolve, reject) => {
      logout(state.token).then(() => {
        commit('SET_TOKEN', null);
        commit('SET_ROLES', []);
        removeToken();
        resetRouter();

        dispatch('tagsView/delAllViews', null, { root: true })

        resolve()
      }).catch(error => {
        reject(error)
      })
    })
  },

  // 删除令牌
  resetToken({ commit ,dispatch}) {
    return new Promise(resolve => {
      dispatch('tagsView/delAllViews', null, { root: true })
      commit('SET_TOKEN', null);
      commit('SET_ROLES', []);
      removeToken();
      resolve();
    })
  },

};

export default {
  namespaced: true,
  state,
  mutations,
  actions
}

permission模块:

import { asyncRoutes, publicRoutes } from '../../router'

/**
 * 使用meta.role确定当前用户是否具有权限
 * @param roles
 * @param route
 */
function hasPermission(roles, route) {

  if (route.meta && route.meta.roles) {

    return roles.some(role => route.meta.roles.includes(role))
  } else {

    return true
  }
}

/**
 * 
 * 动态获取路由,根据路由元信息  meta:中的roles
 * @param routes
 * @param roles 
 * @returns {[]}
 */
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: [],
  addRoutes: []
}

const mutations = {
  SET_ROUTES: (state, routes) => {

    state.addRoutes = routes
    state.routes = publicRoutes.concat(routes)
  }
}

const actions = {
  generateRoutes({ commit }, roles) {
    return new Promise(resolve => {
      let accessedRoutes
      if (roles.includes('ROLE_ADMIN') || roles.includes('ROLE_TEA')||roles.includes('ROLE_STU')) {


        accessedRoutes = filterAsyncRoutes(asyncRoutes, roles)

      } else {

        accessedRoutes = filterAsyncRoutes(asyncRoutes, roles)
      }
      commit('SET_ROUTES', accessedRoutes)
      resolve(accessedRoutes)
    })
  }
}

export default {
  namespaced: true,
  state,
  mutations,
  actions
}

把生成路由信息加载到Router

你可能注意到router.addRoutes(authorityRouter); 这是vue router 提供动态添加路由的方法(具体使用方式

除了拦截需要权限的路径,还必须开放公共部分的的路由。下面是实现代码。

/**
 * 验证 whiteList,可以在reg里面添加正则验证
 * @param path
 * @returns {boolean}
 */
export function whiteList(path) {
    const reg = /([public]|[password]|[login]|[404])/
    return reg.test(path);

}

最后,

到这里基本上已经完成了动态路由和前端权限管理的实现,这里只是基本的使用。有问题欢迎在下面留言。

马上对线!

Title - Artist
0:00