权限设计

发布时间 2023-12-06 00:00:01作者: 一笔带过

权限组件开发

为什么是什么?

1.为什么要开发关于权限相关的系统?
	在现实生活中,就有很多的关于权限的场景,比如:在超市,店长的权限永远比员工权限大。在公司,老板的权限永远比经理大。老板可以知道全部员工的工资多少,而经理只能知道自己部门的工资。这些就是权限的体现。

2.权限是什么?
	在web系统中一个url就是一个权限,那么一个人能访问的url,那么就有多少个权限

权限的表结构

第一版权限结构

用户表
ID User
1 小明
2 小张
3 小李
用户与权限(多对多表)
ID user_id(与user表形成1对多关系) Permissions_id(与权限表形成1对多关系)
1 1 1
1 2 1
1 3 1
1 1 2
权限表(url表)
ID Url Shows
1 /user/index 用户首页
2 /home 网站首页
3 /user/del 用户删除
实现: '用户' 直接对应 '权限',通过'第三张表的多对多关系' 绑定对应的权限关系
当前实现了对权限的关联,但是存在弊端!
缺点与弊端:
    1.如果有新来的用户需要权限,需要在 关联表中 进行1条1条的设置 # 权限建立麻烦 
    2.如果对某些用户需要修改权限,那么需要找出这类用户在 关联表中 进行增删改权限 # 修改权限麻烦
    3.如果权限表中得权限是大量的(1000个),用户的体量也很大(50000个) 那么每一次修改需要花费大量时间 # 面多权限量大用户量大情况进行修改不明智不可取

第二版权限结构

用户表
ID User
1 小明
2 小张
3 小李
4 小王
用户表 and 角色表(多对多关系)
ID user_id role_id
1 1 1(人事经理权限)
2 2 2(部门经理权限)
3 3 2(部门经理权限)
4 4 3(普通员工权限)
角色表
ID role_name
1 人事经理
2 部门经理
3 员工
角色 and 权限(多对多关系)
ID role_id permissions_id
1 1 1,2,3,4,5(角色:人事经理有5条权限)
2 2 1,2,5(角色:部门经理有3条权限)
3 3 1,2(角色:员工有2条权限)
权限表(url表)
ID Url Shows
1 /user/index 用户首页
2 /home 网站首页
3 /user/del 用户删除
4 /user/update 用户修改
5 /user/insert 用户添加
5 /user/select 用户查询
RBAC:基于角色控制权限管理
优点:
	1.可以通过角色设置权限(1个角色多个权限)
	2.如果用户可以拥有多个角色(有多少角色就有多少权限)
	3.如果新用户添加,那么直接添加对应的角色即可
	4.如果需要修改部分用户的权限,可以创建新的角色绑定权限赋予用户,修改这部分用户的角色权限。
	5.可以通过控制角色权限,而控制用户拥有的权限方便管理

在Django中进行创建表结构

from django.db import models

__all__ = ['Permissions', 'Role', 'User']


class Permissions(models.Model):
    class Mate:
        db_table = 'Permissions'

    id = models.AutoField(primary_key=True)
    title = models.CharField(max_length=128,verbose_name='标题')
    url = models.CharField(max_length=255, verbose_name='正则模式下的url')

    def __str__(self):
        return self.title

class Role(models.Model):
    class Mate:
        db_table = 'Role'

    id = models.AutoField(primary_key=True)
    role_name = models.CharField(max_length=255, verbose_name='角色名称')
    permissions = models.ManyToManyField(to='Permissions', db_table='Role_Permissions', verbose_name='对多对权限角色报绑定字段')

    def __str__(self):
        return self.role_name


class User(models.Model):
    class Mate:
        db_table = 'User'

    id = models.AutoField(primary_key=True)
    user_name = models.CharField(max_length=128, verbose_name='账户')
    password = models.CharField(max_length=128, verbose_name='密码')
    user_email = models.CharField(max_length=128, verbose_name='邮箱')
    role = models.ManyToManyField(to='Role', db_table='User_Role', verbose_name='多对多用户与角色绑定字段')

    def __str__(self):
        return self.user_name

基本权限控制

权限流程图

1.用户登录页面需不需要进行权限控制? # 不需要
2.用户在首页时需不需要进行权限控制? # 不需要
大致的执行流程

1.用户登录[判断用户的密码和账户,同时从数据库中获取权限的信息]
2.将权限的信息存储到session或者redis中[注意细节使用redis存储]
# 注意:
	1.如果存储到session中,用户同时登录,但是用户的权限发生变化怎么办?没办法进行实时的控制权限。
    2.建议存储到redis中或者mongodb中,利用一个表示 + 用户的id作为key进行存储。user_id:[权限1,权限2,]
    3.当进行对角色权限增删改查时,需要清空redis或者mongodb中对应拥有用户的存储的权限 键值对
    4.当每次登录,或者访问时,需要在api接口或者视图中,判断一下redis中是否存在权限,如果没有,从mysql中取权限存到redis中
3.当每次请求时[白名单除外(登录url/首页url/..)],从redis或者session中进行判断当前是否存在访问的权限,如果没有返回'报错信息' 如果有 就接着执行下面的中间


权限开发需要功能:
    1.登录[获取权限,存储到session中 or redis or mongodb]
    2.设置一个访问中间件[每次访问,都会将访问的url与权限列表(从存储的地方进行获取)中得url进行对比]
    3.如果通过,接着执行并返回请求数据。没有通过返回 中间件直接返回无权内容[django中间件特性,如果存在返回值就会直接返回内容,不会再进行执行]

快速开发流程

1.登录页面是否有权限(有)
2.POST请求,校验用户登录是否合法
3.获取登录当前用户的相关信息,存储到session中
4.用户在此访问服务器发起请求: http://xxx.xx.xx/index 后端编写中间件对用户进行访问权限的判断[当前访问的url是否存在权限列表中]

1.用户登录权限获取逻辑

from django.shortcuts import HttpResponse, render, redirect
from rbac.models import User # 导入用户表


def login(request):
	......
    1.根据用户获取用户全部角色
    获取用户全部的角色对象 
    	user_obj.role.all()
   	2.获取用户权限角色下的全部url
    	user_obj.role.filter(permissions__isnull=False).values('permissions__url').distinct()
    
    permissions_list = user_obj.role.filter(permissions__isnull=False).values('permissions__url').distinct()
    # 当前用户的获取权限列表
    permissions_url_list = [ i.get('permissions__url') for i in permissions_list]
    print(permissions_url_list)
    .....


