新手前臺 使用springboot2+vue-element-admin4.0 搭建後臺管理系統 實現後臺交互動態權限

前言:最近不是很忙,所以抽時間學了一下使用vue-element-admin4.0結合springboot2構建一個後臺管理頁面。

vue-element-admin4.0國內節點訪問地址:https://panjiachen.gitee.io/vue-element-admin-site/zh/

本此使用的是https://github.com/PanJiaChen/vue-element-admin/tree/i18n 國際化分支的版本。說是除了國際化其他都一樣。

本文主要介紹前臺動態的使用資源權限。

  • 後臺使用springboot2搭建項目就不說了,百度一下一大堆。
  • 前臺開發前需要安裝一下nodejs,這裏注意一下nodejs和npm的版本要相互匹配,否則在編譯的時候會報錯。

(爲什麼要說笨版本問題呢?是因爲我最開始安裝的是nodejs11.06的版本,npm是安裝的時候自帶安裝的,npm版本好像6.5。我手賤使用更新軟件把nodejs更新了,更新到了12.13,問題來了,打開ide項目無法啓動,報錯。那是因爲更新nodejs的時候沒有同步更新到相匹配的npm版本。哎。。。。。。。最後把npm更新到最新版。版本是6.12.1)

他們使用的是vscode開發的,我覺得不太好用,我用的是webstorm

打開項目後需要安裝一下依賴的模塊。

npm install 

  • 1、全局請求配置的修改。

src/utils/request.js

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

const qs = require('querystring')

// create an axios instance
const service = axios.create({
  baseURL: process.env.VUE_APP_BASE_API, // url = base url + request url
  // withCredentials: true, // send cookies when cross-domain requests
  timeout: 5000, // request timeout
  jsonData: false
})

// request interceptor
service.interceptors.request.use(
  config => {
    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()
    }

    if (config.method.toLowerCase() === 'get') {
      config.params = config.data
    } else if (config.method.toLowerCase() === 'post') {
      if (config.jsonData) {
        config.headers['Content-Type'] = 'application/json;charset=UTF-8'
        config.data = JSON.stringify(config.data)
      } else {
        config.headers['Content-Type'] = 'application/x-www-form-urlencoded;charset=UTF-8'
        config.data = qs.stringify(config.data)
      }
    }
    // console.log(config) // for debug
    return config
  },
  error => {
    // do something with request error
    console.log(error) // for debug
    return Promise.reject(error)
  }
)

// response interceptor
service.interceptors.response.use(
  /**
   * If you want to get http information such as headers or status
   * Please return  response => response
   */

  /**
   * Determine the request status by custom code
   * Here is just an example
   * You can also judge the status by HTTP Status Code
   */
  response => {
    const res = response.data
    // code==2000是業務邏輯成功,其他的返回code都是業務邏輯錯誤
    if (res.code === 5002) {
      // to re-login
      // token過期或者密碼被修改了都需要重新獲取token
      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()
        })
      })
    } else {
      return res
    }
  },
  error => {
    // console.log('err' + error) // for debug
    Message({
      message: error.message,
      type: 'error',
      duration: 5 * 1000
    })
    removeToken()
    if (error.response && error.response.status === 403) {
      this.$router.push(`/login?redirect=${this.$route.fullPath}`)
    }
    return Promise.reject(error)
  }
)

export default service

這個文件修改的主要是get、post請求傳遞參數的設置。

get使用params傳遞參數。post就data

axios默認請求是json傳遞參數,但是我的項目中一些簡單參數我還是希望是億form的形式傳遞過去。後臺接受參數時候比較簡單。

所以我添加了一個jsonData的標識在請求方法中,根據這個標識來判斷傳遞的是json還是普通的form數據。

還有就是form數據需要轉化,使用qs.stringify來轉換。轉化成A=1&B=2&c=3這樣的形式。

  • 2、後臺接口路徑的配置。

 我是本地開發環境,所以直接配置的是:VUE_APP_BASE_API = 'http://localhost:8088'

 

  • 登錄設置角色信息、過濾路由(根據角色動態的生成菜單)
  • store/user.js
