DjangoVue实现动态菜单、动态权限
随着前后端分离架构的流⾏,在 web 应⽤中,RESTful API ⼏乎已经成为了开发者主要选择,它使得客户端和服务端不需要保存对⽅的详细信息,也就是⽆状态性,但是这样在项⽬中需要动态菜单和动态权限就困难起来,本场Chat就是为⼤家提供⼀种思路来解决实际项⽬中如何实现动态菜单和权限。
因为 RESTful API 通常是⽆状态性,服务器怎么样才能知道⽤户已经登录呢?这个时候常⽤的做法就是每个请求都会携带⼀个 access token 来在服务端认证⽤户。最常⽤的就是 JWT 了,有感兴趣的⼩伙伴可以再做深⼊学习。通俗来讲,使⽤了 JWT ⽤户在每次请求时都会在请求头中携带⼀个 Token ,服务端会在执⾏操作之前先解析这个 Token 进⾏认证,认证完成之后服务端就会知道过来请求的⽤户详情,从⽽做出需要的返回。
⽤户与⽤户组的架构设计
通常在⼀个 web 应⽤设计中,⾸先都是从⽤户、⽤户组开始的。⽤户就是 web 应⽤的核⼼,JWT 认证也是因为⽤户才存在的。⽤户在使⽤ MySQL 的 web 应⽤中就是⼀张⽤户表,每⼀个⽤户就是⽤户表中的⼀条数据。⽤户组就相当于是⽤户的权限了,例如 ⼀般的系统中都会有超级管理员、管理员、普通⽤户等⽤户,⽤户就是这些⽤户组下的集合,那么⽤户组和⽤户就是⼀对多的关系,或者说每个⽤户都有⼀个外键指向某个⽤户组 ID。当某个⽤户是管理员时就表⽰他拥有管理员⽤户组下的所有权限。
⽤户-⽤户组表设计图如下
在这样⽤户组-⽤户的架构设计下,如何设计权限和菜单呢?⾸先,菜单就类似是⽤户操作的⼀些功能的集合,集合内的每个元素就相当于是权限了。例如有个菜单名为 员⼯管理 ,在它下⾯就存在四个基本权限:查看员⼯、新增员⼯、编辑员⼯、删除员⼯。也就是说把这四个权限想象成四个⽅法或功能,这些功能是关联某个⽤户组还是某个⽤户呢?显然是关联某个⽤户组是⽐较好的选择。因为这样⽤户组可以携带着很多菜单以及菜单的权限,当多个⽤户属于这个⽤户组时,这些⽤户就拥有了该⽤户组下的所有权限。否则关联某个⽤户的话,每个⽤户在新增的时候都需要设置菜单和权限的话,不仅浪费时间,还浪费资源 占有数据库空间。
⽤ RESTful API 的做法就是:⽤户组的菜单和权限会作为记录存放在权限表中,当需要的时候服务端会将这些数据转成 Json 返回给客户端使⽤,当然在服务端需要使⽤权限的接⼝也会使⽤权限表中的数据来判断,请求接⼝的⽤户是否有权限操作,根据权限表数据做不同处理。
权限-⽤户组-⽤户设计图如下
动态菜单和权限的设计思路与实现
那么动态菜单和权限在服务端和客户端究竟怎样完成这些交互的呢?
1. ⽤户登录系统时输⼊⽤户名、密码等进⾏登录,登录成功之后会将该⽤户的 Token 返回给⽤户。
2. ⽤户携带着这个 Token 请求服务端的⼀个获取⽤户详细信息接⼝,服务端在认证通过后就将该⽤户的⽤户信息、⽤户组信息以及⽤户
组的权限信息全部返回,此时客户端⽤户就得到了权限表的 Json 数据,根据这些数据客户端会展⽰不同的菜单项。
3. 客户端在收到权限 Json 数据时,根据权限中的菜单项设置,动态的设置前端菜单。做到不同的⽤户显⽰不同的菜单项。
4. 当⽤户在请求其他接⼝时,服务端会先进⾏⽤户认证,在得到当前请求⽤户的同时 也会得到该⽤户的⽤户组,从⽽得到该⽤户的所有
权限信息。然后服务端会根据接⼝对应的权限详情做不同的处理,最终再返回给⽤户相应信息。例如 当查到该⽤户对当前接⼝只有查看权限时,如果⽤户发起的是 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(),
avatar: '',
roles: [],
// ⾃动权限相关
group_id: 0,
user_id: 0,
menu_json: [], // 后端返回的权限Json储存在这⾥
router: router // 引⼊路由菜单
},
mutations: {
SET_TOKEN: (state, token) => {
},
SET_NAME: (state, name) => {
state.name = name
},
SET_ROLES: (state, roles) => {
},
SET_MENUS: (state, menus) => {
},
SET_GROUP: (state, group_id) => {
},
SET_USER: (state, user_id) => {
state.user_id = user_id
},
SET_ROUTE: (state, router) => {
// 动态路由、动态菜单
console.log('router:', utes)
var group_id = Item('ShopGroupId')
var menus = JSON.Item('ShopMenus'))
console.log('group_id:', Item('ShopGroupId'))
console.log('menus:', JSON.Item('ShopMenus')))
console.log('group_id为1,不执⾏设置router操作')
if (group_id !== '1') {
for (var i in utes) {
if (utes[i].children !== undefined) {
for (var j in utes[i].children) {
if (utes[i].children[j].path !== 'dashboard') {
utes[i].children[j].hidden = true
for (var k in menus) {
django admin 自定义页面if (menus[k].object_name == utes[i].children[j].name) {
if (menus[k].menu_list) {
utes[i].children[j].hidden = false
}
}
}
}
}
}
}
}
for (var i in utes) {
if (utes[i].children !== undefined) {
var len_router = utes[i].children.length
var check_len = 0
for (var j in utes[i].children) {
if (utes[i].children[j].path !== 'dashboard') {
if (utes[i].children[j].hidden !== null && utes[i].children[j].hidden !== undefined && utes[i].children[j ].hidden === true) {
check_len += 1
}
}
if (len_router === check_len) {
utes[i].hidden = true
}
}
}
// 动态路由、动态菜单的结束
}
},
actions: {
// 登录
Login({ commit }, userInfo) {
return new Promise((resolve, reject) => {
login(userInfo).then(response => {
const data = response.data
ken)
commit('SET_TOKEN', ken)
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', [_name])
commit('SET_MENUS', up.back_menu) // 将返回的权限数据保存          commit('SET_GROUP', up.id)
commit('SET_USER', data.id)
localStorage.setItem('ShopMenus', JSON.up.back_menu))          localStorage.setItem('ShopGroupId', up.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 permissions
from rest_framework.permissions import DjangoModelPermissions, IsAdminUser from rest_framework.permissions import BasePermission as BPermission
dels 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 up.id!=1:
return False
if up.id==1:
return True
if view.action =='list'or view.action =='retrieve':
# 查看权限
return bool(request.auth and u_list ==True)
elif view.action =='create':
# 创建权限
return bool(request.auth and u_create ==True)
elif view.action =='update'or view.action =='partial_update':
# 修改权限
return bool(request.auth and u_update ==True)
elif view.action =='destroy':
# 删除权限
return bool(request.auth and u_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 up.id!=1:
return False
if up.id==1:
return True
if view.action =='list'or view.action =='retrieve':
# 查看权限
return bool(request.auth and u_list ==True)
elif view.action =='create':
# 创建权限
return bool(request.auth and u_create ==True)
elif view.action =='update'or view.action =='partial_update':
# 修改权限
return bool(request.auth and u_update ==True)
elif view.action =='destroy':
# 删除权限
return bool(request.auth and u_destroy ==True)
else:
return False

版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。