登录流程

1.页面发起登录请求

、页面发起登录请求,会调用vuex中的 login() 方法(@/store/modules/user.js)


// user login
  login({ commit }, userInfo) {
    const { username, password } = userInfo
    return new Promise((resolve, reject) => {
      login({ username: username.trim(), password: password }).then(response => {
        const { result } = response
        // 设置 token,作为用户已登陆的前端标识,存在 cookie 中
        commit('SET_TOKEN', result.token)
        setToken(result.token)
        resolve()
      }).catch(error => {
        reject(error)
      })
    })
  },

、全局拦截请求和响应 [有白名单] 和响应(位置:@/utils/request.js)

判断状态码code是否正确

import axios from 'axios'
import { MessageBox, Message } from 'element-ui'
import store from '@/store'
import { getToken } from '@/utils/auth'

// 创建一个AXIOS实例
const service = axios.create({
  baseURL: process.env.VUE_APP_BASE_API, // url = base url + request url
  // withCredentials: true, // 跨域请求时发送cookies
  timeout: 5000 // 请求超时
})

// request interceptor 请求拦截器
service.interceptors.request.use(
  config => {
    // 在发出请求前做的

    if (store.getters.token) {
      // 让每个请求携带 token
      // ['X-Token'] 是自定义头密钥
      // 请根据实际情况修改
      //const token = sessionStorage.getItem('X-Token')
      config.headers['X-Token'] = getToken()
    }
    return config
  },
  error => {
    // 处理请求错误
    console.log(error) // for debug
    return Promise.reject(error)
  }
)

