Django Vue實(shí)現(xiàn)動態(tài)菜單和動態(tài)權(quán)限
隨著前后端分離架構(gòu)的流行,在 web 應(yīng)用中,RESTful API 幾乎已經(jīng)成為了開發(fā)者主要選擇,它使得客戶端和服務(wù)端不需要保存對方的詳細(xì)信息,也就是無狀態(tài)性,但是這樣在項(xiàng)目中需要動態(tài)菜單和動態(tài)權(quán)限就困難起來,本場Chat就是為大家提供一種思路來解決實(shí)際項(xiàng)目中如何實(shí)現(xiàn)動態(tài)菜單和權(quán)限。
因?yàn)?RESTful API 通常是無狀態(tài)性,服務(wù)器怎么樣才能知道用戶已經(jīng)登錄呢?這個時候常用的做法就是每個請求都會攜帶一個 access token 來在服務(wù)端認(rèn)證用戶。最常用的就是 JWT 了,有感興趣的小伙伴可以再做深入學(xué)習(xí)。通俗來講,使用了 JWT 用戶在每次請求時都會在請求頭中攜帶一個 Token ,服務(wù)端會在執(zhí)行操作之前先解析這個 Token 進(jìn)行認(rèn)證,認(rèn)證完成之后服務(wù)端就會知道過來請求的用戶詳情,從而做出需要的返回。
用戶與用戶組的架構(gòu)設(shè)計(jì)
通常在一個 web 應(yīng)用設(shè)計(jì)中,首先都是從用戶、用戶組開始的。用戶就是 web 應(yīng)用的核心,JWT 認(rèn)證也是因?yàn)橛脩舨糯嬖诘摹S脩粼谑褂?MySQL 的 web 應(yīng)用中就是一張用戶表,每一個用戶就是用戶表中的一條數(shù)據(jù)。用戶組就相當(dāng)于是用戶的權(quán)限了,例如 一般的系統(tǒng)中都會有超級管理員、管理員、普通用戶等用戶,用戶就是這些用戶組下的集合,那么用戶組和用戶就是一對多的關(guān)系,或者說每個用戶都有一個外鍵指向某個用戶組 ID。當(dāng)某個用戶是管理員時就表示他擁有管理員用戶組下的所有權(quán)限。
在這樣用戶組-用戶的架構(gòu)設(shè)計(jì)下,如何設(shè)計(jì)權(quán)限和菜單呢?首先,菜單就類似是用戶操作的一些功能的集合,集合內(nèi)的每個元素就相當(dāng)于是權(quán)限了。例如有個菜單名為 員工管理 ,在它下面就存在四個基本權(quán)限:查看員工、新增員工、編輯員工、刪除員工。也就是說把這四個權(quán)限想象成四個方法或功能,這些功能是關(guān)聯(lián)某個用戶組還是某個用戶呢?顯然是關(guān)聯(lián)某個用戶組是比較好的選擇。因?yàn)檫@樣用戶組可以攜帶著很多菜單以及菜單的權(quán)限,當(dāng)多個用戶屬于這個用戶組時,這些用戶就擁有了該用戶組下的所有權(quán)限。否則關(guān)聯(lián)某個用戶的話,每個用戶在新增的時候都需要設(shè)置菜單和權(quán)限的話,不僅浪費(fèi)時間,還浪費(fèi)資源 占有數(shù)據(jù)庫空間。
用 RESTful API 的做法就是:用戶組的菜單和權(quán)限會作為記錄存放在權(quán)限表中,當(dāng)需要的時候服務(wù)端會將這些數(shù)據(jù)轉(zhuǎn)成 Json 返回給客戶端使用,當(dāng)然在服務(wù)端需要使用權(quán)限的接口也會使用權(quán)限表中的數(shù)據(jù)來判斷,請求接口的用戶是否有權(quán)限操作,根據(jù)權(quán)限表數(shù)據(jù)做不同處理。
動態(tài)菜單和權(quán)限的設(shè)計(jì)思路與實(shí)現(xiàn)
那么動態(tài)菜單和權(quán)限在服務(wù)端和客戶端究竟怎樣完成這些交互的呢?
- 用戶登錄系統(tǒng)時輸入用戶名、密碼等進(jìn)行登錄,登錄成功之后會將該用戶的 Token 返回給用戶。
- 用戶攜帶著這個 Token 請求服務(wù)端的一個獲取用戶詳細(xì)信息接口,服務(wù)端在認(rèn)證通過后就將該用戶的用戶信息、用戶組信息以及用戶組的權(quán)限信息全部返回,此時客戶端用戶就得到了權(quán)限表的 Json 數(shù)據(jù),根據(jù)這些數(shù)據(jù)客戶端會展示不同的菜單項(xiàng)。
- 客戶端在收到權(quán)限 Json 數(shù)據(jù)時,根據(jù)權(quán)限中的菜單項(xiàng)設(shè)置,動態(tài)的設(shè)置前端菜單。做到不同的用戶顯示不同的菜單項(xiàng)。
- 當(dāng)用戶在請求其他接口時,服務(wù)端會先進(jìn)行用戶認(rèn)證,在得到當(dāng)前請求用戶的同時 也會得到該用戶的用戶組,從而得到該用戶的所有權(quán)限信息。然后服務(wù)端會根據(jù)接口對應(yīng)的權(quán)限詳情做不同的處理,最終再返回給用戶相應(yīng)信息。例如 當(dāng)查到該用戶對當(dāng)前接口只有查看權(quán)限時,如果用戶發(fā)起的是 POST 請求想新增數(shù)據(jù),那會服務(wù)端會直接返回 沒有執(zhí)行該操作的權(quán)限。
Vue 端如何實(shí)現(xiàn)動態(tài)路由
以基于 element 的管理后臺為例,在 Vue 里面利用 VueX 狀態(tài)管理器,當(dāng)用戶登錄成功后,去獲取用戶詳細(xì)信息,獲取到詳細(xì)信息后根據(jù)權(quán)限 Json 數(shù)據(jù),在 Store 中將路由重新設(shè)置,不該展示路由節(jié)點(diǎn)的需要隱藏或刪除掉。從而做到不同的用戶展示不同的菜單。
Vue 端實(shí)現(xiàn)代碼示例
import { login, logout, getInfo } from '@/api/login' import { getToken, setToken, removeToken } from '@/utils/auth' import router from '../../router' const user = { ? state: { ? ? token: getToken(), ? ? name: '', ? ? avatar: '', ? ? roles: [], ? ? // 自動權(quán)限相關(guān) ? ? group_id: 0, ? ? user_id: 0, ? ? menu_json: [], // 后端返回的權(quán)限Json儲存在這里 ? ? router: router // 引入路由菜單 ? }, ? mutations: { ? ? SET_TOKEN: (state, token) => { ? ? ? state.token = token ? ? }, ? ? SET_NAME: (state, name) => { ? ? ? state.name = name ? ? }, ? ? SET_ROLES: (state, roles) => { ? ? ? state.roles = roles ? ? }, ? ? SET_MENUS: (state, menus) => { ? ? ? state.menu_json = menus ? ? }, ? ? SET_GROUP: (state, group_id) => { ? ? ? state.group_id = group_id ? ? }, ? ? SET_USER: (state, user_id) => { ? ? ? state.user_id = user_id ? ? }, ? ? SET_ROUTE: (state, router) => { ? ? ? // 動態(tài)路由、動態(tài)菜單 ? ? ? console.log('router:', router.options.routes) ? ? ? var group_id = localStorage.getItem('ShopGroupId') ? ? ? var menus = JSON.parse(localStorage.getItem('ShopMenus')) ? ? ? console.log('group_id:', localStorage.getItem('ShopGroupId')) ? ? ? console.log('menus:', JSON.parse(localStorage.getItem('ShopMenus'))) ? ? ? console.log('group_id為1,不執(zhí)行設(shè)置router操作') ? ? ? if (group_id !== '1') { ? ? ? ? for (var i in router.options.routes) { ? ? ? ? ? if (router.options.routes[i].children !== undefined) { ? ? ? ? ? ? for (var j in router.options.routes[i].children) { ? ? ? ? ? ? ? if (router.options.routes[i].children[j].path !== 'dashboard') { ? ? ? ? ? ? ? ? router.options.routes[i].children[j].hidden = true ? ? ? ? ? ? ? ? for (var k in menus) { ? ? ? ? ? ? ? ? ? if (menus[k].object_name == router.options.routes[i].children[j].name) { ? ? ? ? ? ? ? ? ? ? if (menus[k].menu_list) { ? ? ? ? ? ? ? ? ? ? ? router.options.routes[i].children[j].hidden = false ? ? ? ? ? ? ? ? ? ? } ? ? ? ? ? ? ? ? ? } ? ? ? ? ? ? ? ? } ? ? ? ? ? ? ? } ? ? ? ? ? ? } ? ? ? ? ? } ? ? ? ? } ? ? ? } ? ? ? for (var i in router.options.routes) { ? ? ? ? if (router.options.routes[i].children !== undefined) { ? ? ? ? ? var len_router = router.options.routes[i].children.length ? ? ? ? ? var check_len = 0 ? ? ? ? ? for (var j in router.options.routes[i].children) { ? ? ? ? ? ? if (router.options.routes[i].children[j].path !== 'dashboard') { ? ? ? ? ? ? ? if (router.options.routes[i].children[j].hidden !== null && router.options.routes[i].children[j].hidden !== undefined && router.options.routes[i].children[j].hidden === true) { ? ? ? ? ? ? ? ? check_len += 1 ? ? ? ? ? ? ? } ? ? ? ? ? ? } ? ? ? ? ? } ? ? ? ? ? if (len_router === check_len) { ? ? ? ? ? ? router.options.routes[i].hidden = true ? ? ? ? ? } ? ? ? ? } ? ? ? } ? ? ? // 動態(tài)路由、動態(tài)菜單的結(jié)束 ? ? ? state.router = router ? ? } ? }, ? actions: { ? ? // 登錄 ? ? Login({ commit }, userInfo) { ? ? ? return new Promise((resolve, reject) => { ? ? ? ? login(userInfo).then(response => { ? ? ? ? ? const data = response.data ? ? ? ? ? setToken(data.token) ? ? ? ? ? commit('SET_TOKEN', data.token) ? ? ? ? ? resolve() ? ? ? ? }).catch(error => { ? ? ? ? ? reject(error) ? ? ? ? ? alert(error) ? ? ? ? }) ? ? ? }) ? ? }, ? ? // 獲取用戶信息 ? ? GetInfo({ commit, state }) { ? ? ? return new Promise((resolve, reject) => { ? ? ? ? getInfo().then(response => { ? ? ? ? ? const data = response.data ? ? ? ? ? console.log(data) ? ? ? ? ? commit('SET_NAME', data.username) ? ? ? ? ? commit('SET_ROLES', [data.group.en_name]) ? ? ? ? ? commit('SET_MENUS', data.group.back_menu) // 將返回的權(quán)限數(shù)據(jù)保存 ? ? ? ? ? commit('SET_GROUP', data.group.id) ? ? ? ? ? commit('SET_USER', data.id) ? ? ? ? ? localStorage.setItem('ShopMenus', JSON.stringify(data.group.back_menu)) ? ? ? ? ? localStorage.setItem('ShopGroupId', data.group.id) ? ? ? ? ? commit('SET_ROUTE', router) ? ? ? ? ? resolve(response) ? ? ? ? }).catch(error => { ? ? ? ? ? reject(error) ? ? ? ? }) ? ? ? }) ? ? }, ? ? // 前端 登出 ? ? FedLogOut({ commit }) { ? ? ? return new Promise(resolve => { ? ? ? ? commit('SET_TOKEN', '') ? ? ? ? removeToken() ? ? ? ? resolve() ? ? ? }) ? ? } ? } } export default user
Django 端如何實(shí)現(xiàn)動態(tài)權(quán)限
在 Django 中利用 Permission 結(jié)合權(quán)限表,在每個接口在被調(diào)用之前先根據(jù)權(quán)限判斷,調(diào)用接口的用戶是否有權(quán)限操作,如果有就繼續(xù)完成后面的操作,否則直返返回 無權(quán)操作 的提示語。
Django 端代碼示例如下
from rest_framework import permissions from rest_framework.permissions import DjangoModelPermissions, IsAdminUser from rest_framework.permissions import BasePermission as BPermission from shiyouAuth.models import GroupMenu # 最終動態(tài)權(quán)限類 class BasePermission(object): ? ? def base_permission_check(self, basename): ? ? ? ? # 權(quán)限白名單 ? ? ? ? if basename in ['userinfo', 'permissions', 'login', 'confdict']: ? ? ? ? ? ? return True ? ? ? ? else: ? ? ? ? ? ? return False ? ? def has_permission(self, request, view): ? ? ? ? print('請求的path:', request.path.split('/')[1]) ? ? ? ? basename = request.path.split('/')[1] ? ? ? ? if self.base_permission_check(basename): ? ? ? ? ? ? return True ? ? ? ? admin_menu = GroupMenu.objects.filter(object_name=basename).first() ? ? ? ? if admin_menu is None and request.user.group.id != 1: ? ? ? ? ? ? return False ? ? ? ? if request.user.group.id == 1: ? ? ? ? ? ? return True ? ? ? ? if view.action == 'list' or view.action == 'retrieve': ? ? ? ? ? ? # 查看權(quán)限 ? ? ? ? ? ? return bool(request.auth and admin_menu.menu_list == True) ? ? ? ? elif view.action == 'create': ? ? ? ? ? ? # 創(chuàng)建權(quán)限 ? ? ? ? ? ? return bool(request.auth and admin_menu.menu_create == True) ? ? ? ? elif view.action == 'update' or view.action == 'partial_update': ? ? ? ? ? ? # 修改權(quán)限 ? ? ? ? ? ? return bool(request.auth and admin_menu.menu_update == True) ? ? ? ? elif view.action == 'destroy': ? ? ? ? ? ? # 刪除權(quán)限 ? ? ? ? ? ? return bool(request.auth and admin_menu.menu_destroy == True) ? ? ? ? else: ? ? ? ? ? ? return False ? ? def has_object_permission(self, request, view, obj): ? ? ? ? basename = request.path.split('/')[1] ? ? ? ? if self.base_permission_check(basename): ? ? ? ? ? ? return True ? ? ? ? admin_menu = GroupMenu.objects.filter(object_name=basename).first() ? ? ? ? if admin_menu is None and request.user.group.id != 1: ? ? ? ? ? ? return False ? ? ? ? if request.user.group.id == 1: ? ? ? ? ? ? return True ? ? ? ? if view.action == 'list' or view.action == 'retrieve': ? ? ? ? ? ? # 查看權(quán)限 ? ? ? ? ? ? return bool(request.auth and admin_menu.menu_list == True) ? ? ? ? elif view.action == 'create': ? ? ? ? ? ? # 創(chuàng)建權(quán)限 ? ? ? ? ? ? return bool(request.auth and admin_menu.menu_create == True) ? ? ? ? elif view.action == 'update' or view.action == 'partial_update': ? ? ? ? ? ? # 修改權(quán)限 ? ? ? ? ? ? return bool(request.auth and admin_menu.menu_update == True) ? ? ? ? elif view.action == 'destroy': ? ? ? ? ? ? # 刪除權(quán)限 ? ? ? ? ? ? return bool(request.auth and admin_menu.menu_destroy == True) ? ? ? ? else: ? ? ? ? ? ? return False
到此這篇關(guān)于Django Vue實(shí)現(xiàn)動態(tài)菜單和動態(tài)權(quán)限的文章就介紹到這了,更多相關(guān)Django Vue 動態(tài)菜單和動態(tài)權(quán)限內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
vue-router動態(tài)設(shè)置頁面title的實(shí)例講解
今天小編就為大家分享一篇vue-router動態(tài)設(shè)置頁面title的實(shí)例講解,具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧2018-08-08vue3應(yīng)用elementPlus table并滾動顯示問題
這篇文章主要介紹了vue3應(yīng)用elementPlus table并滾動顯示問題,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2022-12-12一篇看懂vuejs的狀態(tài)管理神器 vuex狀態(tài)管理模式
一篇看懂vuejs的狀態(tài)管理神器,Vuex一個專為Vue.js應(yīng)用程序開發(fā)的狀態(tài)管理模式,具有一定的參考價值,感興趣的小伙伴們可以參考一下2017-04-04nginx部署訪問vue-cli搭建的項(xiàng)目的方法
本篇文章主要介紹了nginx部署訪問vue-cli搭建的項(xiàng)目的方法,小編覺得挺不錯的,現(xiàn)在分享給大家,也給大家做個參考。一起跟隨小編過來看看吧2018-02-02Vue的transition-group與Virtual Dom Diff算法的使用
這篇文章主要介紹了Vue的transition-group與Virtual Dom Diff算法的使用,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2019-12-12解決Vue + Echarts 使用markLine標(biāo)線(precision精度問題)
這篇文章主要介紹了解決Vue + Echarts 使用markLine標(biāo)線(precision精度問題),具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧2020-07-07Vue3 響應(yīng)式數(shù)據(jù) reactive使用方法
這篇文章主要介紹了Vue3 響應(yīng)式數(shù)據(jù) reactive使用方法,本文通過實(shí)例代碼給大家介紹的非常詳細(xì),對大家的學(xué)習(xí)或工作具有一定的參考借鑒價值,需要的朋友可以參考下2023-11-11Echarts實(shí)現(xiàn)一張圖現(xiàn)切換不同的X軸(實(shí)例代碼)
這篇文章主要介紹了Echarts 如何實(shí)現(xiàn)一張圖現(xiàn)切換不同的X軸,通過動圖給大家展示效果,實(shí)例代碼相結(jié)合給大家介紹的非常詳細(xì),需要的朋友可以參考下2021-11-11