import { loginByPwd, logout, getLoginUserInfo } from '@/api/user'
import { getToken, setToken, removeToken } from '@/utils/auth'
import router, { resetRouter } from '@/router'

const state = {
  token: getToken(),
  name: '',
  avatar: '',
  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 = {
  // user login
  loginByPwd({ commit }, userInfo) {
    const { userName, passWord } = userInfo

    return new Promise((resolve, reject) => {
      loginByPwd({ userName: userName.trim(), passWord: passWord }).then(response => {
        if (response.code === 2000) {
          commit('SET_TOKEN', response.data)
          setToken(response.data)
        }
        resolve(response)
      }).catch(error => {
        reject(error)
      })
    })
  },

  // get user info
  getLoginUserInfo({ commit, state }) {
    return new Promise((resolve, reject) => {
      getLoginUserInfo(state.token).then(response => {
        // const { data } = response

        // console.log('getLoginUserInfo', response)

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

        if (response.resultFlag) {
          commit('SET_ROLES', response.data.roleList)
          commit('SET_NAME', response.data.likeName)
          commit('SET_AVATAR', response.data.imgUrl)
          commit('SET_INTRODUCTION', '我是一個超級管理員哦')
          // 一個用戶可能有多個角色,這裏返回的是角色的集合信息
          // let allRole = response.data.roleList

          resolve(response)
        } else {
          console.error('獲取當前登錄用戶信息出錯了')
        }
      }).catch(error => {
        reject(error)
      })
    })
  },

  // user logout
  logout({ commit, state }) {
    return new Promise((resolve, reject) => {
      logout(state.token).then(() => {
        commit('SET_TOKEN', '')
        commit('SET_ROLES', [])
        removeToken()
        resetRouter()
        resolve()
        location.reload()
      }).catch(error => {
        reject(error)
      })
    })
  },

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

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

      commit('SET_TOKEN', token)
      setToken(token)

      const { roles } = await dispatch('getInfo')

      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 })

      resolve()
    })
  }
}

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

這個文件就是登錄,成功後獲取當前用戶擁有的資源信息。

系統中或者說網絡上現在都是直接使用roles來做的判斷,就是在router.js文件中直接把菜單需要的角色設置在裏邊。

缺點是添加了角色就要修改代碼打包發佈。這簡直是災難。

response.data.roleList 就是用戶擁有的角色信息,我這裏還是多角色。把角色信息放在全局緩存裏邊。
  • src/permission.js 

這個文件在打開每個菜單的時候都會去執行以下,檢查你的權限信息

檢查的時候,把java後臺返回的角色資源合併了一下,因爲我使用的是多角色,擁有的菜單可能有重複的,這個是最煩人的,因爲擁有的資源菜單是多維數組,在判斷的時候需要和本地前臺的系統路由進行相互判斷。

  • src/store/modules/permission.js

可以看到我用系統提供的深度克隆把系統的動態路由克隆了一份,爲什麼要這樣呢,因爲我們不能改變系統本地的動態路由,如果你有權限新建角色,如果你改變了系統的路由,那你建的角色豈不是很多菜單都沒辦法選擇。所以我深度克隆了一個全局的動態路由來操作,也不會改變原來的路由。

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

asyncRoutes是系統中定義的動態路由,需要做權限驗證在左邊菜單欄顯示的都在這個對象裏邊設置

 

看到系統默認使用roles來做權限驗證的,但是沒辦法和後臺互動起來啊。

 

重點來了:

我花了很久才搞定這裏,這是我目前想到的辦法。

/**
 *
 * @param  {Array} userRouters 後臺返回的用戶權限json
 * @param  {Array} allRouter  前端配置好的所有動態路由的集合
 * @return {Array} realRoutes 過濾後的路由
 */