注意:
    1.在获取角色下的url权限是,需要进行去重
    
	原因:'客户经理'可以有 index/权限 '部门经理'也可以 index/权限,如果角色同时拥有这两个角色,权限重复。
    
    # django orm 语句
    user_obj.role.filter(permissions__isnull=False).values('permissions__url').distinct()
    
    
    # sql 语句
    select DISTINCT rbac_permissions.url from rbac_user 
    INNER JOIN User_Role on rbac_user.id = User_Role.user_id
    INNER JOIN rbac_role on rbac_role.id = User_Role.role_id
    inner join Role_Permissions on rbac_role.id = Role_Permissions.role_id
    INNER JOIN rbac_permissions on Role_Permissions.permissions_id = rbac_permissions.id where rbac_user.id = 1 
    
    
    2.因为权限只是一些url,假设创建的角色关联的权限为空,那么需要进行去空操作
 	role角色 与 permissions权限 关联关系不能为空,必须有值存在(避免角色关联权限不能为空)
    
    # django orm语句
    user_obj.role.filter(permissions__isnull=False).values('permissions__url').distinct()
    
    # sql语句使用内联查询[INNER JOIN],只有满足条件的才会返回(自动将null排除) 如果使用左右查询会出现角色权限为null需要排除null
   	select url from (select DISTINCT rbac_permissions.url from rbac_user 
    INNER JOIN User_Role on rbac_user.id = User_Role.user_id
    INNER JOIN rbac_role on rbac_role.id = User_Role.role_id
    inner join Role_Permissions on rbac_role.id = Role_Permissions.role_id
    INNER JOIN rbac_permissions on Role_Permissions.permissions_id = rbac_permissions.id where rbac_user.id = 1) as permissions_url WHERE permissions_url.url not NULL

2.项目中间件逻辑部分

当用户请求时,进行触发
1.获取当前用户请求的url地址
2.获取当前用户在session/reids中保存的权限列表
3.匹配权限信息
4.面对大家够可以访问的url权限 需要设置一个白名单进行处理


1.中间件代码部分:
    
import re
from django.http import JsonResponse
from django.utils.deprecation import MiddlewareMixin  # 导入继承django中间件


# 创建中间件类
class RbacMiddlewareMixin(MiddlewareMixin):
    '''
    权限中间件认证
    process_request请求来时是进行执行
    django process_request 特点:存在返回值不在执行其他的中间件与视图,直接返回结果页面(默认返回none)
    '''
    
    def process_request(self, request): 
        # 白名单,如果有部分的url是需要匹配的通过权限比如admin,可以通过正则匹配的方式
        valid_url_list = [
            'login/',
            'admin/.*' 
        ]

        # 当前用户的url 获取的是/customer/list/ 进行替换为 customer/list/方便对比
        current_url = request.path_info.replace('/','',1) 
        
        1.通过正则的方式循环白名单,过滤掉不需要拦截的url
        for i in valid_url_list:
            if re.match(i,current_url):
                return None
		
        2.从session中获取权限列表
        permissions_url_list = request.session.get('permissions_url')
        if not permissions_url_list:
            return JsonResponse({"code":403,'error':'当前用户未登录,请进行登录后再访问'})
        
        # 通过变量控制是否有权限
        flag = False 

       
        3.循环权限列表,使用正则匹配确认当前的权限是否通过
        for url in permissions_url_list:
            
            为什么使用正则,正则匹配的更为准确,并且因为url中存在正则的url。
		   比如: customer/edit/(?P<cid>\d+)/  使用常规的匹配方式 customer/edit/1 判断无法通过
            正则匹配 re.match('customer/edit/(?P<cid>\d+)/','customer/edit/123456/')
            
            问题:但是使用正则匹配问题:匹配的内容没有终止起始
            比如:  re.match('customer/edit/','customer/edit/123456/') 也开始可以进行匹配到的
            解决: 加上正则终止和起始符号
            
            mathc_url = f"^{url}$"
            if re.match(mathc_url,current_url): 
                flag = True # 有权限赋值为True,如果是False那么就没有权限
        
        if not flag:
            # 未通过无权限
            return JsonResponse({"code":403,'error':'当前用户没有权限,请让管理员添加权限!'}) 
        
        
 2.在配置中将中间件进行注册
MIDDLEWARE = [
    
    
   '......process_request'
]

3.中间件逻辑部分-优化

1.将使用的变量放到配置文件夹中
config.py


permissions_key = 'permissions_url' # session key

valid_url_list =  [
            'login/',
            'admin/.*'
        ] # 白名单,如果有部分的url是需要匹配的通过权限比如admin,可以通过正则匹配的方式


2.将登录功能对权限的初始化分解(登录   与  权限初始化)
登录功能只需要导入当前函数进行初始化即可

permission_init.py

from rbac.config import permissions_key
def init_permission(user_obj,request):
    '''
    在登录时导入,进行权限的初始化(当前用户权限记录)
    user_obj: 用户对象,通过orm 获取用户的权限列表
    request: 请求对象,将权限存放在session中
    '''
    # 根据用户获取用户全部权限
    # 获取用户全部的角色对象
    # print(user_obj.role.all())
    # 1.获取当前用户下的全部的权限url(连续跨表查询) 同时去重[因为不同的角色权限会有相同:比如都有index/ hotm/] .distinct()
    # 2.因为权限只是一些url,假设创建的角色关联的权限为空,那么需要进行去空操作,permissions__isnull=False,role角色 与 permissions权限 关联关系不能为空,必须有值存在(避免角色关联权限不能为空)
    # print(user_obj.role.filter(permissions__isnull=False).values('permissions__url').distinct())
    
    permissions_list = user_obj.role.filter(permissions__isnull=False).values('permissions__url').distinct()
    permissions_url_list = [ i.get('permissions__url') for i in permissions_list]
    request.session[permissions_key] = permissions_url_list
    
    
3.中间件进行微调

import re
from django.http import JsonResponse
from django.utils.deprecation import MiddlewareMixin  # 导入继承django中间件

from rbac.config import permissions_key,valid_url_list # 权限配置文件中 key 白名单


# 创建中间件类
class RbacMiddlewareMixin(MiddlewareMixin):
    '''权限中间件认证'''
    
    def process_request(self, request):
        '''
        当用户请求时,进行触发
        1.获取当前用户请求的url地址
        2.获取当前用户在session中保存的权限列表
        3.匹配权限信息
        '''

        # 当前用户的url 获取的是/customer/list/ 进行替换为 customer/list/方便对比
        current_url = request.path_info.replace('/','',1) 
        for i in valid_url_list:
            if re.match(i,current_url):
                return None

        permissions_url_list = request.session.get(permissions_key)
        if not permissions_url_list:
            return JsonResponse({"code":403,'error':'当前用户未登录,请进行登录后再访问'})
        
        flag = False # 通过变量控制是否有权限

        # 因为在url中存在 customer/edit/(?P<cid>\d+)/ 正则的url需要使用正则进行匹配
        # customer/edit/1 正则匹配 customer/edit/(?P<cid>\d+)/
        for url in permissions_url_list:
            # 通过有有权限
            # 但是使用正则匹配问题:匹配的内容没有终止起始
            # 比如:  re.match('customer/edit/','customer/edit/123456/') 也开始可以进行匹配到的
            # 解决: 加上正则终止和起始符号
            mathc_url = f"^{url}$"
            if re.match(mathc_url,current_url): 
                flag = True # 有权限赋值为True,如果是False那么就没有权限
        
        if not flag:
            # 未通过无权限
            return JsonResponse({"code":403,'error':'当前用户没有权限,请让管理员添加权限!'}) 



       

