新工作内容是运维与开发公司自研的大数据平台,自研的平台是基于datax-web改造而来。去GitHub查询repo得知datax-web的前端datax-web-ui项目是由vue-element-admin 改造的。这里记录下代码方便自己学习。
这一篇先写vue-element-admin 登录流程,vue-element-admin 使用的是UI toolkit是element-ui,官方文档点击此处。
下面是原作者的编写思路,掘金原帖
进入正题,做后台项目区别于做其它的项目,权限验证与安全性是非常重要的,可以说是一个后台项目一开始就必须考虑和搭建的基础核心功能。我们所要做到的是:不同的权限对应着不同的路由,同时侧边栏也需根据不同的权限,异步生成。这里先简单说一下,我实现登录和权限验证的思路。
登录:当用户填写完账号和密码后向服务端验证是否正确,验证通过之后,服务端会返回一个token,拿到token之后(我会将这个token存贮到cookie中,保证刷新页面后能记住用户登录状态),前端会根据token再去拉取一个 user_info 的接口来获取用户的详细信息(如用户权限,用户名等等信息)。
权限验证:通过token获取用户对应的 role,动态根据用户的 role 算出其对应有权限的路由,通过 router.addRoutes 动态挂载这些路由。
上述所有的数据和操作都是通过vuex全局管理控制的。(补充说明:刷新页面后 vuex的内容也会丢失,所以需要重复上述的那些操作)接下来,我们一起手摸手一步一步实现这个系统。
前端登录界面view是src/views/login/index.vue,handleLogin是核心方法,该方法就做了两件事情:发送登录请求
和进行路由跳转
watch: {
$route: {
handler: function(route) {
const query = route.query
if (query) {
this.redirect = query.redirect
this.otherQuery = this.getOtherQuery(query)
}
},
immediate: true
}
},
methods: {
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 || '/', query: this.otherQuery })
this.loading = false
})
.catch(() => {
this.loading = false
})
} else {
console.log('error submit!!')
return false
}
})
},
getOtherQuery(query) {
// 枚举所有键名,reduce将所有非redirect的内容存到累加器acc,最后返回累加器
return Object.keys(query).reduce((acc, cur) => {
if (cur !== 'redirect') {
acc[cur] = query[cur]
}
return acc
}, {})
}
}
this.$store.dispatch('user/login', this.loginForm)
调用的是 src/store/modules/user.js 里的login action,this.loginForm是form参数this.$router.push({ path: this.redirect || '/', query: this.otherQuery })
this.redirect 是重定向的路径,如果没有就跳转到/
路径,登录的参数是this.otherQuery。this.redirect 和 this.otherQuery 是由watch监听的,
redirect=
%2Fdatax%2Fdependency%2FdependencyInfo redirect=
后面就是this.redirect的值这个方法的目的是发送promise请求给后端,根据username和password换token,将token存在vuex和cookie中
import { login, logout, getInfo } from '@/api/user'
const actions = {
// user login
login({ commit }, userInfo) {
const { username, password } = userInfo
return new Promise((resolve, reject) => {
login({ username: username.trim(), password: password }).then(response => {
// vuex存储返回的data(token)
const { data } = response
commit('SET_TOKEN', data.token)
//token还会存到Cookie中
setToken(data.token)
//promise 将pending态改为resolve态
resolve()
}).catch(error => {
reject(error)
})
})
},
}
@/api/user
,打开该文件可以发现url: '/vue-element-admin/user/login'
这个文件就是1.2.1 promise发送的真正后端api地址,这边导入的request实际上就是自行封装的axios模块,里面配置了request前置拦截器和响应拦截器
import request from '@/utils/request'
export function login(data) {
return request({
url: '/vue-element-admin/user/login',
method: 'post',
data
})
}
请求前置拦截器 作用是,每次发请求前检查vuex里是否有token,有的话把token配置进请求头
中,统一配置方便管理。带上token是后续所有请求后端都需要token验证身份。
// request interceptor
service.interceptors.request.use(
config => {
// do something before request is sent
if (store.getters.token) {
// let each request carry token
// ['X-Token'] is a custom headers key
// please modify it according to the actual situation
config.headers['X-Token'] = getToken()
}
return config
},
error => {
// do something with request error
console.log(error) // for debug
return Promise.reject(error)
}
)
response interceptor,主要是对自定义响应码的处理
response => {
const res = response.data
// if the custom code is not 20000, it is judged as an error.
if (res.code !== 20000) {
Message({
message: res.message || 'Error',
type: 'error',
duration: 5 * 1000
})
// 50008: Illegal token; 50012: Other clients logged in; 50014: Token expired;
if (res.code === 50008 || res.code === 50012 || res.code === 50014) {
// to re-login
MessageBox.confirm('You have been logged out, you can cancel to stay on this page, or log in again', 'Confirm logout', {
confirmButtonText: 'Re-Login',
cancelButtonText: 'Cancel',
type: 'warning'
}).then(() => {
store.dispatch('user/resetToken').then(() => {
location.reload()
})
})
}
return Promise.reject(new Error(res.message || 'Error'))
} else {
return res
}
},
下面开始介绍路由部分,路由部分和请求一样,也有前后拦截器,这里是router.beforeEach
和router.afterEach
。当然配置了路由白名单
beforeEach:根据是否存在token判断,没token就重定向到login页面;有token的情况下,主要工作是根据用户的角色权限进行动态路由挂载
。动态路由挂载会导致不同角色的用户进入系统的侧边栏内容不一样。
和登录一样,发送的请求先找vuex的模块下对应文件,例如获取对应角色的路由就是去src/store/modules/permission.js
。
user/getInfo 这个请求比较简单,就是抓下vuex里有没有token,根据token抓当前用户的信息。
afterEach:beforeeach每次会启动一个虚假的进度条组件nprogress,aftereach里关闭掉
const whiteList = ['/login', '/auth-redirect'] // no redirect whitelist
router.beforeEach(async(to, from, next) => {
// start progress bar
NProgress.start()
// set page title
document.title = getPageTitle(to.meta.title)
// determine whether the user has logged in
const hasToken = getToken()
if (hasToken) {
if (to.path === '/login') {
// if is logged in, redirect to the home page
next({ path: '/' })
NProgress.done() // hack: https://github.com/PanJiaChen/vue-element-admin/pull/2939
} else {
// determine whether the user has obtained his permission roles through getInfo
const hasRoles = store.getters.roles && store.getters.roles.length > 0
if (hasRoles) {
next()
} else {
try {
// get user info
// note: roles must be a object array! such as: ['admin'] or ,['developer','editor']
const { roles } = await store.dispatch('user/getInfo')
// generate accessible routes map based on roles
const accessRoutes = await store.dispatch('permission/generateRoutes', roles)
// dynamically add accessible routes
router.addRoutes(accessRoutes)
// hack method to ensure that addRoutes is complete
// set the replace: true, so the navigation will not leave a history record
next({ ...to, replace: true })
} catch (error) {
// remove token and go to login page to re-login
await store.dispatch('user/resetToken')
Message.error(error || 'Has Error')
next(`/login?redirect=${to.path}`)
NProgress.done()
}
}
}
} else {
/* has no token*/
if (whiteList.indexOf(to.path) !== -1) {
// in the free login whitelist, go directly
next()
} else {
// other pages that do not have permission to access are redirected to the login page.
next(`/login?redirect=${to.path}`)
NProgress.done()
}
}
})
router.afterEach(() => {
// finish progress bar
NProgress.done()
})
这个js文件主要工作·就是根据用户的角色获得对应的路由菜单。
vue-element-admin
的侧边栏菜单和路由是映射在一起的,再动态挂载路由。你只需要修改路由文件src/router/index.js
和用户角色 和本文件的挂载逻辑就可以根据用户角色动态挂载~
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)
})
}
}
admin
进行asyncRoutes的过滤,asyncRoutes
是动态路由。动态路由和静态路由配置文件src/router/index.js
路由挂载后,就会真正进行路由的跳转。根据重定向的结果跳转重定向页面or首页,流程结束。
核心文件其实就是