本次是基于Django和vue实现
github源码:nineaiyu/xadmin-server: xadmin-基于Django+vue3的rbac权限管理系统 (github.com)
服务器设计及部分代码
权限控制的话,可以基于Django的permission进行控制,并通过访问api的URL操作
核心代码如下
import re
from django.conf import settings
from rest_framework.exceptions import PermissionDenied, NotAuthenticated
from rest_framework.permissions import BasePermission
from system.models import Menu
def get_user_permission(user_obj):
menu = []
if user_obj.roles:
menu_obj = Menu.objects.filter(userrole__in=user_obj.roles.all()).distinct()
menu = menu_obj.filter(is_active=True, menu_type=2).values('path', 'component').all().distinct()
return menu
class IsAuthenticated(BasePermission):
"""
Allows access only to authenticated users.
"""
def has_permission(self, request, view):
auth = bool(request.user and request.user.is_authenticated)
if auth:
if request.user.is_superuser:
return True
url = request.path_info
for w_url in settings.PERMISSION_WHITE_URL:
if re.match(w_url, url):
return True
permission_data = get_user_permission(request.user)
for p_data in permission_data:
if p_data.get('component') == request.method and re.match(f"/{p_data.get('path')}", url):
return True
raise PermissionDenied('权限不足')
else:
raise NotAuthenticated('未授权认证')
因此,需要对menu表进行设计,由于涉及到了前端vue路由,因此,menu模型字段比较多,如下
class DbBaseModel(models.Model):
created_time = models.DateTimeField(auto_now_add=True, verbose_name="添加时间")
updated_time = models.DateTimeField(auto_now=True, verbose_name="更新时间")
description = models.CharField(max_length=128, verbose_name="描述信息", null=True, blank=True)
class Meta:
abstract = True
class MenuMeta(DbBaseModel):
title = models.CharField(verbose_name="菜单名称", max_length=256, null=True, blank=True)
icon = models.CharField(verbose_name="菜单图标", max_length=256, null=True, blank=True)
r_svg_name = models.CharField(verbose_name="菜单右侧额外图标iconfont名称,目前只支持iconfont", max_length=256,
null=True, blank=True)
is_show_menu = models.BooleanField(verbose_name="是否显示该菜单", default=True)
is_show_parent = models.BooleanField(verbose_name="是否显示父级菜单", default=False)
is_keepalive = models.BooleanField(verbose_name="是否开启页面缓存", default=False,
help_text='开启后,会保存该页面的整体状态,刷新后会清空状态')
frame_url = models.CharField(verbose_name="内嵌的iframe链接地址", max_length=256, null=True, blank=True)
frame_loading = models.BooleanField(verbose_name="内嵌的iframe页面是否开启首次加载动画", default=False)
transition_enter = models.CharField(verbose_name="当前页面进场动画", max_length=256, null=True, blank=True)
transition_leave = models.CharField(verbose_name="当前页面离场动画", max_length=256, null=True, blank=True)
is_hidden_tag = models.BooleanField(verbose_name="当前菜单名称或自定义信息禁止添加到标签页", default=False)
dynamic_level = models.IntegerField(verbose_name="显示标签页最大数量", default=1)
class Meta:
verbose_name = "菜单元数据"
verbose_name_plural = "菜单元数据"
ordering = ("-created_time",)
def __str__(self):
return f"{self.title}-{self.description}"
class Menu(DbBaseModel):
parent = models.ForeignKey(to='Menu', on_delete=models.SET_NULL, verbose_name="父节点", null=True, blank=True)
menu_type_choices = ((0, '目录'), (1, '菜单'), (2, '权限'))
menu_type = models.SmallIntegerField(choices=menu_type_choices, default=0, verbose_name="节点类型")
name = models.CharField(verbose_name="组件英文名称", max_length=128, unique=True)
rank = models.IntegerField(verbose_name="菜单顺序", default=9999)
path = models.CharField(verbose_name="路由地址", max_length=256)
component = models.CharField(verbose_name="组件地址", max_length=256, null=True, blank=True)
is_active = models.BooleanField(verbose_name="是否启用该菜单", default=True)
meta = models.OneToOneField(to=MenuMeta, on_delete=models.CASCADE, verbose_name="菜单元数据")
method_choices = (('GET', 'get'), ('POST', 'post'), ('PUT', 'put'), ('DELETE', 'delete'))
def delete(self, using=None, keep_parents=False):
if self.meta:
self.meta.delete(using, keep_parents)
super().delete(using, keep_parents)
class Meta:
verbose_name = "菜单信息"
verbose_name_plural = "菜单信息"
ordering = ("-created_time",)
def __str__(self):
return f"{self.name}-{self.menu_type}-{self.meta.title}"
最重要的menu表已经设计完成,那么接下来就更简单了,还需要一个获取所有路由的方法,当添加权限的时候,可以方便的选择相应的路由权限
import re
from collections import OrderedDict
from django.conf import settings
from django.urls import URLPattern, URLResolver
from django.utils.module_loading import import_string
def check_show_url(url):
for prefix in settings.PERMISSION_SHOW_PREFIX:
if re.match(prefix, url):
return True
def recursion_urls(pre_namespace, pre_url, urlpatterns, url_ordered_dict):
"""递归去获取URL
:param pre_namespace: namespace前缀,以后用户拼接name
:param pre_url: url前缀,以后用于拼接url
:param urlpatterns: 路由关系列表
:param url_ordered_dict: 用于保存递归中获取的所有路由
"""
for item in urlpatterns:
if isinstance(item, URLPattern):
if not item.name:
continue
if pre_namespace:
name = "%s:%s" % (pre_namespace, item.name)
else:
name = item.name
if not item.name:
raise Exception('URL路由中必须设置name属性')
url = pre_url + item.pattern.regex.pattern.lstrip('^')
# url = url.replace('^', '').replace('$', '')
if check_show_url(url):
url_ordered_dict[name] = {'name': name, 'url': url}
elif isinstance(item, URLResolver): # 路由分发,递归操作
if pre_namespace:
if item.namespace:
namespace = "%s:%s" % (pre_namespace, item.namespace)
else:
namespace = item.namespace
else:
if item.namespace:
namespace = item.namespace
else:
namespace = None
recursion_urls(namespace, pre_url + item.pattern.regex.pattern.lstrip('^'), item.url_patterns,
url_ordered_dict)
def get_all_url_dict(pre_url='/'):
"""
获取项目中所有的URL(必须有name别名)
"""
url_ordered_dict = OrderedDict()
md = import_string(settings.ROOT_URLCONF)
url_ordered_dict['#'] = {'name': '#', 'url': '#'}
recursion_urls(None, pre_url, md.urlpatterns, url_ordered_dict) # 递归去获取所有的路由
return url_ordered_dict.values()
前端设计
前端代码已经开源,GitHub如下: nineaiyu/xadmin-client: xadmin-基于Django+vue3的rbac权限管理系统 (github.com)q
前端是基于pure-admin二次开发的, 省去了前端开发,直接上手。