zoukankan      html  css  js  c++  java
  • 项目:rbac 基于角色的权限管理系统;

    - 简单示意流程图

    - RBAC分析:

      - 基于角色的权限管理;

      - 权限等于用户可以访问的URL;

      - 通过限制URL来限制权限;

    - RBAC表结构组成:

    from django.db import models
    
    
    class Menu(models.Model):
        """
        菜单表:
        """
        title = models.CharField(verbose_name='菜单名称', max_length=32, db_index=True)  # 创建索引
        icon = models.CharField(verbose_name='图标', max_length=32)
    
        def __str__(self):
            return self.title
    
        class Meta:
            # db_table = 'menu'
            verbose_name = '菜单'
            verbose_name_plural = '菜单'
    
    
    class Jurisdiction(models.Model):
        """
        权限表
        """
        url = models.CharField(max_length=32)
        title = models.CharField(verbose_name='权限名称', max_length=32)
        name = models.CharField(verbose_name='反向解析别名', max_length=32, unique=True)
        menu = models.ForeignKey(to='Menu', null=True, blank=True)
    
        def __str__(self):
            return self.title
    
        class Meta:
            verbose_name = '权限'
            verbose_name_plural = '权限'
    
    
    class Role(models.Model):
        """
        角色表
        """
        name = models.CharField(verbose_name='角色名称', max_length=32)
        permissions = models.ManyToManyField(to='Jurisdiction')
    
        def __str__(self):
            return self.name
    
        class Meta:
            verbose_name = '角色'
            verbose_name_plural = '角色'
    
    
    class User(models.Model):
        """用户表"""
        name = models.CharField(verbose_name='用户名称', max_length=32)
        password = models.CharField(verbose_name='密码', max_length=64)
        roles = models.ManyToManyField(to="Role")
    
        def __str__(self):
            return self.name
    
        class Meta:
            verbose_name = '用户'
            verbose_name_plural = '用户'
    models表结构

      - 菜单表;(用于生成二级菜单)

        - 字段:

          - id:

          - title:

          - icon:

      - 用户表;

        - 字段:

          - id:

          - name:

          - pwd:

      - 角色表;

        - 字段:

          - id:

          - name:

      - 权限表;

        - 字段:

          - id:

          - url:

          - name:

          - menu:

      - 用户与角色关联表;

        - 字段:

          - id

          - uid

          - rid

      - 角色与权限关联表;

        - 字段:

          - id

          - rid

          - pid

    - 请求访问流程:

      - 中间件:

        - 详情代码:

    import re
    
    from django.shortcuts import redirect, HttpResponse
    from django.utils.deprecation import MiddlewareMixin
    
    from django.conf import settings
    
    
    class RbacMiddleware(MiddlewareMixin):
        """
        权限校验的中间件
        """
    
        def process_request(self, request):
            """
            请求校验
            :param request:
            :return:
            """
    
            # 1. 处理白名单
            for ele in settings.VALID_LIST:
                if re.match(ele, request.path_info):
                    return None  # 通过白名单,无需再做权限校验
    
            # 2. 权限校验;去session中获取权限然后对用户请求的url 一一进行匹配。
            permission_dict = request.session.get(settings.RBAC_PERMISSION_SESSION_KEY)
            if not permission_dict:
                return redirect('/login/')
    
            flag = False
            for name, info in permission_dict.items():
                reg = "^%s$" % info['url']
                if re.match(reg, request.path_info):
                    flag = True
                    break
            if not flag:
                return HttpResponse('无权访问')
    中间件代码

        - 进行白名单设置,若访问的是白名单中的URL不做任何限制,例如访问:login/   admin/.* 等等

        - 非白名单中的URL访问的验证;

          - 获取用户浏览器中的session信息,若不存在,则让用户进行登录;

      - login视图:

        - 登录验证:

          - 代码详情:

    from django.shortcuts import render, redirect
    from rbac import models
    
    from crm.utils.md5 import gen_md5
    from rbac.service.permission import init_permission
    
    
    def login(request):
        """
        用户登录
        :param request:
        :return:
        """
        if request.method == "GET":
            return render(request, 'login.html')
    
        user = request.POST.get('user')
        pwd = request.POST.get('pwd')
        pwd_md5 = gen_md5(pwd)
        # 根据用户名和密码去数据库校验,是否用户合法。
        user_object = models.User.objects.filter(name=user, password=pwd_md5).first()
    
        if not user_object:
            return render(request, 'login.html', {'msg': '用户名或密码错误'})
    
        # 用户登录成功,获取用户权限信息并放入到session中。
        # 初始化session数据
        init_permission(user_object, request)
        return redirect('/user/')
    login视图以及初始化入口

          - 获取form表单信息与数据库进行比较,验证不通过,则返回错误信息;

          - 验证通过,则进行数据初始化,并通过session写入用户浏览器中;

          - 返回重定向;

        - 数据初始化:

          - 根据用户姓名从数据库获取数据,组成相对应的数据结构,写入session;

          - 详情代码:

    from django.conf import settings
    
    
    def init_permission(user, request):
        """
        权限初始化
        :param user: 用户对象
        :param request:  请求相关信息:request.session
        :return: 无
        """
        # 1. 获取权限信息
        # user.roles.all()
        permission_queryset = user.roles.filter(permissions__id__isnull=False).values('permissions__id',
                                                                                      'permissions__title',
                                                                                      'permissions__url',
                                                                                      'permissions__name',
                                                                                      'permissions__menu_id',
                                                                                      'permissions__menu__title',
                                                                                      'permissions__menu__icon').distinct()
        # 2. 将权限和菜单信息放入到session中。设计:权限和菜单的数据结构。
        """
        权限结构 = {
            'user':{'url':'/user/'},
            'user_add':{'url':'/user/add/'},
            ...
        }
        菜单结构 = {
            菜单id:{
                title:'xxx',
                icon:'xx',
                children:[
                    {'title':'xxx','name':'xxx','url':'xxx'} # 能做菜单的权限
                ]
            }
        }
        生成的两种数据结构:
            - permission_dict: 权限认证的字典
            - menu_dict:生成二级菜单以及权限控制到按钮级别的字典
        """
        permission_dict = {}
        menu_dict = {}
        for row in permission_queryset:
            permission_dict[row["permissions__name"]] = {"url": row["permissions__url"]}
            if not row.get("permissions__menu_id"):
                continue
            if not menu_dict.get(row["permissions__menu_id"]):
                menu_dict[row["permissions__menu_id"]] = {
                    "title": row["permissions__menu__title"],
                    "icon": row["permissions__menu__icon"],
                    "children": []
                }
    
            menu_dict[row["permissions__menu_id"]]["children"].append(
                {
                    "title": row["permissions__title"],
                    "name": row["permissions__name"],
                    "url": row["permissions__url"],
                }
            )
    
        # 3. 写入session
        request.session[settings.RBAC_PERMISSION_SESSION_KEY] = permission_dict
        request.session[settings.RBAC_MENU_SESSION_KEY] = menu_dict
    初始化数据结构代码

    - 难点:

      - 表关联关系;

    - 第一版:
        - 根据用户所拥有的权限多少进行限制:
            - 需要将用户和权限进行关联:
                - 用户表
                - 权限表
            - 假若用户很多,以及权限很多;
            - 生成的关联表中的数据十分多,不利于查询;
    
    
    - 第二版:
        - 在第一版的基础上为用户创建角色:
            - 让用户表与角色进行关联;
            - 角色表与权限表进行关联;
        - 通过对不同的角色赋予不同的权限,在通过用户的角色来限制用户的权限;
            - 用户与角色需要多对多的关系,生成第三张关联表;
                - 介与用户或许会有许多不同的角色身份
                - 每个角色都会有许多用户的存在
                - 生成第三张 用户与角色关联表;
    
            - 同时 权限与角色也是需要多对多的关系:
                - 每个权限都有可能会有不同的角色都使用
                - 每个角色都有会有不同的多个权限使用;
                - 生成第四张 角色与权限关联表
    
    
    - 补充:菜单表:
        - 为了在生成二级菜单时,辅助生成的一张表;
        - 详情可见下面的菜单结构的分析

      - 数据结构搭建;

        - 两种数据结构:

    权限结构 = {
        'user':{'url':'/user/'},
        'user_add':{'url':'/user/add/'},
        ...
    }
    菜单结构 = {
        菜单id:{
            title:'xxx',
            icon:'xx',
            children:[
                {'title':'xxx','name':'xxx','url':'xxx'} # 能做菜单的权限
            ]
        }
    }

      

        - 权限结构:

          - 在权限结构中,用url的别名作为key,url的路由信息作为value,组成字典;

          - 在中间件中,读取用户携带的该权限字典中所有的values,然后循环判断即可;

          - 若想省事,可以直接组成列表;

        - 菜单结构:

          - 在所有的权限中,可以做菜单的权限不多;例如:添加,删除,更改,便不能作为菜单显示,只能作为按钮显示;

          - 需要在数据库中将可以做菜单,和不可以做菜单的表做出区别;

            - 在这里采取的做法是另外创建一张菜单表,和权限表关联,如果某条权限可以作为菜单,则将该信息和菜单表进行关联,若不可,则为None;

            - 在显示菜单的时候,根据该字段是否有值来进行判断,以便于生成菜单结构字典;

          - 若该菜单id的值存在,且在菜单结构中买有该菜单id的key则新建一个字典,该字典中所对应的 children为空列表。

          - 往所有菜单id相对应的值中的字典中的children对应的列表里添加所有的信息字典;

          - 没有菜单id的权限则continue掉;

    - 扩展点:

      - 权限控制到按钮级别;

        - 母板:

    <!DOCTYPE html>
    <html lang="en">
    <head>
        <meta charset="UTF-8">
        <meta http-equiv="x-ua-compatible" content="IE=edge">
        <meta name="viewport" content="width=device-width, initial-scale=1">
        <title>Title</title>
    </head>
    <body>
        <div style=" 20%;float: left">
            {% load rbac %}
    
            {% menu request %}
        </div>
    
        <div style=" 80%;float: left">
            {% block content %} {% endblock %}
        </div>
    
    </body>
    </html>
    简单母板

        - filter函数;

    from django.template import Library
    from django.conf import settings
    
    register = Library()
    
    
    @register.filter
    def has_permission(name, request):
        permission_dict = request.session[settings.RBAC_PERMISSION_SESSION_KEY]
    
        if name in permission_dict:
            return True
    自定义的filter

        - inclusion_tag函数;

    # rbac.py
    
    from django.template import Library
    from django.conf import settings
    
    register = Library()
    
    
    @register.inclusion_tag('menu.html')
    def menu(request):
        menu_dict = request.session[settings.RBAC_MENU_SESSION_KEY]
        return {'menu_dict': menu_dict}
    
    
    ###########################
    
    # menu.html
    <ul>
        {% for menu in menu_dict.values %}
        <li>{{ menu.title }}</li>
            <ul>
                {% for child in menu.children %}
                    <li><a href="{{ child.url }}">{{ child.title }}</a></li>
                {% endfor %}
            </ul>
        {% endfor %}
    </ul>
    自定义的inclusion

        - 继承母板以及控制到按钮级别权限的简单示例:

    {% extends 'layout.html' %}
    {% load rbac %}
    {% block content %}
        <h1>右侧内容</h1>
        {% if 'user_add'|has_permission:request %}
        <a href="">添加</a>
        {% endif %}
    
    
        <table>
            <tbody>
                {% for row in data_list %}
                    <tr>
                        <td>{{ row.name }}</td>
                        <td>{{ row.age }}</td>
                        <td>
                            {% if 'user_edit'|has_permission:request %}
                            <a href="">编辑</a>
                            {% endif %}
    
                            {% if 'user_del'|has_permission:request %}
                            <a href="">删除</a>
                            {% endif %}
                        </td>
                    </tr>
                {% endfor %}
            </tbody>
        </table>
    
    {% endblock %}
    user_list.html

      - js控制菜单hide

      - 分页组件;

      - 

  • 相关阅读:
    关于js计算非等宽字体宽度的方法
    [NodeJs系列]聊一聊BOM
    Vue.js路由管理器 Vue Router
    vue 实践技巧合集
    微任务、宏任务与Event-Loop
    事件循环(EventLoop)的学习总结
    Cookie、Session和LocalStorage
    MySQL 树形结构 根据指定节点 获取其所在全路径节点序列
    MySQL 树形结构 根据指定节点 获取其所有父节点序列
    MySQL 创建函数报错 This function has none of DETERMINISTIC, NO SQL, or READS SQL DATA in its declaration and binary logging is enabled (you *might* want to use the less safe log_bin_trust_function_creators
  • 原文地址:https://www.cnblogs.com/Fushengliangnian/p/9931200.html
Copyright © 2011-2022 走看看