权限之菜单

一级菜单设计思路/代码

权限表与菜单的关系 (包含关系)

1.需要在权限表中设置一个字段,用来表示 当前权限是否可以作为功能
2.在admin中进行配置当前字段
3.在权限出初始化函数中将,可以作为菜单的权限存储到列表保存到session中

1.表结构
from django.db import models

__all__ = ['Permissions', 'Role', 'User']


class Permissions(models.Model):
    class Mate:
        db_table = 'Permissions'

    id = models.AutoField(primary_key=True)
    title = models.CharField(max_length=128, verbose_name='标题')
    url = models.CharField(max_length=255, verbose_name='正则模式下的url')
    # 添加当前字段 当前字段为True那么就是可以当做菜单
    menu = models.BooleanField(blank=True,null=True,verbose_name='当前权限是否可以作为菜单') 

    def __str__(self):
        return self.title


class Role(models.Model):
    class Mate:
        db_table = 'Role'

    id = models.AutoField(primary_key=True)
    role_name = models.CharField(max_length=255, verbose_name='角色名称')
    permissions = models.ManyToManyField(to='Permissions', db_table='Role_Permissions', verbose_name='对多对权限角色报绑定字段',blank=True)

    def __str__(self):
        return self.role_name


class User(models.Model):
    class Mate:
        db_table = 'User'

    id = models.AutoField(primary_key=True)
    user_name = models.CharField(max_length=128, verbose_name='账户')
    password = models.CharField(max_length=128, verbose_name='密码')
    user_email = models.CharField(max_length=128, verbose_name='邮箱')
    role = models.ManyToManyField(to='Role', db_table='User_Role', verbose_name='多对多用户与角色绑定字段')

    def __str__(self):
        return self.user_name
    

2.在admin中进行设置对权限进行设置,那些权限可以作为菜单


3.在权限初始化函数中进行初始化菜单设置
from rbac.config import permissions_key, menu_list


def init_permission(user_obj, request):
    '''
    在登录时导入,进行权限的初始化(当前用户权限记录)
    user_obj: 用户对象,通过orm 获取用户的权限列表
    request: 请求对象,将权限存放在session中
    '''

    # 1.权限url 2.权限名称 3.权限可以作为菜单
    permissions_list = user_obj.role.filter(permissions__isnull=False).values('permissions__url',
                                                                              'permissions__menu',
                                                                              'permissions__title').distinct()
    # 当前用户的权限列表
    permissions_url_list = [i.get('permissions__url') for i in permissions_list]
    # 当前用户的菜单列表(可以作为菜单的列表) 获取url与title
    menu_url_list = [{'menu_url': i.get('permissions__url'), 'menu_title': i.get('permissions__title')} for i in
                     permissions_list if i.get('permissions__menu')]
    print(menu_url_list)
    request.session[menu_list] = menu_url_list
    request.session[permissions_key] = permissions_url_list

仅限前后端不分离使用

在用户登录后,需要将菜单信息给到用户页面中 进行展示
在html页面进行循环展示
<div class="static-menu">
    {% for menu in request.session.menu_list %}
    	<a href="/{{ menu.menu_url }}" class="active">{{ menu.menu_title }}</a>
    {% endfor %}
</div>

这种方式不行,因为需要手动的配置,可以设置一个变量使用的是django inclusion_tag

1.创建一个文件 rbac/templatetags/menu.py

import os
from django.template import Library

path = os.path.abspath(os.path.dirname(__file__))
register = Library()


# inclusion_tag(传入模板的路径)
@register.inclusion_tag(os.path.join(path, '../templates', 'static_menu.html'))
def static_menu(request):
    '''
    1菜单
    request 使用当前inclusion_tag方法传入的位置参数
    '''
    menu_list = request.session['menu_list']
    for i in menu_list:
        if i.get('menu_url') == request.path.replace('/','',1): # 如果和当前访问的url一致那么设置默认选中
            i['class'] = 'active'
    return {'menu_list':menu_list} # 当前传入的值就会被inclusion_tag(传入模板的路径)接受




2.创建一个模板  rbac/templates/static_menu.html
接受static_menu方法返回的结果变量
    <div class="static-menu">
         {% for menu in menu_list %}
              <a href="/{{ menu.menu_url }}" class={{ menu.class }} >{{ menu.menu_title }}</a>
         {% endfor %}
    </div>
    
3.使用当前方法的模板
只要其他模板使用就会将 inclusion_tag(传入模板的路径) 将路径的模板进行渲染到使用者的页面中
{% load menu %} # 找到当前的py文件
# 在需要的位置进行引用当前文件下的inclusion_tag方法同时传入一个值request
{% static_menu  request %} 


1.使用inclusion_tag(模板路径)装饰一个函数
2.装饰的函数的返回值会给到模板路径中得模板作为参数使用
3.使用者需要在使用的模板进行 load 当前 inclusion_tag 使用的函数文件名
4.在需要使用的位置将函数名进行 {% 函数名 参数1 参数2%}

二级菜单设计思路/代码

1.1级菜单没有必要进行跳转,只是为了展开二级菜单(可以写死)
2.2级菜单需要进行跳转,需要url,需要进行跳转

菜单的数据结构:
[
	{
	title:'1级菜单名称',
	children:[
		{title:'2级菜单1'},
		{title:'2级菜单2'},
		]
	}
]
可以进行双层循环展示

思路
1.表结构修改
2.session中存储的菜单信息结构进行变化
3.修改权限初始化的菜单内容部分的信息
4.需要修改页面显示效果

表结构修改

from django.db import models

__all__ = ['Permissions', 'Role', 'User', 'Menu']


class Menu(models.Model):
    '''存放1级菜单'''

    class Mate:
        db_table = 'Menu'

    id = models.AutoField(primary_key=True)
    title = models.CharField(max_length=128, verbose_name='1级菜单名称')

    def __str__(self):
        return self.title


class Permissions(models.Model):
    '''权限表'''
    class Mate:
        db_table = 'Permissions'

    id = models.AutoField(primary_key=True)
    title = models.CharField(max_length=128, verbose_name='标题')
    url = models.CharField(max_length=255, verbose_name='正则模式下的url')
    # 创建归属关系,如果menu存在值 那么就是Menu表所属的2级菜单 null那么就不是菜单
    menu = models.ForeignKey(blank=True, null=True, verbose_name='所属菜单', to='Menu', on_delete=models.CASCADE)

    def __str__(self):
        return self.title


class Role(models.Model):
    '''角色表'''
    class Mate:
        db_table = 'Role'

    id = models.AutoField(primary_key=True)
    role_name = models.CharField(max_length=255, verbose_name='角色名称')
    permissions = models.ManyToManyField(to='Permissions', db_table='Role_Permissions', verbose_name='对多对权限角色报绑定字段',
                                         blank=True)

    def __str__(self):
        return self.role_name