// response interceptor 响应拦截器
service.interceptors.response.use(
  /**
   * 如果您想要获取 headers或状态之类的http信息
   * Please return  response => response
  */

  /**
   * 通过自定义代码确定请求状态
   * 这只是个例子
   * 您还可以通过http状态代码判断状态
   */
  response => {
    const res = response.data

    // 如果自定义代码不是20000,则判断为错误。
    if (res.code !== '000000') {
      Message({
        message: res.message || 'Error',
        type: 'error,code值不匹配',
        duration: 5 * 1000
      })

      // 50008: 非法token  50012: 其他客户端登录  50014: Token 过期
      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 {
      // alert("获取登录数据成功!")
      return res
    }
  },
  error => {
    console.log('err' + error) // for debug
    Message({
      message: error.message,
      type: 'error',
      duration: 5 * 1000
    })
    return Promise.reject(error)
  }
)

export default service

、若果正确,继续返回 第一步 ,login()方法,例如,将token存入cookie和vuex中

 // user login
  login({ commit }, userInfo) {
    const { username, password } = userInfo
    return new Promise((resolve, reject) => {
      login({ username: username.trim(), password: password }).then(response => {
        const { result } = response
        // 设置 token,作为用户已登陆的前端标识,存在 cookie 中
        commit('SET_TOKEN', result.token)
        setToken(result.token)
        resolve()
      }).catch(error => {
        reject(error)
      })
    })
  },

、登录步骤已经走完,现在需要路由跳转到首页,跳转前需要先全局判断是否有token存在(位置:@/permission.js)先用getToken函数读取token如果有,先判断是不是要跳转到login页面,如果是,就直接重定向到首页,如果不是则需要判断vuex里是否存在用户的信息,如果用户信息为空,则需要根据用户token重新获取

 const hasToken = getToken()
  // alert('thken')
  console.log(hasToken)
  if (hasToken) {
    window.console.log('有token, goto path is ' + to.path)
    if (to.path === '/login') {
      // 如果已登录,请重定向到主页
      window.console.log('jumper home /')
      next({ path: '/' })
      NProgress.done()
    } else {
      // 判断store里是否存在有角色信息
      const hasGetUserInfo = store.getters.name
      if (hasGetUserInfo) {
        next()
      } else {
        try {
          //  store角色信息为空,即重新获取
          window.console.log('角色为空,并获取用户信息...')
          // 获取用户信息
          //注意:角色必须是一个对象数组!例如:['admin']或,['developer','editor']
          const { roles } = await store.dispatch('user/getInfo')
          window.console.log(store.getters.roles)
          // 根据角色生成可访问路由映射
          window.console.log('get permission routes map...')
          // alert('到这来')
          const accessRoutes = await store.dispatch('permission/generateRoutes', roles)
          // 动态添加可访问路由
          window.console.log('add routes......')
          window.console.log(accessRoutes)
          window.console.log('add routes......')
          router.addRoutes(accessRoutes)

          // 破解方法,以确保addRoutes是完整的
          // 设置replace: true,这样导航就不会留下历史记录
          next({ ...to, replace: true })
        } catch (error) {
          // 移除token并转到登录页以重新登录
          await store.dispatch('user/resetToken')
          Message.error(error || 'Has Error')
          next(`/login?redirect=${to.path}`)
          NProgress.done()
        }
      }
    }
***五***、	如果没有用户信息,就会调用vuex中(@/store/modules/user.js)中的getInfo()方法来获取用户信息
// get user info
  getInfo({ commit, state }) {
    return new Promise((resolve, reject) => {
      console.log('getInfo方法state.token');
      console.log(state.token)
      getInfo(state.token).then(response => {
        const { result } = response
        console.log('获取用户信息的result')
        console.log(result)

        if (!result) {
          reject('Verification failed, please Login again.')
        }

        const { roles, name, avatar, introduction } = result

        // roles must be a non-empty array
        if (!roles || roles.length <= 0) {
          reject('getInfo: 角色必须是非空数组!')
        }

        commit('SET_ROLES', roles)
        commit('SET_NAME', name)
        commit('SET_AVATAR', avatar)
        commit('SET_INTRODUCTION', introduction)
        resolve(result)
      }).catch(error => {
        reject(error)
      })
    })
  },

、vuex中的getInfo()方法中又调用了(@/api/user.js)中的getInfo()方法并传入了用户的token标识

export function getInfo(token) {
  return request({
    url: '/user/info',
    method: 'get',
    withCredentials: true,
    params: { token }

  })
}

、在mock(位置:mock/user.js)中定义接口返回的数据,返回数据一定要有roles权限,且是数组

const users = {
  'admin-token': {
    roles: ['admin'],
    introduction: 'I am a super administrator',
    avatar: 'https://wpimg.wallstcn.com/f778738c-e4f8-4870-b634-56703b4acafe.gif',
    name: 'Super Admin'
  },
  'editor-token': {
    roles: ['editor'],
    introduction: 'I am an editor',
    avatar: 'https://wpimg.wallstcn.com/f778738c-e4f8-4870-b634-56703b4acafe.gif',
    name: 'Normal Editor'
  }
}
//此时传入的token为admin-token所以返回的数据info为users['admin-token']
// get user info
  {
    url: '/user/info\.*',
    type: 'get',
    response: config => {
      console.log('获取用户信息')
      const { token } = config.query
      const info = users[token]

      // mock error
      if (!info) {
        return {
          code: 50008,
          message: 'Login failed, unable to get user details.'
        }
      }
      return {
        code: '000000',
        result:info
      }
    }
  },

、然后数据请求成功,重新返回***五***、vuex中(@/store/modules/user.js)中的getInfo()方法成功来继续下一步操作将这些数据存入vuex

// get user info
  getInfo({ commit, state }) {
    return new Promise((resolve, reject) => {
      console.log('getInfo方法state.token');
      console.log(state.token)
      getInfo(state.token).then(response => {
        const { result } = response
        console.log('获取用户信息的result')
        console.log(result)

        if (!result) {
          reject('Verification failed, please Login again.')
        }

        const { roles, name, avatar, introduction } = result

        // roles must be a non-empty array
        if (!roles || roles.length <= 0) {
          reject('getInfo: 角色必须是非空数组!')
        }

        commit('SET_ROLES', roles)
        commit('SET_NAME', name)
        commit('SET_AVATAR', avatar)
        commit('SET_INTRODUCTION', introduction)
        resolve(result)
      }).catch(error => {
        reject(error)
      })
    })
  },

、此时获取用户token已经成功,拿用户token来获取用户信息也成功,所以现在走进 、顺着方法的执行顺序继续往下执行,发现又调用了一个方法(位置:@/store/modules/permission.js)下的generateRoutes方法
第四步代码

const hasToken = getToken()
  // alert('thken')
  console.log(hasToken)
  if (hasToken) {
    window.console.log('有token, goto path is ' + to.path)
    if (to.path === '/login') {
      // 如果已登录,请重定向到主页
      window.console.log('jumper home /')
      next({ path: '/' })
      NProgress.done()
    } else {
      // 判断store里是否存在有角色信息
      const hasGetUserInfo = store.getters.name
      if (hasGetUserInfo) {
        next()
      } else {
        try {
          //  store角色信息为空,即重新获取
          window.console.log('角色为空,并获取用户信息...')
          // 获取用户信息
          //注意:角色必须是一个对象数组!例如:['admin']或,['developer','editor']
          const { roles } = await store.dispatch('user/getInfo')
          window.console.log(store.getters.roles)
          // 根据角色生成可访问路由映射
          window.console.log('get permission routes map...')
          // alert('到这来')
          const accessRoutes = await store.dispatch('permission/generateRoutes', roles)
          // 动态添加可访问路由
          window.console.log('add routes......')
          window.console.log(accessRoutes)
          window.console.log('add routes......')
          router.addRoutes(accessRoutes)

          // 破解方法,以确保addRoutes是完整的
          // 设置replace: true,这样导航就不会留下历史记录
          next({ ...to, replace: true })
        } catch (error) {
          // 移除token并转到登录页以重新登录
          await store.dispatch('user/resetToken')
          Message.error(error || 'Has Error')
          next(`/login?redirect=${to.path}`)
          NProgress.done()
        }
      }
    }

本次走到

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

这个地方

、这个方法的目的就是通过请求后端数据获取异步路由,拿着用户的roles权限数组去遍历和整合一个全新的侧边栏
虽然这里我自定义了一个函数getAuthMenu模拟向mock发送数据,但返回的结果并不是异步路由的列表,只是一个为000000的状态码,只是一个模拟,也可以像原本的那样,不要

  // 根据用户权限异步生成全新路由
  generateRoutes({ commit }, roles) {
    // alert("store")
    return new Promise(resolve => {
      // 先查询后台并返回左侧菜单数据并把数据添加到路由
      getAuthMenu(state.token).then(response => {
        // let data = response
        console.log(response)
        if (response.code !== '000000') {   //请求失败
          console.log(1)
        } else {
          // 模拟加载成功,返回的路由
          let accessedRoutes
          console.log('判断侧边栏异步加载权限的roles')
          console.log(roles)
          
          if (roles.includes('admin')) {	//如果roles权限数组里包含admin则将获取整个异步路由的权限,当没有异步路由时,就定义为一个空数组
            accessedRoutes = asyncRoutes || []	
          } else {	//如果roles不包含admin就说明这个用户不是最高权限,需要过滤掉他没有权限的路由
            accessedRoutes = filterAsyncRoutes(asyncRoutes, roles)	//这里类似过滤器
          }
          commit('SET_ROUTES', accessedRoutes)	//将整理好的异步路由放入vuex
          resolve(accessedRoutes)	//将整理好的异步路由返回给调用者
        }
      })
    })
  }

十一、异步路由已经整合过滤完成,下面就是合并路由了,看第 *** 四 *** 步的下一步会发现现在进入了
第四步代码

const hasToken = getToken()
  // alert('thken')
  console.log(hasToken)
  if (hasToken) {
    window.console.log('有token, goto path is ' + to.path)
    if (to.path === '/login') {
      // 如果已登录,请重定向到主页
      window.console.log('jumper home /')
      next({ path: '/' })
      NProgress.done()
    } else {
      // 判断store里是否存在有角色信息
      const hasGetUserInfo = store.getters.name
      if (hasGetUserInfo) {
        next()
      } else {
        try {
          //  store角色信息为空,即重新获取
          window.console.log('角色为空,并获取用户信息...')
          // 获取用户信息
          //注意:角色必须是一个对象数组!例如:['admin']或,['developer','editor']
          const { roles } = await store.dispatch('user/getInfo')
          window.console.log(store.getters.roles)
          // 根据角色生成可访问路由映射
          window.console.log('get permission routes map...')
          // alert('到这来')
          const accessRoutes = await store.dispatch('permission/generateRoutes', roles)
          // 动态添加可访问路由
          window.console.log('add routes......')
          window.console.log(accessRoutes)
          window.console.log('add routes......')
          router.addRoutes(accessRoutes)

          // 破解方法,以确保addRoutes是完整的
          // 设置replace: true,这样导航就不会留下历史记录
          next({ ...to, replace: true })
        } catch (error) {
          // 移除token并转到登录页以重新登录
          await store.dispatch('user/resetToken')
          Message.error(error || 'Has Error')
          next(`/login?redirect=${to.path}`)
          NProgress.done()
        }
      }
    }

现在走到了

router.addRoutes(accessRoutes)

这是vue-router的一个方法,把本地原本的路由和异步获取的路由合并成一个路由

Logo

华为开发者空间,是为全球开发者打造的专属开发空间,汇聚了华为优质开发资源及工具,致力于让每一位开发者拥有一台云主机,基于华为根生态开发、创新。

更多推荐