随着前后端分离架构的流行,在 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(),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 = truefor (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.lengthvar check_len = 0for (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.datasetToken(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.dataconsole.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 permissions
from rest_framework.permissions import DjangoModelPermissions, IsAdminUser
from rest_framework.permissions import BasePermission as BPermission
from shiyouAuth.models import GroupMenu# 最终动态权限类
class BasePermission(object):def base_permission_check(self, basename):# 权限白名单if basename in ['userinfo', 'permissions', 'login', 'confdict']:return Trueelse:return Falsedef has_permission(self, request, view):print('请求的path:', request.path.split('/')[1])basename = request.path.split('/')[1]if self.base_permission_check(basename):return Trueadmin_menu = GroupMenu.objects.filter(object_name=basename).first()if admin_menu is None and request.user.group.id != 1:return Falseif request.user.group.id == 1:return Trueif 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 Falsedef has_object_permission(self, request, view, obj):basename = request.path.split('/')[1]if self.base_permission_check(basename):return Trueadmin_menu = GroupMenu.objects.filter(object_name=basename).first()if admin_menu is None and request.user.group.id != 1:return Falseif request.user.group.id == 1:return Trueif 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实现动态菜单、动态权限相关推荐

  1. vue+antdesign导航菜单动态加载

    2019独角兽企业重金招聘Python工程师标准>>> antdesign侧边栏菜单,需根据后台返回的数据动态加载菜单列表,在循环填充时会遇到子菜单项<a-sub-menu&g ...

  2. Vue 动态路由和权限菜单的实现思路

    为什么不贴代码详细说而是只讲思路呢--对,因为我懒--实际上动态菜单和动态路由的思路还是比较简单的,只是平日只在路由文件里配静态路由什么都不需要多考虑的,一上来可能会有点懵逼,当然这里边也多少有点小坑 ...

  3. vue项目token放在哪里_关于vue动态菜单的那点事

    vue-element-admin4.0国内节点访问地址:https://panjiachen.gitee.io/vue-element-admin-site/zh/ 本此使用的是https://gi ...

  4. 动态菜单,根据登录用户权限返回不同菜单,登录完成跳转到不同的首页

    1.动态菜单,前提是根据后端返回的不同菜单,展示给不同的人,路由一定要和菜单返回的URL保持一致, 2.前提是,路由中不要写死redirect到某一个指定 文件,因为登录的用户不一定有这个权限 3.流 ...

  5. vue点击菜单跳转时,背景颜色动态变化

    ** vue点击菜单跳转时,背景颜色动态变化 ** html: script: export default { name: "menu", data() { return { a ...

  6. 前后端分离,基于vue+elementUI的动态菜单

    制作动态菜单的思路: 就是当前用户登陆后,保存用户的信息,通过用户的id,去查找该用户对象的菜单.后台获取的菜单数据,最终传给前台形式就是父菜单里包含了所有的子菜单:并在前台登录成功后,把菜单数据取出 ...

  7. Ant Design Pro v5 获取动态菜单与基于角色权限管理视频教程(33 个视频)

    Ant Design Pro v5 获取动态菜单与基于角色权限管理视频教程(33 个视频) 很多人问如何获取动态菜单的,所以在 antd prov 5 来讲一下角色管理的.从最基础的用户管理,角色管理 ...

  8. VUE项目后台管理系统(四)左边菜单动态展示,不仅可以折叠,而且点击不同的菜单,右边展示不同的页面

    目录 左边菜单动态的展示 左边菜单的属性介绍 遍历菜单 左边菜单折叠效果 不同的菜单右面展示不同的页面 左边菜单动态的展示 首先后端的接口要返回菜单的list集合,是json格式,我使用的是pytho ...

  9. django html菜单,django实现动态菜单的方式

    1.model from django.contrib.auth.models import User #django自带 class UserProfile(models.Model): " ...

最新文章

  1. 免费教材丨第58期:机器学习相关汇总资料大放送(中)
  2. AI也会查水表啦!德国小哥开发水表读取器,可OTA升级,成本不到80元
  3. clover写入efi_2014 黑苹果折腾之clover efi全新安装
  4. MSSQL 判断表是否存在的两种方法
  5. 关于Visual Studio 2019的前期详情
  6. linux 4.9 内核 nptl,【linuxThread和NPTL】
  7. Vim直接打开Tampermonkey网址的方法。
  8. gets函数用不了_函数篇:指数函数
  9. Eclipse的部署项目时的配置
  10. Percona XtraBackup
  11. GridView数据导出到Excel的类
  12. 【黑苹果教程】修复OS X 10.11+上HD4200/HD4400/HD4600/HD5600驱动
  13. 酷安绿色版 免安装无需UWP版
  14. 2022年大数据技能大赛国赛(模块A,B)
  15. 用m ip衡量的计算机性能指标是,ip网络技术要求网络性能参数与指标.pdf.pdf
  16. 【代码质量】-阿里巴巴java开发手册(代码质量提升神器)学习笔记
  17. android studio一个好看的字体设置
  18. 2021年危险化学品经营单位主要负责人考试资料及危险化学品经营单位主要负责人新版试题
  19. 美国留学申请干货--转自 AdmitWrite留学(无图)
  20. Conflux人物志 | 杨哲:Sheldon Pro

热门文章

  1. 如何使用计算机网络打印机,电脑重新连接网络打印机的操作方法-电脑自学网...
  2. RPA:让电商财务拥有“分身术”
  3. 微信小程序实现跳转到另外一个小程序的方法
  4. LeetCode:838. 推多米诺————中等
  5. 服务器识别不了移动硬盘
  6. 属性动画+购物车+结算
  7. 劳务员培训建筑八大员培训劳务员建筑劳务分包管理突出的问题
  8. 在学校图书馆里进入WOS却依然无法搜索文献如何解决?
  9. windows server 2008r2 更新失败解决方案
  10. nova青春版支持鸿蒙吗,华为nova青春版有NFC吗 华为nova青春版支持NFC功能吗