class User(models.Model):
    '''用户表'''
    class Mate:
        db_table = 'User'

    id = models.AutoField(primary_key=True)
    user_name = models.CharField(max_length=128, verbose_name='账户')
    password = models.CharField(max_length=128, verbose_name='密码')
    user_email = models.CharField(max_length=128, verbose_name='邮箱')
    role = models.ManyToManyField(to='Role', db_table='User_Role', verbose_name='多对多用户与角色绑定字段')

    def __str__(self):
        return self.user_name

用户登录权限初始化函数

from rbac.config import permissions_key, menu_session_name


def init_permission(user_obj, request):
    '''
    在登录时导入,进行权限的初始化(当前用户权限记录)
    user_obj: 用户对象,通过orm 获取用户的权限列表
    request: 请求对象,将权限存放在session中
    '''

    # 权限url 权限名称 权限可以作为菜单
    permissions_list = user_obj.role.filter(permissions__isnull=False).values(
        'permissions__url',  # 用户关联角色关联权限(全部权限url)
        # 用户关联角色关联权限(全部权限url名称)
        'permissions__title',
        # 用户关联角色关联权限(全部权限url 1级菜单id)
        'permissions__menu__id',
        # 用户关联角色关联权限(全部权限url 1级菜单的名称)
        'permissions__menu__title'
    ).distinct()

    # 1.当前用户的权限列表
    permissions_url_list = [i.get('permissions__url') for i in permissions_list]
    # 2.获取当前用户的菜单信息 ( 1级菜单 与 二级菜单 ) 变量构建
    menu_dict = {}
    for item in permissions_list:
        # 1.children : 二级菜单全部参数
        children_menu = {'permissions_url': item.get('permissions__url'),
                         "permissions_title": item.get('permissions__title')}

        # 2.当前判断是为了构造结构 将1菜单下的全部二级菜单归属到children 1级菜单的列表中
        # 3.1级菜单的id 与 title
        one_menu_id = item.get('permissions__menu__id')
        # 4.菜单结构生成与条件过滤
        if not one_menu_id:  # 如果没有menu_id 直接跳过(不能成为2级菜单)
            continue
        if one_menu_id in menu_dict:  # 如果当前的menu_id 存在构建的结构中 就将二级菜单进行append添加children中
            menu_dict.get(one_menu_id).get('children').append(children_menu)
        else:  # 如果当前的menu_id 不在 menu_dict 结构中就进行构建
            menu_dict[one_menu_id] = {
                "permissions__menu__title": item.get('permissions__menu__title'),
                'children': [children_menu, ]
            }
    request.session[menu_session_name] = menu_dict
    request.session[permissions_key] = permissions_url_list
    
    
menu_dict变量构建:
{
    1: {
        'permissions__menu__title': '客户管理', 
        'children': [
            {'permissions_url': 'customer/list/', 'permissions_title': '客户列表'}, 
            {'permissions_url': 'payment/list/', 'permissions_title': '付款列表'}
        ]
    },
    2: {
        'permissions__menu__title': '账单管理', 
        'children': [
            {'permissions_url': 'customer/list/', 'permissions_title': '客户列表'}, 
            {'permissions_url': 'payment/list/', 'permissions_title': '付款列表'}
        ]
    }
    ....
    ....
    ....
}
    

显示2级菜单(不分离有用)

@register.inclusion_tag(os.path.join(path, '../templates', 'multi_menu.html'))
def menu_menu(request):
    '''
    2菜单模板
    '''
    menu_list = request.session[menu_session_name]

    for item in menu_list.values():
        for children in item.get('children'):  # 如果当前访问的url是二级菜单的url,给它默认样式,同时让1级菜单展开
            if children.get('permissions_url') == request.path.replace('/', '', 1):
                children['class'] = 'active'
                item['body_hidden'] = ''
            else: # 如果访问的不是就进行闭合
                item['body_hidden'] = 'hidden'
    return {'menu_list': menu_list}


<div class="multi-menu">
    {% for menu in menu_list.values %}
        <div class="item">
            <div class="title">
                {{ menu.permissions__menu__title }}
            </div>
            <div class="body {{ menu.body_hidden }}">
                {% for children in menu.children %}
                    <a href="/{{ children.permissions_url }}"
                       class="{{ children.class }}">{{ children.permissions_title }}</a>
                {% endfor %}
            </div>
        </div>
    {% endfor %}
</div>

不能作为二级菜单的权限与二级菜单做归属关系(不分离有用)

还有好多方式能解决当前的问题

当点击非2级菜单是,归属的二级菜单直接进行默认选中效果


1.在登录是重置权初始化
权限的结构要变为:
permissions_key = [
	{id:1,url:'xxxx/xx/',pid:null}, # 可以作为菜单的权限
	{id:2,url:'xxxx/xx/',pid:1}, # 不可以作为菜单的权限 归属于pid 1 
    {id:3,url:'xxxx/xx/',pid:1}, # 不可以作为菜单的权限 归属于pid 1
	]

菜单的结构变为:
menu_session_name = {
    1: {
        'permissions__menu__title': '客户管理', 
        'children': [
            {id:1,'permissions_url': 'customer/list/', 'permissions_title': '客户列表',pid:null}, 
            {id:2,'permissions_url': 'payment/list/', 'permissions_title': '付款列表',pid:null}
        ]
    },
}

2.当用户登录后,再次访问通过中间件时(跳转到首页时的操作)
for i in permissions_key:
    # 设置一个参数存储
	当前访问的url归属于那个二级菜单的id = item['pid'] or item['id']

    
3.在菜单显示的inclusion_tag的部分,修改判断的条件,变为根据id判断
for item in menu_list.values():
        for children in item.get('children'):  # 如果当前访问的url是二级菜单的url,给它默认样式,同时让1级菜单展开
            if children.get('id') == 当前访问的url归属于那个二级菜单的id:
                children['class'] = 'active'
                item['body_hidden'] = ''
            else: # 如果访问的不是就进行闭合
                item['body_hidden'] = 'hidden'
    return {'menu_list': menu_list}

表结构

from django.db import models

__all__ = ['Permissions', 'Role', 'User', 'Menu']


class Menu(models.Model):
    '''存放1级菜单'''

    class Mate:
        db_table = 'Menu'

    id = models.AutoField(primary_key=True)
    title = models.CharField(max_length=128, verbose_name='1级菜单名称')

    def __str__(self):
        return self.title