function recursionRouter(userRouters = [], allRouter = []) {
  const realRoutes = []
  allRouter.forEach(sysRoute => {
    if (sysRoute.meta) {
      if (sysRoute.flag === undefined) {
        sysRoute.flag = true
      }
      userRouters.forEach(item => {
        if (item.meta && item.meta.title === sysRoute.meta.title) {
          if (item.children && item.children.length > 0) {
            sysRoute.children.concat(recursionRouter(item.children, sysRoute.children))
          }
          if (sysRoute.flag) {
            realRoutes.push(sysRoute)
            sysRoute.flag = false
          }
        }
      })
      if (sysRoute.children) {
        sysRoute.children = sysRoute.children.filter(a => a.flag === false)
      }
    } else {
      realRoutes.push(sysRoute)
    }
  })
  return realRoutes
}

就是把克隆的路由和後臺返回的路由信息進行比對,最終得到一個本登錄用戶擁有的資源信息數組,多維數組哦。。。

這是我目前寫的代碼,js好坑的 return true都沒辦法退出函數拿來判斷。

以上代碼就是本文的精華。

如果有哪位有更簡潔的更有效的方法,請回復,非常感謝。

 

還漏了一個系統添加角色:

還是系統中的添加角色方法。

這裏把選擇的資源的component屬性刪除了,太大了,而且沒有必要保存在數據庫中。

 

超級管理員添加到數據庫中的資源信息:

