权限管理---设计分析以及具体细节

Posted maaosheng

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了权限管理---设计分析以及具体细节相关的知识,希望对你有一定的参考价值。

说起权限我们大家都知道,不一样的角色会有不一样的权限。比如就像学生管理系统一样,管理员,老师,学生之间的权限都是不一样的,那么展示的页面也是不一样的。所以,我们现在来看看具体操作。

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

一、先创建一个 项目,建一个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
            ]
        ,
    
技术图片

 

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

 

以上是关于权限管理---设计分析以及具体细节的主要内容,如果未能解决你的问题,请参考以下文章

权限系统组织管理—具体设计说明书

Linux系统下用户以及权限管理

权限管理设计------之数据库设计

通用权限管理设计数据库结构设计

通用权限管理设计 之 数据库结构设计

RBAC从入门到精通