class Permissions(models.Model):
    class Mate:
        db_table = 'Permissions'

    id = models.AutoField(primary_key=True)
    title = models.CharField(max_length=128, verbose_name='标题')
    url = models.CharField(max_length=255, verbose_name='正则模式下的url')
    # 创建归属关系,如果menu存在值 那么就是Menu表所属的2级菜单 null那么就不是菜单
    menu = models.ForeignKey(blank=True, null=True, verbose_name='所属菜单', to='Menu', on_delete=models.CASCADE)
    # 创建自关联,将不能成为2级菜单的权限关联到,可以成为2级菜单的归属
    pid = models.ForeignKey(to='Permissions', verbose_name='不能成为2级菜单的权限关联到可以成为2级菜单下面 归属关系', related_name='parents',on_delete=models.SET_NULL, null=True, blank=True)

    def __str__(self):
        return self.title


class Role(models.Model):
    class Mate:
        db_table = 'Role'

    id = models.AutoField(primary_key=True)
    role_name = models.CharField(max_length=255, verbose_name='角色名称')
    permissions = models.ManyToManyField(to='Permissions', db_table='Role_Permissions', verbose_name='对多对权限角色报绑定字段',
                                         blank=True)

    def __str__(self):
        return self.role_name


class User(models.Model):
    class Mate:
        db_table = 'User'

    id = models.AutoField(primary_key=True)
    user_name = models.CharField(max_length=128, verbose_name='账户')
    password = models.CharField(max_length=128, verbose_name='密码')
    user_email = models.CharField(max_length=128, verbose_name='邮箱')
    role = models.ManyToManyField(to='Role', db_table='User_Role', verbose_name='多对多用户与角色绑定字段')

    def __str__(self):
        return self.user_name

用户登录权限初始化部分

from rbac.config import permissions_key, menu_session_name


def init_permission(user_obj, request):
    '''
    在登录时导入函数,进行权限的初始化(当前用户权限记录)
    user_obj: 用户对象,通过orm 获取用户的权限列表
    request: 请求对象,将权限存放在session中
    '''

    # 权限url 权限名称 权限可以作为菜单
    permissions_list = user_obj.role.filter(permissions__isnull=False).values(
        # 权限id
        "permissions__id",
        # 用户关联角色关联权限(全部权限url)
        'permissions__url',
        # 用户关联角色关联权限(全部权限url名称)
        'permissions__title',
        # 用户关联角色关联权限(全部权限url 1级菜单id)
        'permissions__menu__id',
        # 用户关联角色关联权限(全部权限url 1级菜单的名称)
        'permissions__menu__title',
        # 获取pid,非菜单权限的的归属菜单的id(自关联)
        "permissions__pid__id"
    ).distinct()

    # 1.当前用户的权限列表
    permissions_url_list = []
    # 2.获取当前用户的菜单信息 ( 1级菜单 与 二级菜单 ) 结构变量
    menu_dict = {}

    for item in permissions_list:
        1.权限的全部有用的参数组成一个字典
        children_menu = {
            "id": item.get('permissions__id'), 
            'permissions_url': item.get('permissions__url'),
             "permissions_title": item.get('permissions__title'), 
            "pid":item.get('permissions__pid__id')
        }
		2. 将权限添加到权限列表中
        permissions_url_list.append(children_menu) 

        3.构建1级菜单与2级菜单的数据结构
        one_menu_id = item.get('permissions__menu__id')
    	'''
    	-- 具体逻辑
    	3.1.如果没有menu_id 直接跳过(不能成为2级菜单)
    	3.2.如果当前的menu_id 存在构建的结构中 就将二级菜单进行append添加children中
    	3.3.如果当前的menu_id 不在 menu_dict 结构中就进行构建
    	'''   
        if not one_menu_id:  
            continue
        if one_menu_id in menu_dict: 
            menu_dict.get(one_menu_id).get('children').append(children_menu)
        else:  
            menu_dict[one_menu_id] = {
                "permissions_menu_title": item.get('permissions__menu__title'),
                'children': [children_menu, ]
            }

    request.session[menu_session_name] = menu_dict
    request.session[permissions_key] = permissions_url_list

中间件修改为

from django.utils.deprecation import MiddlewareMixin  # 导入继承django中间件
import re
from django.http import JsonResponse

from rbac.config import permissions_key, valid_url_list  # 权限key 白名单


# 创建中间件类
class RbacMiddlewareMixin(MiddlewareMixin):
    '''权限中间件认证功能(django 中间件)'''

    def process_request(self, request):
        '''
        当用户请求时,进行触发
        1.获取当前用户请求的url地址
        2.获取当前用户在session中保存的权限列表
        3.匹配权限信息
        '''

        1.当前用户的url 获取的是/customer/list/ 进行替换为 customer/list/方便对比
        current_url = request.path_info.replace('/', '', 1)
        for i in valid_url_list:
            if re.match(i, current_url):
                return None

        permissions_url_list = request.session.get(permissions_key)
        if not permissions_url_list:
            return JsonResponse({"code": 403, 'error': '当前用户未登录,请进行登录后再访问'})
        
	    2.通过变量控制是否有权限
        flag = False  

	    3.循环从session获取权限信息
        for url in permissions_url_list:
            '''
            3.1.因为在url中存在 customer/edit/(?P<cid>\d+)/ 正则的url需要使用正则进行匹配
            例如:customer/edit/1 正则匹配 customer/edit/(?P<cid>\d+)/
            3.2.通过有有权限 但是使用正则匹配问题:匹配的内容没有终止起始
            例如: re.match('customer/edit/','customer/edit/123456/') 也开始可以进行匹配到的
            3.3.解决: 加上正则终止和起始符号
            3.4. 有权flag限赋值为True,如果是False那么就没有权限
            '''
            re_url = f"^{url.get('permissions_url')}$"

            if re.match(re_url, current_url):
                # 新添加 menu_id 变量 当访问非菜单权限时,默认选中归属的二级菜单选中
                request.menu_id = url.get('pid') or url.get('id')
                flag = True   
        if not flag:
            # 未通过无权限
            return JsonResponse({"code": 403, 'error': '当前用户没有权限,请让管理员添加权限!'})

二级菜单inclusion_tag修改


@register.inclusion_tag(os.path.join(path, '../templates', 'multi_menu.html'))
def menu_menu(request):
    '''
    2菜单模板
    '''
    menu_list = request.session[menu_session_name]

    for item in menu_list.values():
        for children in item.get('children'):  # 如果当前访问的url是二级菜单的url,给它默认样式,同时让1级菜单展开
            # 判断不在使用url进行判断,而使用id进行判断
            # menu_id:如果是非菜单权限 就是关联的所属二级权限绑定pid是存在值
            # menu_id: 如果是菜单权限权限 那么就是自己的id因为 pid为null
            # menu_id = url.get('pid') or url.get('id')
            if children.get('id') == request.menu_id:
                children['class'] = 'active'
                item['body_hidden'] = ''
            else: # 如果访问的不是就进行闭合
                item['body_hidden'] = 'hidden'
    print(request.path.replace('/', '', 1))
    return {'menu_list': menu_list}

multi_menu模板

