Django+vue3权限菜单rabc设计和动态路由

news/2024/7/9 23:53:03 标签: django, python, 后端, vue, RBAC, 权限控制

本次是基于Django和vue实现

github源码:nineaiyu/xadmin-server: xadmin-基于Django+vue3的rbac权限管理系统 (github.com)

服务器设计及部分代码 

权限控制的话,可以基于Django的permission进行控制,并通过访问api的URL操作

核心代码如下

python">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模型字段比较多,如下

python">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表已经设计完成,那么接下来就更简单了,还需要一个获取所有路由的方法,当添加权限的时候,可以方便的选择相应的路由权限

python">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二次开发的, 省去了前端开发,直接上手。

 


http://www.niftyadmin.cn/n/1009775.html

相关文章

2023.7.2-逆向显示键入的整数

功能&#xff1a;输入一个整数(多位)&#xff0c;逆向显示输入的结果。 程序&#xff1a; int main() {int a;printf("请输入一个整数&#xff1a;");scanf("%d",&a);if (a < 0)printf("请输入一个正整数");else{while (a>0){printf…

图像分割的大变革:从SAM(分割一切)到FastSAM、MobileSAM

前言 SAM就是一类处理图像分割任务的通用模型。与以往只能处理某种特定类型图片的图像分割模型不同&#xff0c;SAM可以处理所有类型的图像。 在SAM出现前&#xff0c;基本上所有的图像分割模型都是专有模型。比如&#xff0c;在医学领域&#xff0c;有专门分割核磁图像的人工…

深度学习视角下的视频息肉分割

结直肠癌(CRC)是全球第二大致命癌症和第三大常见的恶性肿瘤&#xff0c;据估计每年会在全球范围内造成数百万人发病和死亡。结直肠癌患者在第一阶段的生存概率超过95%&#xff0c;但在第四和第五阶段却大幅下降到35%以下。因此&#xff0c;通过结肠镜、乙状结肠镜等筛查技术对阳…

036:mapboxGL点击某位置,转换坐标为地址,弹出地理信息

第036个 点击查看专栏目录 本示例的目的是介绍演示如何在vue+mapbox中点击某位置,转换坐标位地址,弹出地理信息. 直接复制下面的 vue+mapbox源代码,操作2分钟即可运行实现效果 文章目录 示例效果配置方式示例源代码(共106行)相关API参考:专栏目标示例效果 配置方式 1)…

MySQL8.0安装详细教程

前言&#xff1a; MySQL版本区别&#xff1a; ● MySQL Community Server&#xff1a;Community是社区版本&#xff0c;开源免费&#xff0c;但不提供官方技术支持&#xff1b; ● MySQL Enterprise Edition&#xff1a;Enterprise企业版本&#xff0c;需付费&#xff0c;可以…

发送邮箱验证码【spring boot】

⭐前言⭐ ※※※大家好&#xff01;我是同学〖森〗&#xff0c;一名计算机爱好者&#xff0c;今天让我们进入学习模式。若有错误&#xff0c;请多多指教。更多有趣的代码请移步Gitee &#x1f44d; 点赞 ⭐ 收藏 &#x1f4dd;留言 都是我创作的最大的动力&#xff01; 1. 思维…

String中的常用方法

length()&#xff1a;返回字符串的长度。 String str "Hello"; int length str.length(); // length的值为5charAt(int index)&#xff1a;返回字符串中指定索引位置的字符。 String str "Hello"; char ch str.charAt(1); // ch的值为esubstring(int…

装饰器设计模式应⽤-JDK源码⾥⾯的Stream IO流

装饰器设计模式在JDK源码⾥⾯应⽤场景 抽象组件&#xff08;Component&#xff09;&#xff1a;InputStream 定义装饰⽅法的规范被装饰者&#xff08;ConcreteComponent) : FileInputStream、ByteArrayInputStream Component的具体实现&#xff0c;也就是我们要装饰的具体对…