上一篇:Vue-Element-Admin目录结构

登录

在讲解权限控制之前,先讲解一下用户是如何登录的,浏览器是如何保存状态的:
当用户第一次进入页面时,此时浏览器缓存中时没有任何数据的,所以会让用户进行登录。
用户在输入完账号密码后,点击登录,此时应该请求后端接口实现登录。但是此时我们应该要拦截这个事件,在这个事件请求之前或请求之后应该进行一些操作,比如状态存储或者其他的事件。
所以我们将login事件封装到vuex中,然后在登录界面中使用vuex中的login事件,在登录成功后,将用户信息存储到vuex中,然后跳转到首页(或上一个页面)。
vuex:user.js

import { login, logout, getInfo } from '@/api/user'
import { getToken, setToken, removeToken } from '@/utils/auth'
import router, { resetRouter } from '@/router'
const state = {
token: getToken(),
name: '',
avatar: '',
introduction: '',
roles: [],
botNumber: ''
}

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
},
SET_BOTNUMBER: (state, botNumber) => {
state.botNumber = botNumber
}
}

const actions = {
// user login
login({ commit }, userInfo) {
const { username, password, verify_code, verify_code_id } = userInfo
return new Promise((resolve, reject) => {
login({ username: username.trim(), password: password, verify_code, verify_code_id }).then(response => {
// console.log(response.token)
commit('SET_TOKEN', response.token)
setToken(response.token)
resolve()
}).catch(error => {
console.log(error)
reject(error)
})
})
},

// get user info
getInfo({ commit, state }) {
return new Promise((resolve, reject) => {
getInfo().then(res => {
if (!res) reject('Verification failed, please Login again.')
const Roles = []
for (const item of res.authorities) Roles.push(item.authority_name)
if (Roles.length === 0) reject('getInfo: roles must be a non-null array!')
commit('SET_ROLES', res.roles = Roles)
commit('SET_NAME', res.nick_name)
commit('SET_AVATAR', res.user_avatar || 'https://img1.baidu.com/it/u=2180828429,3689566301&fm=26&fmt=auto')
resolve(res)
}).catch(error => {
reject(error)
})
})
},

// user logout
logout({ commit, state, dispatch }) {
return new Promise((resolve, reject) => {
logout(state.token).then(() => {
commit('SET_TOKEN', '')
commit('SET_ROLES', [])
removeToken()
resetRouter()
dispatch('tagsView/delAllViews', null, { root: true })
resolve()
}).catch(error => {
reject(error)
})
})
},

// remove token
resetToken({ commit }) {
return new Promise(resolve => {
commit('SET_TOKEN', '')
commit('SET_ROLES', [])
removeToken()
resolve()
})
},

// dynamically modify permissions
async changeRoles({ commit, dispatch }, role) {
const token = role + '-token'

commit('SET_TOKEN', token)
setToken(token)
console.log('11')
const { roles } = await dispatch('getInfo')
console.log('22')
resetRouter()

// generate accessible routes map based on roles
const accessRoutes = await dispatch('permission/generateRoutes', roles, { root: true })
// dynamically add accessible routes
router.addRoutes(accessRoutes)

// reset visited views and cached views
dispatch('tagsView/delAllViews', null, { root: true })
}
}

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


但是如果在登录的时候请求用户的数据的话,那么岂不是每次都要让用户登录,或者是让浏览器一直存着用户的账号密码?显然是错误的,所以我们需要请求一个加密后的token,然后通过这个token请求用户的数据。所以在vuex:user.js中封装了一个getInfo函数。在用户登录完成后,将后端返回的token存在本地缓存中,然后再调用getInfo获取用户的信息,将用户的名字,权限,头像等信息存储到vuex中。
此时就完成了用户的登录,并且获取到了用户的信息
同理,此文件中的logoutremovaToken也就是将浏览器中的数据清空,如果后端还有一些控制的话,那么可以继续进行网络请求。

用户已登录

以上将的是用户首次登陆,如果用户已经登录,那么就不需要再次登录,直接调用getInfo获取用户的信息即可。
关键是什么时候调用:
permission.js

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'
NProgress.configure({ showSpinner: false })

const whiteList = ['/login', '/auth-redirect']

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: '/' })
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) {
console.log('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()
}
}
})

router.afterEach(() => {
// finish progress bar
NProgress.done()
})


这里就是典型的权限控制代码,首先先获取本地用户的token,然后判断是否存在,如果不存在,那么就直接跳转到登录页面, 说明用户此时一定没有登录。若本地有缓存,那么就去获取vuex中的权限,若有对应的权限,则放行;若用户的权限为空,则有两种情况,1. 用户刷新了页面,vuex中的状态被刷新了,2. 用户根本没有被分配权限, 如果是2的话,那么就跳转到登录页面,说明管理员并没有为这个用户分配权限,应该提示用户联系管理员。而如果是1的话,我们应该把vuex中的权限请求到,所以会调用vuex:user.getInfo获取用户的权限列表,然后将这个列表传入到另外一个函数中进行处理,这个函数就是generateRoutes,这个函数会返回一个新的路由列表,这个新的路由列表就是我们的权限控制的路由列表,然后将这个路由列表传入到router.addRoutes中,这样就可以控制用户的访问了。

vuex:permission

import { asyncRoutes, constantRoutes } from '@/router'

/**
* Use meta.role to determine if the current user has permission
* @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
}
}

/**
* Filter asynchronous routing tables by recursion
* @param routes asyncRoutes
* @param roles
*/
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 = constantRoutes.concat(routes)
}
}

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

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


在此文件中,我们将router中所有的路由拿过来,如果在这个权限列表中有admin, 那么就直接将所有的异步路由都拿过来。否则,我们需要过滤掉所有的异步路由,然后再拿过来,这样就可以控制用户的访问了。
filterAsyncRoutes函数的功能就是将路由过滤,dfs算法。
function hasPermission(roles, route) {
if (route.meta && route.meta.roles) {
// 遍历所有用户的权限,如果当前路由包含一个用户的权限,那么就返回true
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中
res.push(tmp)
}
})
// 将过滤后的路由返回
return res
}

此时我们就将所有的异步路由都拿过来了,然后再过滤掉不能访问的路由。
直接next放行就可以了,应该在公共路由中有一个404页面,所以访问到router中没有的路由,就会重定向到404页面。

{ path: '*', redirect: '/404', hidden: true }

以上就是用户权限控制的全部过程。


下一篇:Vue-Element-Admin-添加路由