<div class="multi-menu">
    {% for menu in menu_list.values %}
        <div class="item">
            <div class="title">
                {{ menu.permissions_menu_title }}
            </div>
            <div class="body {{ menu.body_hidden }}">
                {% for children in menu.children %}
                    <a href="/{{ children.permissions_url }}"
                       class="{{ children.class }}">{{ children.permissions_title }}</a>
                {% endfor %}
            </div>
        </div>
    {% endfor %}
</div>

权限之导航路由

当进行访问时,需要修改导航的路由内容
因为在二级菜单中得pid已经作何了这层关系,只需要进行添加判断语句

用户登录权限初始化

from rbac.config import permissions_key, menu_session_name


def init_permission(user_obj, request):
    '''
    在登录时导入函数,进行权限的初始化(当前用户权限记录)
    user_obj: 用户对象,通过orm 获取用户的权限列表
    request: 请求对象,将权限存放在session中
    '''

    # 权限url 权限名称 权限可以作为菜单
    permissions_list = user_obj.role.filter(permissions__isnull=False).values(
        "permissions__id",
        'permissions__url',
        'permissions__title',
        'permissions__menu__id',
        'permissions__menu__title',
        "permissions__pid__id",
        # 非菜单权限的的归属菜单的id(自关联) title 新增
        "permissions__pid__title",
        # 非菜单权限的的归属菜单的id(自关联) url 新增
        "permissions__pid__url",
    ).distinct()

    # 1.当前用户的权限列表
    permissions_url_list = []
    # 2.获取当前用户的菜单信息 ( 1级菜单 与 二级菜单 ) 结构变量
    menu_dict = {}

    for item in permissions_list:
        1.权限的全部有用的参数组成一个字典
        children_menu = {
            "id": item.get('permissions__id'),
            'permissions_url': item.get('permissions__url'),
            "permissions_title": item.get('permissions__title'),
            "pid": item.get('permissions__pid__id'),
            # 多加的两个键值对: 如果访问的是非菜单,需要将他的归属的2级菜单也同时显示 新增
            "ptitle": item.get('permissions__pid__title'),
            "purl": item.get('permissions__pid__url')
        }
		2. 将权限添加到权限列表中
        permissions_url_list.append(children_menu) 

        3.构建1级菜单与2级菜单的数据结构
        one_menu_id = item.get('permissions__menu__id')
    	'''
    	-- 具体逻辑
    	3.1.如果没有menu_id 直接跳过(不能成为2级菜单)
    	3.2.如果当前的menu_id 存在构建的结构中 就将二级菜单进行append添加children中
    	3.3.如果当前的menu_id 不在 menu_dict 结构中就进行构建
    	'''   
        if not one_menu_id:  
            continue
        if one_menu_id in menu_dict: 
            menu_dict.get(one_menu_id).get('children').append(children_menu)
        else:  
            menu_dict[one_menu_id] = {
                "permissions_menu_title": item.get('permissions__menu__title'),
                'children': [children_menu, ]
            }

    request.session[menu_session_name] = menu_dict
    request.session[permissions_key] = permissions_url_list

中间件修改为

from django.utils.deprecation import MiddlewareMixin  # 导入继承django中间件
import re
from django.http import JsonResponse

from rbac.config import permissions_key, valid_url_list  # 权限key 白名单


# 创建中间件类
class RbacMiddlewareMixin(MiddlewareMixin):
    '''权限中间件认证功能(django 中间件)'''

    def process_request(self, request):
        '''
        当用户请求时,进行触发
        1.获取当前用户请求的url地址
        2.获取当前用户在session中保存的权限列表
        3.匹配权限信息
        '''

        1.当前用户的url 获取的是/customer/list/ 进行替换为 customer/list/方便对比
        current_url = request.path_info.replace('/', '', 1)
        for i in valid_url_list:
            if re.match(i, current_url):
                return None

        permissions_url_list = request.session.get(permissions_key)
        if not permissions_url_list:
            return JsonResponse({"code": 403, 'error': '当前用户未登录,请进行登录后再访问'})
        
	    2.通过变量控制是否有权限
        flag = False  
	    # 导航显示列表 新增
        navigation_list = [
            {"title": "首页", "url": "#"}
        ]
	    3.循环从session获取权限信息
        for url in permissions_url_list:
            '''
            3.1.因为在url中存在 customer/edit/(?P<cid>\d+)/ 正则的url需要使用正则进行匹配
            例如:customer/edit/1 正则匹配 customer/edit/(?P<cid>\d+)/
            3.2.通过有有权限 但是使用正则匹配问题:匹配的内容没有终止起始
            例如: re.match('customer/edit/','customer/edit/123456/') 也开始可以进行匹配到的
            3.3.解决: 加上正则终止和起始符号
            3.4. 有权flag限赋值为True,如果是False那么就没有权限
            '''
            re_url = f"^{url.get('permissions_url')}$"

            if re.match(re_url, current_url):
                # menu_id 变量 当访问非菜单权限时,默认选中归属的二级菜单选中
                request.menu_id = url.get('pid') or url.get('id')
                flag = True 
                if url.get('pid'): # 新增
                    # 存在pid,那么就说明当前的url不属于菜单权限,需要加入两个
                    # 1.所属的2级菜单权限 url 与 title
                    # 2.自己的url 与title
                    navigation_list.extend(
                        [{"title": url.get('ptitle'), "url": url.get('purl')},
                         {"title": url.get('permissions_title'), "url": url.get('permissions_url')}]
                    )
                else:
                    # 那么就是访问的属于菜单的权限,
                    # 就显示自己的url 与 title
                    navigation_list.extend([
                        {"title": url.get('permissions_title'), "url": url.get('permissions_url')}
                    ])
                request.navigation_list = navigation_list
        if not flag:
            # 未通过无权限
            return JsonResponse({"code": 403, 'error': '当前用户没有权限,请让管理员添加权限!'})

添加模板

@register.inclusion_tag(os.path.join(path, '../templates', 'navigation.html'))
def navigation(request):
    '''
    导航栏模板
    '''
    navigation_list = request.navigation_list
    return {'navigation_list': navigation_list}
    
navigation.html
{% for navigation in navigation_list %}
    <li><a href="/{{ navigation.url }}">{{ navigation.title }}</a></li>
{% endfor %}



# 在其他模板中使用
{% load menu %}

{% navigation request %}

权限的控制力度到按钮级别

也就是在页面按钮(每一个按钮都是一个发送数据到后端,相当于一个url)都属于权限,也就是将当前这些按钮进行控制。如果没有权限的按钮就进行隐藏或者变为不可点击,如果有权限就进行显示可点击

在前后端不分离处理时模板处理,
根据模板语法进行判断当前是否存在权限
{% if user/add in 权限列表 %}
	<a href="user/add">添加用户</a>
{%endfor%}

因为这种方式太过于繁琐,可以在数据库中添加一个字段 为权限起一个别名的字段

数据库修改

from django.db import models

__all__ = ['Permissions', 'Role', 'User', 'Menu']