[{"path":"/permission","redirect":"/permission/page","alwaysShow":true,"name":"Permission","meta":{"title":"permission","icon":"lock","roles":["admin","editor"]},"children":[{"path":"page","name":"PagePermission","meta":{"title":"pagePermission","roles":["admin"]}},{"path":"directive","name":"DirectivePermission","meta":{"title":"directivePermission"}},{"path":"role","name":"RolePermission","meta":{"title":"rolePermission","roles":["admin"]}}],"flag":true},{"path":"/icon","children":[{"path":"/icon/index","name":"Icons","meta":{"title":"icons","icon":"icon","noCache":true}}],"flag":true},{"path":"/components","redirect":"noRedirect","name":"ComponentDemo","meta":{"title":"components","icon":"component"},"children":[{"path":"tinymce","name":"TinymceDemo","meta":{"title":"tinymce"}},{"path":"markdown","name":"MarkdownDemo","meta":{"title":"markdown"}},{"path":"json-editor","name":"JsonEditorDemo","meta":{"title":"jsonEditor"}},{"path":"split-pane","name":"SplitpaneDemo","meta":{"title":"splitPane"}},{"path":"avatar-upload","name":"AvatarUploadDemo","meta":{"title":"avatarUpload"}},{"path":"dropzone","name":"DropzoneDemo","meta":{"title":"dropzone"}},{"path":"sticky","name":"StickyDemo","meta":{"title":"sticky"}},{"path":"count-to","name":"CountToDemo","meta":{"title":"countTo"}},{"path":"mixin","name":"ComponentMixinDemo","meta":{"title":"componentMixin"}},{"path":"back-to-top","name":"BackToTopDemo","meta":{"title":"backToTop"}},{"path":"drag-dialog","name":"DragDialogDemo","meta":{"title":"dragDialog"}},{"path":"drag-select","name":"DragSelectDemo","meta":{"title":"dragSelect"}},{"path":"dnd-list","name":"DndListDemo","meta":{"title":"dndList"}},{"path":"drag-kanban","name":"DragKanbanDemo","meta":{"title":"dragKanban"}}],"flag":true},{"path":"/charts","redirect":"noRedirect","name":"Charts","meta":{"title":"charts","icon":"chart"},"children":[{"path":"keyboard","name":"KeyboardChart","meta":{"title":"keyboardChart","noCache":true}},{"path":"line","name":"LineChart","meta":{"title":"lineChart","noCache":true}},{"path":"mix-chart","name":"MixChart","meta":{"title":"mixChart","noCache":true}}],"flag":true},{"path":"/nested","redirect":"/nested/menu1/menu1-1","name":"Nested","meta":{"title":"nested","icon":"nested"},"children":[{"path":"menu1","name":"Menu1","meta":{"title":"menu1"},"redirect":"/nested/menu1/menu1-1","children":[{"path":"menu1-1","name":"Menu1-1","meta":{"title":"menu1-1"}},{"path":"menu1-2","name":"Menu1-2","redirect":"/nested/menu1/menu1-2/menu1-2-1","meta":{"title":"menu1-2"},"children":[{"path":"menu1-2-1","name":"Menu1-2-1","meta":{"title":"menu1-2-1"}},{"path":"menu1-2-2","name":"Menu1-2-2","meta":{"title":"menu1-2-2"}}]},{"path":"menu1-3","name":"Menu1-3","meta":{"title":"menu1-3"}}]},{"path":"menu2","name":"Menu2","meta":{"title":"menu2"}}],"flag":true},{"path":"/table","redirect":"/table/complex-table","name":"Table","meta":{"title":"Table","icon":"table"},"children":[{"path":"dynamic-table","name":"DynamicTable","meta":{"title":"dynamicTable"}},{"path":"drag-table","name":"DragTable","meta":{"title":"dragTable"}},{"path":"inline-edit-table","name":"InlineEditTable","meta":{"title":"inlineEditTable"}},{"path":"complex-table","name":"ComplexTable","meta":{"title":"complexTable"}}],"flag":true},{"path":"/example","redirect":"/example/list","name":"Example","meta":{"title":"example","icon":"example"},"children":[{"path":"create","name":"CreateArticle","meta":{"title":"createArticle","icon":"edit"}},{"path":"list","name":"ArticleList","meta":{"title":"articleList","icon":"list"}}],"flag":true},{"path":"/tab","children":[{"path":"/tab/index","name":"Tab","meta":{"title":"tab","icon":"tab"}}],"flag":true},{"path":"/error","redirect":"noRedirect","name":"ErrorPages","meta":{"title":"errorPages","icon":"404"},"children":[{"path":"401","name":"Page401","meta":{"title":"page401","noCache":true}},{"path":"404","name":"Page404","meta":{"title":"page404","noCache":true}}],"flag":true},{"path":"/error-log","children":[{"path":"/error-log/log","name":"ErrorLog","meta":{"title":"errorLog","icon":"bug"}}],"flag":true},{"path":"/excel","redirect":"/excel/export-excel","name":"Excel","meta":{"title":"excel","icon":"excel"},"children":[{"path":"export-excel","name":"ExportExcel","meta":{"title":"exportExcel"}},{"path":"export-selected-excel","name":"SelectExcel","meta":{"title":"selectExcel"}},{"path":"export-merge-header","name":"MergeHeader","meta":{"title":"mergeHeader"}},{"path":"upload-excel","name":"UploadExcel","meta":{"title":"uploadExcel"}}],"flag":true},{"path":"/zip","redirect":"/zip/download","alwaysShow":true,"name":"Zip","meta":{"title":"zip","icon":"zip"},"children":[{"path":"/zip/download","name":"ExportZip","meta":{"title":"exportZip"}}],"flag":true},{"path":"/pdf","redirect":"/pdf/index","children":[{"path":"/pdf/index","name":"PDF","meta":{"title":"pdf","icon":"pdf"}}],"flag":true},{"path":"/theme","children":[{"path":"/theme/index","name":"Theme","meta":{"title":"theme","icon":"theme"}}],"flag":true},{"path":"/clipboard","children":[{"path":"/clipboard/index","name":"ClipboardDemo","meta":{"title":"clipboardDemo","icon":"clipboard"}}],"flag":true},{"path":"/i18n","children":[{"path":"/i18n/index","name":"I18n","meta":{"title":"i18n","icon":"international"}}],"flag":true},{"path":"external-link","children":[{"path":"/external-link/https:/github.com/PanJiaChen/vue-element-admin","meta":{"title":"externalLink","icon":"link"}}],"flag":true}]

可以格式化json看一下結構,除了component 其他都一樣

  • 注意點 
  • 退出後換個用戶登錄,資源菜單有時候沒有刷新,使用系統中的logout還是有問題。所以我在退出的時候加了強制刷新。

 

 

======================2020年1月9日18:07:00==============================

請求的jsonflag在傳遞的時候做了下修改,放在headers裏邊傳遞過去

相應的在判斷裏邊也修改一下

 

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章