• 270 第九篇:BBS项目01:基于角色权限管理:rbac设计分析以及具体细节


    BBS博客园项目完整代码日后上传。这里只写一下web项目里的权限问题。说起权限我们大家都知道,不一样的角色会有不一样的权限。比如就像学生管理系统一样,管理员,老师,学生之间的权限都是不一样的,那么展示的页面也是不一样的。所以,我们现在来看看具体操作。

    目标:生成一个独立的组件,到哪都能用

    一、先创建一个 项目,建一个app01和rbac的应用

    二、表结构设计

     1、先看配置文件合适不,给创建的rbac在配置文件里面设置一下
        找到INSTALLED_APPS=['rbac']

    配置静态文件

    2、设计表结构
        models中创建类:五个类,七张表
        角色表:
        用户表:
        权限表:

        权限组表:

        菜单表: 

        角色表和权限表是多对多的关系(一个角色可以有多个权限,一个权限可以对应多个角色)
        用户表和角色表是多对多的关系(一个用户可以有多个角色,一个角色有多个用户)

        所以有会多生成两张关系表

        一个菜单下面有多个组

        一个组下面有多个菜单

        一个菜单下面有多个权限

     

    from django.db import models
    
    # Create your models here.
    class Role(models.Model):
        title = models.CharField(max_length=32,verbose_name="角色")
        permissions = models.ManyToManyField(to="Permission",verbose_name="拥有权限的角色",blank=True)  #权限和角色是多对多的关系
    
        def __str__(self):
            return self.title
        class Meta:
            verbose_name_plural = "角色表"
    
    class Permission(models.Model):
        title = models.CharField(max_length=32,verbose_name="权限名")
        url = models.CharField(max_length=32,verbose_name="带正则的url")
        codes = models.CharField(max_length=32,verbose_name="代码")
        group = models.ForeignKey(to="Group",verbose_name="所属组",blank=True)  #组和权限是一对多的关系,一个组有多个权限
        menu_gp = models.ForeignKey(to='Permission',related_name='aaa',null=True,blank=True,verbose_name="组内菜单")
        def __str__(self):
            return self.title
        class Meta:
            verbose_name_plural = "权限表"
    
    class UserInfo(models.Model):
        name = models.CharField(max_length=32,verbose_name="姓名")
        password = models.CharField(max_length=64,verbose_name="密码")
        email = models.CharField(max_length=32,verbose_name="邮箱")
        roles = models.ManyToManyField(to="Role",blank=True)  #用户和角色是多对多的关系
        def __str__(self):
            return self.name
        class Meta:
            verbose_name_plural = "用户表"
    
    class Group(models.Model):
        title = models.CharField(max_length=32,verbose_name="组名称")
        menu = models.ForeignKey(to="Menu",verbose_name="组内菜单",blank=True)  #一个组下有多个菜单
        def __str__(self):
            return self.title
        class Meta:
            verbose_name_plural = "权限"
    
    class Menu(models.Model):
        caption = models.CharField(max_length=32,verbose_name="菜单")
        def __str__(self):
            return self.caption
        class Meta:
            verbose_name_plural = "菜单表"

    具体分析为什么要多加个code列和权限组表呢?

    1、我们一般是先看到的是列表页面,在这个页面上是否显示添加,是否显示编辑,是否显示删除,都是需要判断的
       有无添加权限,有无删除权限,有无编辑权限,我们可以给每一个url一个代号

    dict = {
        1:{                    代号
              /userinfo/            list
           /userinfo/add/       add
           /userinfo/del(d+)/    del 
           /userinfo/edit(d+)/    edit
        }
      }         

    不仅在列表页面需要知道他有那些权限,在其他页面也知道他有那些权限
    所以上面的方案还是有点不好,那么我们采取下面的方案。将代号取出来放在一个列表里面

    dict = {
          1:{
                  "codes":["list","add","del","edit"]
                 urls:[
                    "/userinfo/",
                    "/userinfo/add"/,
                    "/userinfo/del(d+)/ ",
                    "/userinfo/edit(d+)/ ",
                  ]    
            }
          2:{
               "codes":{"list","add","del","edit"}
                urls:[
                     "/order",
                     "/order/add"/,
                      "/order/del(d+)/ ",
                     "/order/edit(d+)/ ",
                   ]    
           }
    }            

    把这个字典存到session中
    当你访问页面的时候我就知道你有什么权限
    一个url对应一个code
    多个url对应一个组

    注意:
      关联字段 null = True        数据库用的时候可以为空
      关联字段 blank = True     admin用的时候可以为空
    当出现这个错误的时候

    解决办法

    python manage.py migrate --fake 废弃

    三、通过django-admin录入权限数据

    - 先创建一个超级用户 python3 manage.py createsuperuser
        - 用户名 root
        - 密码 zhy123456
        - 在admin.py 中
            from rbac import models
            admin.site.register(models.Permission)
            admin.site.register(models.Role)
            admin.site.register(models.UserInfo)
          这样的话上去的是英文的,如果你想让中文显示就在类中加一个类
            class Meta:
               verbose_name_plural = "权限表"
          - 当你给关联字段录入数据的时候会有错误提示,那么在类中你的那个关联字段在加一个属性blank = True 可以为空
          permissions = models.ManyToManyField(to="Permission",verbose_name="具有的所有权限", blank=True)

    四、编写登录

    1.编写登录
    2.如果用户验证成功就设置session
    3.先查出当前用户的所有的权限
    4.从这些权限中找到所有的url,吧这些url放到session中
       这些都是在rbac里面的操作,如果我们做一些复杂的操作,可能会有好多的代码
     我们写rbac的目的是做成一个公共的组件,为了让别人省事
     我们在创建一个server的文件夹,里面创建一个init_permission的py文件。

     结构化数据:方便以后做操作。。。

    dict = {
          1:{
                  "codes":["list","add","del","edit"]
                 urls:[
                    "/userinfo/",
                    "/userinfo/add"/,
                    "/userinfo/del(d+)/ ",
                    "/userinfo/edit(d+)/ ",
                  ]    
            }
          2:{
               "codes":{"list","add","del","edit"}
                urls:[
                     "/order",
                     "/order/add"/,
                      "/order/del(d+)/ ",
                     "/order/edit(d+)/ ",
                   ]    
           }
    }            

    5.拿到用户请求的url去session里面做验证
      获取当前请求的url
      获取session中保存当前用户的权限
      然后开始验证
      如果匹配成功就有权访问
      如果匹配不成功就无权访问
      用re去匹配的时候,re.match(/userinfo/,/userinfo/add) #都能匹配到
      那么要记得在匹配正则的时候加个起始符和终止符regex = "^{0}$".format(url)
      def login(request):
        .....
        设置session
      def index(request):
        ....
        获取session
      def userinfo(request):
        获取session
    这样如果有好多个函数,就的重复好多代码,我们可以用中间件来处理
    中间件和装饰器的区别:
      中间件用来做批量处理
      如果函数不多的话可以用加装饰器的方法

    五、中间件:获取session,并且当用户匹配成功的时候,先把code保存在request中,方便以后判断

    1、记得要配置白名单

    2、必须继承MiddlewareMixin这个类

    class MiddlewareMixin(object):
        def __init__(self, get_response=None):
            self.get_response = get_response
            super(MiddlewareMixin, self).__init__()
    
        def __call__(self, request):
            response = None
            if hasattr(self, 'process_request'):
                response = self.process_request(request)
            if not response:
                response = self.get_response(request)
            if hasattr(self, 'process_response'):
                response = self.process_response(request, response)
            return response

    六、设计权限管理-----问题:在访问列表页面时,是否需要判断有无添加权限、有无删除权限、有无编辑权限。 

    views

    def userinfo(request):
        # 方式一
        # Page_permission = request.permission_code_list
        # 方式二:实例化
        page_permission = BasePagePermission(request.permission_code_list)
        print("page_permission",request.permission_code_list)
        data_list = [
            {"id":1,"name":"xxx1"},
            {"id":2,"name":"xxx2"},
            {"id":3,"name":"xxx3"},
            {"id":4,"name":"xxx4"},
            {"id":5,"name":"xxx5"},
        ]
        return render(request,"userinfo.html",{"data_list":data_list,"page_permission":page_permission})
    

    在模板userinfo.html中:两种使用方式

    方式一:

    <table>
        {% if "add" in Page_permission %}
            <a href="#">添加</a>
        {% endif %}
        {% for row in data_list %}
             <tr>
                <td>{{ row.id }}</td>
                <td>{{ row.name }}</td>
                 {% if "edit" in Page_permission %}
                    <td><a href="#">编辑</a></td>
                 {% endif %}
               {% if "del" in Page_permission %}
                     <td>{<a href="#">删除</a></td>
               {% endif %}
            </tr>
        {% endfor %}
    </table>

    如果不想像上面一样每个都判断,那么还有第二种方法,

    方式二:

    吧permission_code_list处理一下
    在views中定义一个类
    class BasePagePermission(object):
        def __init__(self,code_list):
            self.code_list = code_list
        def has_add(self):
            if "add" in self.code_list:
                return True
        def has_del(self):
            if "del" in self.code_list:
                return True
        def has_edit(self):
            if "edit" in self.code_list:
                return True
    实例化:page_permission = BasePagePermission(request.permission_code_list)
    在模板中
    <table>
        {% if page_permission.has_add %}
            <a href="#">添加</a>
        {% endif %}
        {% for row in data_list %}
             <tr>
                <td>{{ row.id }}</td>
                <td>{{ row.name }}</td>
                 {% if page_permission.has_edit %}
                    <td><a href="#">编辑</a></td>
                 {% endif %}
               {% if page_permission.has_del %}
                     <td>{<a href="#">删除</a></td>
               {% endif %}
            </tr>
        {% endfor %}
    </table>

    七、设计菜单管理-----问题:1、如何生成菜单

                     2、怎么让这些菜单分级显示并且如果当前访问的url权限默认展开如果是组内菜单就加粗或者变红

                  3、非菜单url,默认选中原菜单。(如果你是点击用户列表进来的,那么你看到页面了,如果你点击添加的时候,你的那个用户列看不见了,这就不好了。所以要设计当你点击添加按钮的时候,那个用户列表被默认选中)

    菜单管理
      菜单一
        用户管理
        权限管理
      菜单二
        订单管理
        角色管理

    分级做了菜单。这些菜单该显示什么菜单?是当前用户登录之后从数据库拿到这个用户拥有的权限,然后把权限搞成菜单

    在表里面设计了一个组内菜单(自关联 ),当menu_gp_id为NULL就代表可以作为菜单

    1、在初始化的时候,初始化权限信息,获取权限信息并放置到session中

     menu_list = []
        for item in permission_list:
            tpl = {
                "id":item["permissions__id"],
                "title":item["permissions__title"],
                "url":item["permissions__url"],
                "menu_gp_id":item["permissions__menu_gp_id"],
                "menu_id":item["permissions__group__menu_id"],
                "menu_title":item["permissions__group__menu__caption"]
            }
            menu_list.append(tpl)
        request.session[settings.PERMISSION_MENU_KEY] = menu_list

    因为是要在页面上渲染,一般我们会在视图函数的render里面加{"":变量}这样渲染,
    但是还有个更好用的方法:用自定义的标签

    具体操作:
      1、找到app创建一个templatetags的文件夹
      2、然后在里面随便创建一个文件
      3、导入form django.template import Library
           register = Library()

          方式一:
            @register.simple_tag
            def menu():
              return "菜单"     这里返回啥页面上就显示啥
            然后在母版里面导入mnue.html
            {% load rbac %}

          方式二:
            @register.includsion_tag("xxx.html") #这里存放的是html文件,,,@register.includsion_tag("xxx.html")   自动会读这个文件并且把返回值拿到在页面上渲染
            def menu():
              return "菜单"     这里返回啥页面上就显示啥
            “在母版中:{%menu_html  request%}     request是参数,记得要加上{% load rbac %}

      4、注意:
            如果有两个文件夹同名,避免发生冲突:就再创建一个文件夹包起来

     2、去Session中获取菜单相关信息,匹配当前URL,生成菜单

    先把和菜单相关的所有字段取出来

    menu_list = [
        {'id': 1, 'title': '用户列表', 'url': '/userinfo/', 'menu_gp_id': None, 'menu_id': 2, 'menu_title': '菜单二'}, 
        {'id': 2, 'title': '添加用户', 'url': '/userinfo/add/', 'menu_gp_id': 1, 'menu_id': 2, 'menu_title': '菜单二'}, 
        {'id': 3, 'title': '删除用户', 'url': '/userinfo/del/(\d+)/', 'menu_gp_id': 1, 'menu_id': 2, 'menu_title': '菜单二'}, 
        {'id': 4, 'title': '编辑用户', 'url': '/userinfo/edit/(\d+)/', 'menu_gp_id': 1, 'menu_id': 2, 'menu_title': '菜单二'}, 
        {'id': 5, 'title': '订单列表','url': '/order/', 'menu_gp_id': None, 'menu_id': 1, 'menu_title': '菜单一'}, 
        {'id': 6, 'title': '添加订单', 'url': '/order/add/', 'menu_gp_id': 2, 'menu_id': 1, 'menu_title': '菜单一'}, 
        {'id': 7, 'title': '删除订单', 'url': '/order/del/(\d+)/', 'menu_gp_id': 2, 'menu_id': 1, 'menu_title': '菜单一'}, 
        {'id': 8, 'title': '编辑订单', 'url': '/order/edit/(\d+)/', 'menu_gp_id': 2, 'menu_id': 1, 'menu_title': '菜单一'}
    ]

    然后循环列表找出可以作为菜单的权限

    {
        1: {'id': 1, 'title': '用户列表', 'url': '/userinfo/', 'menu_gp_id': None, 'menu_id': 2, 'menu_title': '菜单二'}, 
        5: {'id': 5, 'title': '订单列表', 'url': '/order/', 'menu_gp_id': None, 'menu_id': 1, 'menu_title': '菜单一'}
    }

    再次循环列表向上边的字典中添加active

    {
        1: {'id': 1, 'title': '用户列表', 'url': '/userinfo/', 'menu_gp_id': None, 'menu_id': 2, 'menu_title': '菜单二', 'active': True},
        5: {'id': 5, 'title': '订单列表', 'url': '/order/', 'menu_gp_id': None, 'menu_id': 1, 'menu_title': '菜单一'}
    }

    结构化数据(吧上面得到的数据化成下面这样格式的,方便以后使用)

    {
        1: {
                'menu_id': 1,
                'menu_title': '菜单一',
                'active': None, 
                'children': [
                        {'title': '订单列表', 'url': '/order/', 'active': None}
                    ]
                }
        2: {
            'menu_id': 2, 
            'menu_title': '菜单二', 
            'active': True,
            'children': [
                    {'title': '用户列表', 'url': '/userinfo/', 'active': True}
                ]
            },
        
    }

    八、具体代码详见下一篇。。。。。。。。

  • 相关阅读:
    xunjian.sh
    192.168.50.235配置
    自动备份并删除旧日志
    bg和fg命令
    linux之sed用法
    正则表示第二行,第二列
    linux下redis安装
    Hma梳理
    linux 系统监控、诊断工具之 lsof 用法简介
    java的基本数据类型有八种
  • 原文地址:https://www.cnblogs.com/abdm-989/p/14093308.html
Copyright © 2020-2023  润新知