class Menu(models.Model):
    '''存放1级菜单'''

    class Mate:
        db_table = 'Menu'

    id = models.AutoField(primary_key=True)
    title = models.CharField(max_length=128, verbose_name='1级菜单名称')

    def __str__(self):
        return self.title


class Permissions(models.Model):
    class Mate:
        db_table = 'Permissions'

    id = models.AutoField(primary_key=True)
    title = models.CharField(max_length=128, verbose_name='标题')
    url = models.CharField(max_length=255, verbose_name='正则模式下的url')
    # 为权限设置一个别名 新增
    name = models.CharField(max_length=255,verbose_name='权限的别名,用户控制前后端不分离模板的按钮可显示否',null=False,unique=True)
    # 创建归属关系,如果menu存在值 那么就是Menu表所属的2级菜单 null那么就不是菜单
    menu = models.ForeignKey(blank=True, null=True, verbose_name='所属菜单', to='Menu', on_delete=models.CASCADE)
    # 创建自关联,将不能成为2级菜单的权限关联到,可以成为2级菜单的归属
    pid = models.ForeignKey(to='Permissions', verbose_name='不能成为2级菜单的权限关联到可以成为2级菜单下面 归属关系', related_name='parents',
                            on_delete=models.SET_NULL, null=True, blank=True)

    def __str__(self):
        return self.title


class Role(models.Model):
    class Mate:
        db_table = 'Role'

    id = models.AutoField(primary_key=True)
    role_name = models.CharField(max_length=255, verbose_name='角色名称')
    permissions = models.ManyToManyField(to='Permissions', db_table='Role_Permissions', verbose_name='对多对权限角色报绑定字段',
                                         blank=True)

    def __str__(self):
        return self.role_name


class User(models.Model):
    class Mate:
        db_table = 'User'

    id = models.AutoField(primary_key=True)
    user_name = models.CharField(max_length=128, verbose_name='账户')
    password = models.CharField(max_length=128, verbose_name='密码')
    user_email = models.CharField(max_length=128, verbose_name='邮箱')
    role = models.ManyToManyField(to='Role', db_table='User_Role', verbose_name='多对多用户与角色绑定字段')

    def __str__(self):
        return self.user_name

用户登录权限初始化修改

# 将 权限列表改为权限字典结构进行变化

from rbac.config import permissions_key, menu_session_name


def init_permission(user_obj, request):
    '''
    在登录时导入函数,进行权限的初始化(当前用户权限记录)
    user_obj: 用户对象,通过orm 获取用户的权限列表
    request: 请求对象,将权限存放在session中
    '''

    # 权限url 权限名称 权限可以作为菜单
    permissions_list = user_obj.role.filter(permissions__isnull=False).values(
        "permissions__id",
        'permissions__url',
        'permissions__title',
        'permissions__menu__id',
        'permissions__menu__title',
        "permissions__pid__id",
        "permissions__pid__title",
        "permissions__pid__url",
        # 新增将权限别名获取
        "permissions__name"
    ).distinct()

    # 1.当前用户的权限列表
    permissions_dict = {} # 从列表变为字典
    # 2.获取当前用户的菜单信息 ( 1级菜单 与 二级菜单 ) 结构变量
    menu_dict = {}

    for item in permissions_list:
        1.权限的全部有用的参数组成一个字典
        children_menu = {
            "id": item.get('permissions__id'),
            'permissions_url': item.get('permissions__url'),
            "permissions_title": item.get('permissions__title'),
            "pid": item.get('permissions__pid__id'),
            # 多加的两个键值对: 如果访问的是非菜单,需要将他的归属的2级菜单也同时显示 新增
            "ptitle": item.get('permissions__pid__title'),
            "purl": item.get('permissions__pid__url')
        }
		2. 将权限添加到权限中
        permissions_dict[item.get('permissions__name')] = children_menu 

        3.构建1级菜单与2级菜单的数据结构
        one_menu_id = item.get('permissions__menu__id')
    	'''
    	-- 具体逻辑
    	3.1.如果没有menu_id 直接跳过(不能成为2级菜单)
    	3.2.如果当前的menu_id 存在构建的结构中 就将二级菜单进行append添加children中
    	3.3.如果当前的menu_id 不在 menu_dict 结构中就进行构建
    	'''   
        if not one_menu_id:  
            continue
        if one_menu_id in menu_dict: 
            menu_dict.get(one_menu_id).get('children').append(children_menu)
        else:  
            menu_dict[one_menu_id] = {
                "permissions_menu_title": item.get('permissions__menu__title'),
                'children': [children_menu, ]
            }

    request.session[menu_session_name] = menu_dict
    request.session[permissions_key] = permissions_dict

中间件修改

# 以为在权限初始化中进行了结构修改 将列表变为字典
需要进行修改
from django.utils.deprecation import MiddlewareMixin  # 导入继承django中间件
import re
from django.http import JsonResponse

from rbac.config import permissions_key, valid_url_list  # 权限key 白名单


# 创建中间件类
class RbacMiddlewareMixin(MiddlewareMixin):
    '''权限中间件认证功能(django 中间件)'''

    def process_request(self, request):
        '''
        当用户请求时,进行触发
        1.获取当前用户请求的url地址
        2.获取当前用户在session中保存的权限列表
        3.匹配权限信息
        '''
        current_url = request.path_info.replace('/', '', 1)
        for i in valid_url_list:
            if re.match(i, current_url):
                return None

        permissions_dict = request.session.get(permissions_key) # 新增 获取的不在是列表 变为字典
        if not permissions_dict:
            return JsonResponse({"code": 403, 'error': '当前用户未登录,请进行登录后再访问'})

        flag = False
        navigation_list = [
            {"title": "首页", "url": "#"}
        ]
        for url in permissions_dict.values(): # 获取values()
            re_url = f"^{url.get('permissions_url')}$"

            if re.match(re_url, current_url):
               
                request.menu_id = url.get('pid') or url.get('id')
                if url.get('pid'):
                
                    navigation_list.extend(
                        [{"title": url.get('ptitle'), "url": url.get('purl')},
                         {"title": url.get('permissions_title'), "url": url.get('permissions_url')}]
                    )
                else:
                   
                    navigation_list.extend([
                        {"title": url.get('permissions_title'), "url": url.get('permissions_url')}
                    ])
                request.navigation_list = navigation_list
                flag = True  

        if not flag:

            return JsonResponse({"code": 403, 'error': '当前用户没有权限,请让管理员添加权限!'})

设置模板方法filter

@register.filter
def has_permissions(request, name):
    '''
    特点:
        1.最多接受两个参数
        2.可以作为模板的判断
        3.模板使用 需要先lode 在进行使用
        4.传参 第一个参数|has_permissions:"第二个参数"
    '''
    # 因为 session中权限key为字典结构,in的时候是in的字典key
    if name in request.session.get(permissions_key):
        return True
    return False

html中使用
{% load menu %}

