用戶與用戶組的架構設計
動態菜單和權限的設計思路與實現
Vue 端如何實現動態路由
Django 端如何實現動態權限
隨著前後端分離架構的流行,在 web 應用中,RESTful API 幾乎已經成為了開發者主要選擇,它使得客戶端和服務端不需要保存對方的詳細信息,也就是無狀態性,但是這樣在項目中需要動態菜單和動態權限就困難起來,本場Chat就是為大家提供一種思路來解決實際項目中如何實現動態菜單和權限。
因為 RESTful API 通常是無狀態性,服務器怎麼樣才能知道用戶已經登錄呢?這個時候常用的做法就是每個請求都會攜帶一個 access token 來在服務端認證用戶。最常用的就是 JWT 了,有感興趣的小伙伴可以再做深入學習。通俗來講,使用了 JWT 用戶在每次請求時都會在請求頭中攜帶一個 Token ,服務端會在執行操作之前先解析這個 Token 進行認證,認證完成之後服務端就會知道過來請求的用戶詳情,從而做出需要的返回。
用戶與用戶組的架構設計通常在一個 web 應用設計中,首先都是從用戶、用戶組開始的。用戶就是 web 應用的核心,JWT 認證也是因為用戶才存在的。用戶在使用 MySQL 的 web 應用中就是一張用戶表,每一個用戶就是用戶表中的一條數據。用戶組就相當於是用戶的權限了,例如 一般的系統中都會有超級管理員、管理員、普通用戶等用戶,用戶就是這些用戶組下的集合,那麼用戶組和用戶就是一對多的關系,或者說每個用戶都有一個外鍵指向某個用戶組 ID。當某個用戶是管理員時就表示他擁有管理員用戶組下的所有權限。
在這樣用戶組-用戶的架構設計下,如何設計權限和菜單呢?首先,菜單就類似是用戶操作的一些功能的集合,集合內的每個元素就相當於是權限了。例如有個菜單名為 員工管理 ,在它下面就存在四個基本權限:查看員工、新增員工、編輯員工、刪除員工。也就是說把這四個權限想象成四個方法或功能,這些功能是關聯某個用戶組還是某個用戶呢?顯然是關聯某個用戶組是比較好的選擇。因為這樣用戶組可以攜帶著很多菜單以及菜單的權限,當多個用戶屬於這個用戶組時,這些用戶就擁有了該用戶組下的所有權限。否則關聯某個用戶的話,每個用戶在新增的時候都需要設置菜單和權限的話,不僅浪費時間,還浪費資源 占有數據庫空間。
用 RESTful API 的做法就是:用戶組的菜單和權限會作為記錄存放在權限表中,當需要的時候服務端會將這些數據轉成 Json 返回給客戶端使用,當然在服務端需要使用權限的接口也會使用權限表中的數據來判斷,請求接口的用戶是否有權限操作,根據權限表數據做不同處理。
動態菜單和權限的設計思路與實現那麼動態菜單和權限在服務端和客戶端究竟怎樣完成這些交互的呢?
用戶登錄系統時輸入用戶名、密碼等進行登錄,登錄成功之後會將該用戶的 Token 返回給用戶。
用戶攜帶著這個 Token 請求服務端的一個獲取用戶詳細信息接口,服務端在認證通過後就將該用戶的用戶信息、用戶組信息以及用戶組的權限信息全部返回,此時客戶端用戶就得到了權限表的 Json 數據,根據這些數據客戶端會展示不同的菜單項。
客戶端在收到權限 Json 數據時,根據權限中的菜單項設置,動態的設置前端菜單。做到不同的用戶顯示不同的菜單項。
當用戶在請求其他接口時,服務端會先進行用戶認證,在得到當前請求用戶的同時 也會得到該用戶的用戶組,從而得到該用戶的所有權限信息。然後服務端會根據接口對應的權限詳情做不同的處理,最終再返回給用戶相應信息。例如 當查到該用戶對當前接口只有查看權限時,如果用戶發起的是 POST 請求想新增數據,那會服務端會直接返回 沒有執行該操作的權限。
Vue 端如何實現動態路由以基於 element 的管理後台為例,在 Vue 裡面利用 VueX 狀態管理器,當用戶登錄成功後,去獲取用戶詳細信息,獲取到詳細信息後根據權限 Json 數據,在 Store 中將路由重新設置,不該展示路由節點的需要隱藏或刪除掉。從而做到不同的用戶展示不同的菜單。
Vue 端實現代碼示例
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: [], // 自動權限相關 group_id: 0, user_id: 0, menu_json: [], // 後端返回的權限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) => { // 動態路由、動態菜單 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,不執行設置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 } } } // 動態路由、動態菜單的結束 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) // 將返回的權限數據保存 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 端如何實現動態權限在 Django 中利用 Permission 結合權限表,在每個接口在被調用之前先根據權限判斷,調用接口的用戶是否有權限操作,如果有就繼續完成後面的操作,否則直返返回 無權操作 的提示語。
Django 端代碼示例如下
from rest_framework import permissionsfrom rest_framework.permissions import DjangoModelPermissions, IsAdminUserfrom rest_framework.permissions import BasePermission as BPermissionfrom shiyouAuth.models import GroupMenu# 最終動態權限類class BasePermission(object): def base_permission_check(self, basename): # 權限白名單 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': # 查看權限 return bool(request.auth and admin_menu.menu_list == True) elif view.action == 'create': # 創建權限 return bool(request.auth and admin_menu.menu_create == True) elif view.action == 'update' or view.action == 'partial_update': # 修改權限 return bool(request.auth and admin_menu.menu_update == True) elif view.action == 'destroy': # 刪除權限 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': # 查看權限 return bool(request.auth and admin_menu.menu_list == True) elif view.action == 'create': # 創建權限 return bool(request.auth and admin_menu.menu_create == True) elif view.action == 'update' or view.action == 'partial_update': # 修改權限 return bool(request.auth and admin_menu.menu_update == True) elif view.action == 'destroy': # 刪除權限 return bool(request.auth and admin_menu.menu_destroy == True) else: return False
到此這篇關於Django Vue實現動態菜單和動態權限的文章就介紹到這了,更多相關Django Vue 動態菜單和動態權限內容請搜索軟件開發網以前的文章或繼續浏覽下面的相關文章希望大家以後多多支持軟件開發網!
解決報錯UnicodeDecodeError: ‘utf-8
已解決:pip安裝第三模塊(庫)與PyCharm中不同步的問