# 传入 request 与 权限name名称 通过 has_permissions 查看是否权限存在
{% if request| has_permissions:"payment_edit" %}
    <a style="color: #333333;" href="/payment/edit/{{ row.id }}/">
    	<i class="fa fa-edit" aria-hidden="true"></i>
    </a>
{% endif %}

上述总结

1.权限控制(登录后获取权限,再次访问后通过中间件进行判断)
2.动态菜单(在中间件中将动态菜单进行生成)
3.权限的控制到按钮级别(根据权限在html页面判断当前权限是否存在,选择隐藏与显示)

权限功能分配

用户创建 删除 查询 修改
角色创建 修改 查询 删除
权限创建 修改 查询 删除

角色 分配 权限
用户 分配 角色


# 4张表的之间的关系也需要进行展示添加
1.创建url 角色[单表] 增删改查  显示绑定的权限内容
2.创建url 用户[单表] 正删改查	 显示绑定的角色内容
3.创建url 菜单[单表] 增删改查	
4.创建url 权限[单表] 增删改查 显示自关联[关联表中可以作为二级菜单] 显示绑定1级菜单


最需要注意的:
    1.这几张表的之间的关系,
    2.如果是前后端不分离的情况下,前端框架的路由与数据库中菜单表的关系,路由与后端的权限表api接口的关系进行绑定,前端框架的循环展示当前用户的菜单以及菜单下的2级菜单(前端框架的路由),点击2级菜单,就会从后端api接口中获取数据。进行保定关系设置。

前后端分离状态下的:https://www.yuque.com/zhanghaofei/blog/xrpz9p权限思路

任务分解

1.角色管理 单表操作 增删改查
	1.不分离(modlform)使用
	2.反向生成 namespace 和 name 的反向生成url 
	3.django模板查找顺序 按照注册app的顺序一个一个找
2.用户管理 单表操作 增删改查
	1.不分离(modlform)使用
	2.反向生成 namespace 和 name 的反向生成url 
	3.django模板查找顺序 按照注册app的顺序一个一个找
	
3.菜单表与权限表(菜单与权限管理) 2张表的增删改查
	1.需要展示一个1级菜单的添加删除修改编辑
	2.在添加修改中需要进行绑定权限表中url进行二级菜单
	3.同时二级菜单需要绑定非菜单的权限url

发现项目中权限django

批量的添加权限,发现项目中得权限(url)

前后端不分离:
	1.modelform 单个表单的添加操作
	2.modelformset 多个表单添加操作验证 批量操作进行使用
前后端分离:
	一个api接口将数据库中得已经记录的url与项目中得url进行对比显示在前端即可

通过框架内部的方法获取全部项目下的url进行处理与数据库中进行对比获取剩下的





from collections import OrderedDict  # 有序字典
from django.conf import settings  # 配置文件
from django.utils.module_loading import import_string  # 根据字符串获导入一个模块
from django.urls import URLResolver, URLPattern  # 路由的类型


def recursion_urls(pre_namespace, pre_url, urlpatterns, url_ordered_dict):
    """
        # url的递归操作
        pre_namespace : 路由分发的namespace
        per_url:路由分发的路由
        urlpatterns:路由关系
        url_ordered_dict:用来保存获取的所有的路由
        for item in urlpatterns:
            item 是每一个url url.name 获取url别名 item.pattern 获取url对象
            item.pattern._regex # 获取url
    """
    for item in urlpatterns:

        if isinstance(item, URLPattern):  # 非路由分发,不需要进行循环操作
            if not item.name:  # 如果没有给url起别名,让当前自己的url作为自己的别名
                name = str(item.pattern).replace('^', "").replace('$', '')

            elif pre_namespace:  # 有路由分发的别名 进行拼接
                name = "%s:%s" % (pre_namespace, item.name)
            else:  # 没有路由分发的别名就使用自己的
                name = item.name
                
            url = pre_url + str(item.pattern)
            url = url.replace('^', "").replace('$', '')  # 清除终止符与其实符
            url_ordered_dict[name] = url # 存储到有序字典中
            
        elif isinstance(item, URLResolver):  # 是路由分发需要进行接着循环操作,进行递归操作
            if pre_namespace:  # 如果传入了namespace就需要进行拼接 分发别名使用
                if item.namespace: # 如果自己有拼接 namespace
                    namespace = "%s:%s" % (pre_namespace, item.namespace)
                else: # 没有就是用传入的 namespace
                    namespace = pre_namespace
            else: # 如果没有那么使用自己
                if item.namespace:
                    namespace = item.namespace
                else: # 自己的没有默认为none
                    namespace = None
            recursion_urls(namespace, pre_url + str(item.pattern), item.url_patterns, url_ordered_dict)


def get_all_url_dict():
    '''获取全部的项目的路由(需要有name别名)'''
    url_ordered_dict = OrderedDict()
    md = import_string(settings.ROOT_URLCONF)
    recursion_urls(None, '/', md.urlpatterns, url_ordered_dict)
    return url_ordered_dict


# 视图:
def multi_permissions(request):
    '''
        获取项目中得全部的url
    '''
    a = get_all_url_dict()
    for k,v in a.items():
        print(k,v)
    return HttpResponse('ok')

总结前后端分离

1.当用户登录后获取权限(用户登录页面白名单)
	1.1. 当用户登录初始化数据库中得权限存储到session或者缓存中
2.当用户在进行访问时
	2.1 通过中间件 从缓存中获取初始化的权限信息 无权访问返回内容 与通过 返回的内容

权限需要根据项目中url而定
每一个url相当于一个接口


vue --- django 

vue 有自己的路由
django 有自己的路由(是接口数据)

数据库表结构:
# 固定不变的
1.权限表 api接口url
2.角色表 角色绑定的权限
3.用户表 用户绑定的角色
4.菜单表
5.vue 前端路由表

菜单表 # 作为在vue前端渲染的循环的 1级菜单信息
nenu_name : 菜单表的名称 

vue 前端路由表 # 二级菜单,显示的页面
url: 前端的路由信息
menu_id : 与菜单表绑定的关系

权限表 # 后端接口表
url : 后端的接口
vue 前端路由_id # 归属到前端路由下面

角色表 # 角色表
权限表_id :拥有的权限信息

用户表 #
角色表_id : 拥有那些角色

# 例如: 
页面显示
'''
用户管理: 一级菜单(数据库菜单表生成的)
	用户列表 二级菜单(前端的路由渲染的)
	权限列表 二级菜单(前端的路由渲染的)
	角色列表 二级菜单(前端的路由渲染的)
	....
'''
数据显示(在前端页面进行循环展示侧边栏菜单)
[
    {
        'title':'用户管理',"id":"1作为渲染菜单的唯一标识",
        "child":[
           {'title':'用户列表',"url":'url/list/',},
           {'title':'权限列表',"url":'url/list/',},
           {'title':'角色列表',"url":'url/list/',},
        ]
    }
]

用户登录后根据角色找到权限信息
权限找到前端页面的显示路由,根据路由找到1级菜单信息
进行渲染操作
前端页面显示要与后端api接口做归属关系