首页 > 代码库 > 用户角色权限 案例

用户角色权限 案例

#权限管理:rbac:role basic access control
# 一 根据用户获取权限, session中 中间件实现权限控制
# 二 菜单管理
#默认展开
#只显示当前用户菜单

一、权限用户表

a:SQL表结构

技术分享
from django.db import models

# Create your models here.
class User(models.Model):
    """
    用户表
    """
    username = models.CharField(verbose_name=用户名, max_length=32)
    password = models.CharField(verbose_name=密码, max_length=64)
    email = models.EmailField(verbose_name=邮箱)

    def __str__(self):
        return self.username


class Role(models.Model):
    """
    角色表
    """
    caption = models.CharField(verbose_name=角色, max_length=32)

    def __str__(self):
        return self.caption


class User2Role(models.Model):
    """
    用户角色关系表
    """
    user = models.ForeignKey(User, verbose_name=用户, related_name=roles)
    role = models.ForeignKey(Role, verbose_name=角色, related_name=users)

    def __str__(self):
        return %s-%s % (self.user.username, self.role.caption,)


class Menu(models.Model):
    """
    菜单表
    """
    caption = models.CharField(verbose_name=菜单名称, max_length=32)
    parent = models.ForeignKey(self, verbose_name=父菜单, related_name=p, null=True, blank=True)

    def __str__(self):
        prev = ""
        parent = self.parent
        while True:
            if parent:
                prev = prev + - + str(parent.caption)
                parent = parent.parent
            else:
                break
        return %s-%s % (prev, self.caption,)


class Permission(models.Model):
    """
    权限
    """
    caption = models.CharField(verbose_name=权限, max_length=32)
    url = models.CharField(verbose_name=URL正则, max_length=128)
    menu = models.ForeignKey(Menu, verbose_name=所属菜单, related_name=permissions,null=True,blank=True)

    def __str__(self):
        return "%s-%s" % (self.caption, self.url,)


class Action(models.Model):
    """
    操作:增删改查
    """
    caption = models.CharField(verbose_name=操作标题, max_length=32)
    code = models.CharField(verbose_name=方法, max_length=32)

    def __str__(self):
        return self.caption


class Permission2Action2Role(models.Model):
    """
    权限操作关系表
    """
    permission = models.ForeignKey(Permission, verbose_name=权限URL, related_name=actions)
    action = models.ForeignKey(Action, verbose_name=操作, related_name=permissions)
    role = models.ForeignKey(Role, verbose_name=角色, related_name=p2as)

    class Meta:
        unique_together = (
            (permission, action, role),
        )

    def __str__(self):
        return "%s-%s-%s" % (self.permission, self.action, self.role,)
models.py

技术分享

b.输出菜单

技术分享
urlpatterns = [
    url(r^auth-menu.html$,view2.menu),

]
url.py
技术分享
from django.contrib import admin

# Register your models here.
from app02 import models

admin.site.register(models.User)
admin.site.register(models.Role)
admin.site.register(models.User2Role)
admin.site.register(models.Menu)
admin.site.register(models.Permission)
admin.site.register(models.Action)
admin.site.register(models.Permission2Action2Role)
app02/admin.py
技术分享
def menu(request):

   """
    需要用户名或用户ID,产出:用户关联所有菜单
    :param request:
    :return:
    """

    # 所有菜单:处理成当前用关联的菜单
    all_menu_list = models.Menu.objects.all().values(id, caption, parent_id)
    """
    [
        {‘id‘:1, ‘caption‘:‘菜单1‘, parent_id:None},
        {‘id‘:2, ‘caption‘:‘菜单2‘, parent_id:None},
        {‘id‘:3, ‘caption‘:‘菜单3‘, parent_id:None},
        {‘id‘:4, ‘caption‘:‘菜单1-1‘, parent_id:1},
    ]

    {
        1:{‘id‘:1, ‘caption‘:‘菜单1‘, parent_id:None,status:False,opened:False,child:[]},
        2:{‘id‘:2, ‘caption‘:‘菜单2‘, parent_id:None,status:False,opened:False,child:[]},
        3:{‘id‘:3, ‘caption‘:‘菜单3‘, parent_id:None,status:False,opened:False,child:[]},
        5:{‘id‘:4, ‘caption‘:‘菜单1-1‘, parent_id:1,status:False,opened:False,child:[]},
    }
   """
    user = models.User.objects.filter(username=alex).first()
    role_list = models.Role.objects.filter(users__user=user)
    permission_list = models.Permission2Action2Role.objects.filter(role__in=role_list).values(permission__id,
                                                                                              permission__url,
                                                                                              permission__menu_id,
                                                                                              permission__caption).distinct()



    """

    [
        {‘permission__url‘:‘/order.html‘,‘permission__caption‘: ‘订单管理‘,‘permission__menu_id‘: 1 },
        {‘permission__url‘:‘/order.html‘,‘permission__caption‘: ‘订单管理‘,‘permission__menu_id‘: 2 },
        {‘permission__url‘:‘/order.html‘,‘permission__caption‘: ‘订单管理‘,‘permission__menu_id‘: 3 },
        {‘permission__url‘:‘/order.html‘,‘permission__caption‘: ‘订单管理‘,‘permission__menu_id‘: 4 },
    ]
    """
    ##### 将权限挂靠到菜单上 ########
    all_menu_dict = {}
    for row in all_menu_list:
        row[child] = []  # 添加孩子
        row[status] = False  # 是否显示菜单
        row[opened] = False  # 是否默认打开
        all_menu_dict[row[id]] = row




    for per in permission_list:
        if not per[permission__menu_id]:
            continue

        item = {
            id: per[permission__id],
            caption: per[permission__caption],
            parent_id: per[permission__menu_id],
            url: per[permission__url],
            status: True,
            opened: False
        }

        # print(item["url"])
        if re.match(per[permission__url],request.path_info):
        # if re.match(per[‘permission__url‘], "/orders.html"):
            item[opened] = True
        pid = item[parent_id]
        all_menu_dict[pid][child].append(item)

        # 将当前权限前辈status=True
        temp = pid  # 1.父亲ID
        while not all_menu_dict[temp][status]:
            all_menu_dict[temp][status] = True
            temp = all_menu_dict[temp][parent_id]
            if not temp:
                break

        # 将当前权限前辈opened=True
        if item[opened]:
            temp1 = pid  # 1.父亲ID
            while not all_menu_dict[temp1][opened]:
                all_menu_dict[temp1][opened] = True
                temp1 = all_menu_dict[temp1][parent_id]
                if not temp1:
                    break
    # ############ 处理菜单和菜单之间的等级关系 ############
    """
    all_menu_dict = {
        1:{‘id‘:1, ‘caption‘:‘菜单1‘, parent_id:None,status:False,opened:False,child:[{‘permission__url‘:‘/order.html‘,‘permission__caption‘: ‘订单管理‘,‘permission__menu_id‘: 1 },]},
        2:{‘id‘:2, ‘caption‘:‘菜单2‘, parent_id:None,status:False,opened:False,child:[]},
        3:{‘id‘:3, ‘caption‘:‘菜单3‘, parent_id:None,status:False,opened:False,child:[]},
        5:{‘id‘:4, ‘caption‘:‘菜单1-1‘, parent_id:1,status:False,opened:False,child:[]},
    }


    all_menu_list= [
        {‘id‘:1, ‘caption‘:‘菜单1‘, parent_id:None,status:False,opened:False,child:[{‘permission__url‘:‘/order.html‘,‘permission__caption‘: ‘订单管理‘,‘permission__menu_id‘: 1 }, {‘id‘:4, ‘caption‘:‘菜单1-1‘, parent_id:1,status:False,opened:False,child:[]},]},
        {‘id‘:2, ‘caption‘:‘菜单2‘, parent_id:None,status:False,opened:False,child:[]},
        {‘id‘:3, ‘caption‘:‘菜单3‘, parent_id:None,status:False,opened:False,child:[]},

    ]
    """

    result = []
    for row in all_menu_list:
        pid = row[parent_id]
        if pid:
            all_menu_dict[pid][child].append(row)
        else:
            result.append(row)


    ##################### 结构化处理结果 #####################
    # print(result)
    # for row in result:
    #     # print(row[‘caption‘], row[‘status‘], row[‘opened‘], )
    #     print(row)

    ##################### 通过结构化处理结果,生成菜单开始 #####################

    def menu_tree(menu_list):
        tpl1 = """
        <div class=‘menu-item‘>
            <div class=‘menu-header‘>{0}</div>
            <div class=‘menu-body {2}‘>{1}</div>
        </div>
        """
        tpl2 = """
        <a href=http://www.mamicode.com/‘{0}‘ class=‘{1}‘>{2}>"""

        menu_str = ""
        for menu in menu_list:
            if not menu[status]:
                continue
            # menu: 菜单,权限(url)
            if menu.get(url):
                # 权限
                menu_str += tpl2.format(menu[url],active if menu[opened] else "",menu[caption])
            else:
                # 菜单
                if menu[child]:
                    child_html = menu_tree(menu[child])
                else:
                    child_html = ""
                menu_str += tpl1.format(menu[caption], child_html,"" if menu[opened] else hide)

        return menu_str

    menu_html = menu_tree(result)




    return render(request, "menu_html.html", {"menu_html":menu_html})
app02/views.py
技术分享
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
    <style>
        .menu-body{
            margin-left: 20px;
        }
        .menu-body a{
            display: block;
        }
        .menu-body a.active{
            color: red;
        }
        .hide{
            display: none;
        }
    </style>
</head>
<body>

    {{ menu_html|safe }}

    <script src=http://www.mamicode.com/"/static/jquery-3.2.1.js"></script>
    <script>
        $(function () {
            $(".menu-header").click(function () {
                $(this).next().removeClass("hide").parent().siblings().find(".menu-body").addClass("hide")
            })
        })
    </script>
</body>
</html>
menu_html

c.result 结果递归 debug调试

result =
    [
    {‘opened‘: True, ‘parent_id‘: None, ‘caption‘: ‘菜单一‘, ‘id‘: 1, ‘status‘: True,
        ‘child‘: [
                {‘opened‘: False, ‘parent_id‘: 1, ‘caption‘: ‘帅哥管理‘, ‘id‘: 5, ‘status‘: True, ‘url‘: ‘/shuaige.html‘},
                {‘opened‘: True, ‘parent_id‘: 1, ‘caption‘: ‘菜单一  一‘, ‘id‘: 4, ‘status‘: True,
                    ‘child‘: [
                        {‘opened‘: True, ‘parent_id‘: 4, ‘caption‘: ‘用户管理‘, ‘id‘: 1, ‘status‘: True, ‘url‘: ‘/users.html‘},
                        {‘opened‘: False, ‘parent_id‘: 4, ‘caption‘: ‘订单管理‘, ‘id‘: 2, ‘status‘: True, ‘url‘: ‘/orders.html‘}
                    ]
                },
                {‘opened‘: False, ‘parent_id‘: 1, ‘caption‘: ‘菜单一 二‘, ‘id‘: 5, ‘status‘: False, ‘child‘: []},
                {‘opened‘: False, ‘parent_id‘: 1, ‘caption‘: ‘菜单一  三‘, ‘id‘: 6, ‘status‘: False, ‘child‘: []}
            ]
    },
    {‘opened‘: False, ‘parent_id‘: None, ‘caption‘: ‘菜单二‘, ‘id‘: 2, ‘status‘: False, ‘child‘: []},
    {‘opened‘: False, ‘parent_id‘: None, ‘caption‘: ‘菜单三‘, ‘id‘: 3, ‘status‘: False, ‘child‘: []}
    ] 
result = [{opened: True, parent_id: None, caption: 菜单一, id: 1, status: True, child: [{opened: False, parent_id: 1, caption: 帅哥管理, id: 5, status: True, url: /shuaige.html}, {opened: True, parent_id: 1, caption: 菜单一  一, id: 4, status: True, child: [{opened: True, parent_id: 4, caption: 用户管理, id: 1, status: True, url: /users.html}, {opened: False, parent_id: 4, caption: 订单管理, id: 2, status: True, url: /orders.html}]}, {opened: False, parent_id: 1, caption: 菜单一 二, id: 5, status: False, child: []}, {opened: False, parent_id: 1, caption: 菜单一  三, id: 6, status: False, child: []}]}, {opened: False, parent_id: None, caption: 菜单二, id: 2, status: False, child: []}, {opened: False, parent_id: None, caption: 菜单三, id: 3, status: False, child: []}]


def menu_tree(menu_list):
    tpl1 = """
    <div class=‘menu-item‘>
        <div class=‘menu-header‘>{0}</div>
        <div class=‘menu-body {2}‘>{1}</div>
    </div>
    """
    tpl2 = """
    <a href=http://www.mamicode.com/‘{0}‘ class=‘{1}‘>{2}>"""

    menu_str = ""
    for menu in menu_list:
        if not menu[status]:
            continue
        # menu: 菜单,权限(url)
        if menu.get(url):
            # 权限
            menu_str += tpl2.format(menu[url], active if menu[opened] else "", menu[caption])
            print("***", menu_str)
        else:
            # 菜单
            if menu[child]:
                child_html = menu_tree(menu[child])
                print("----", child_html)
            else:
                child_html = ""
                print("111")
            menu_str += tpl1.format(menu[caption], child_html, "" if menu[opened] else hide)
            print("AAAAA", menu_str)
    print(123)
    return menu_str


menu_html = menu_tree(result)
print("xx", menu_html)

二、组件

生成公共app
       - 权限限制
       - 生成菜单
python3 manage.py startapp rbac  

配置文件:

技术分享
复制代码
#白名单url, 不验证

VALID_URL = [
    /app01/.*,
    /app02/.*
    /login.html
    /logout.html
]
config.py

rbac models:

技术分享
from django.db import models


class User(models.Model):
    """
    用户表
    """
    username = models.CharField(verbose_name=用户名, max_length=32)
    password = models.CharField(verbose_name=密码, max_length=64)
    email = models.EmailField(verbose_name=邮箱)

    def __str__(self):
        return self.username


class Role(models.Model):
    """
    角色表
    """
    caption = models.CharField(verbose_name=角色, max_length=32)

    def __str__(self):
        return self.caption


class User2Role(models.Model):
    """
    用户角色关系表
    """
    user = models.ForeignKey(User, verbose_name=用户, related_name=roles)
    role = models.ForeignKey(Role, verbose_name=角色, related_name=users)

    def __str__(self):
        return %s-%s % (self.user.username, self.role.caption,)


class Menu(models.Model):
    """
    菜单表
    """
    caption = models.CharField(verbose_name=菜单名称, max_length=32)
    parent = models.ForeignKey(self, verbose_name=父菜单, related_name=p, null=True, blank=True)

    def __str__(self):
        prev = ""
        parent = self.parent
        while True:
            if parent:
                prev = prev + - + str(parent.caption)
                parent = parent.parent
            else:
                break
        return %s-%s % (prev, self.caption,)


class Permission(models.Model):
    """
    权限
    """
    caption = models.CharField(verbose_name=权限, max_length=32)
    url = models.CharField(verbose_name=URL正则, max_length=128)
    menu = models.ForeignKey(Menu, verbose_name=所属菜单, related_name=permissions,null=True,blank=True)

    def __str__(self):
        return "%s-%s" % (self.caption, self.url,)


class Action(models.Model):
    """
    操作:增删改查
    """
    caption = models.CharField(verbose_name=操作标题, max_length=32)
    code = models.CharField(verbose_name=方法, max_length=32)

    def __str__(self):
        return self.caption


class Permission2Action2Role(models.Model):
    """
    权限操作关系表
    """
    permission = models.ForeignKey(Permission, verbose_name=权限URL, related_name=actions)
    action = models.ForeignKey(Action, verbose_name=操作, related_name=permissions)
    role = models.ForeignKey(Role, verbose_name=角色, related_name=p2as)

    class Meta:
        unique_together = (
            (permission, action, role),
        )

    def __str__(self):
        return "%s-%s-%s" % (self.permission, self.action, self.role,)
models.py

中间件认证  

技术分享
#验证中间件


from django.utils.deprecation import MiddlewareMixin
from django.shortcuts import HttpResponse
from rbac import config
import re


class RbacMiddleware(MiddlewareMixin):

    def process_request(self,request,*args,**kwargs):
        for pattern in config.VALID_URL:
            if re.match(pattern,request.path_info):
                return None

        action = request.GET.get(md) # GET
        user_permission_dict = request.session.get(user_permission_dict)
        if not user_permission_dict:
            return HttpResponse(无权限)

        # action_list = user_permission_dict.get(request.path_info)
        flag = False
        for k,v in user_permission_dict.items():
            if re.match(k,request.path_info):
                if action in v:
                    flag = True
                    break
        if not flag:
            return HttpResponse(无权限)
/middleware/md.py

后端代码

技术分享
import re
from rbac import models
from django.utils.safestring import mark_safe

def permission_session(user_id,request):
    """

    :param user_id:  rbac中的user表中一条数据id
    :param request:
    :return:
    """
    # obj = models.User.objects.filter(username=‘杨明‘).first()
    #
    # # x = models.User2Role.objects.filter(user_id=obj.id)
    # # [User2Role,User2Role,User2Role]
    #
    # role_list = models.Role.objects.filter(users__user_id=obj.id)
    # # [Role,]
    # from django.db.models import Count
    # # permission_list = models.Permission2Action2Role.objects.filter(role__in=role_list).values(‘permission__url‘,‘action__code‘).annotate(c=Count(‘id‘))
    # permission_list = models.Permission2Action2Role.objects.filter(role__in=role_list).values(‘permission__url‘,‘action__code‘).distinct()
    """
    [
        {permission_url: ‘/index.html‘, action_code:‘GET‘},
        {permission_url: ‘/index.html‘, action_code:‘POST‘},
        {permission_url: ‘/index.html‘, action_code:‘DEL‘},
        {permission_url: ‘/index.html‘, action_code:‘Edit‘},
        {permission_url: ‘/order.html‘, action_code:‘GET‘},
        {permission_url: ‘/order.html‘, action_code:‘POST‘},
        {permission_url: ‘/order.html‘, action_code:‘DEL‘},
        {permission_url: ‘/order.html‘, action_code:‘Edit‘},
    ]
    放在Session中
    /index.html?md=GET

    {
        ‘/index.html‘: [GET,POST,DEL,Edit],
        ‘/order.html‘: [GET,POST,DEL,Edit],
    }

    """

    user_permission_dict = {
        /ah-index.html: ["GET","POST","DEL","Edit"],
        /order.html:  ["GET","POST","DEL","Edit"],
        /index-(\d+).html:  ["GET","POST","DEL","Edit"],
    }

    request.session[user_permission_dict] = user_permission_dict


def menu(user_id,current_url):
    """
    根据用户ID,当前URL:获取用户所有菜单以及权限,是否显示,是否打开
    :param user_id:
    :param current_url:
    :return:
    """
    # 所有菜单:处理成当前用关联的菜单
    all_menu_list = models.Menu.objects.all().values(id,caption,parent_id)
    user = models.User.objects.filter(id=user_id).first()
    role_list = models.Role.objects.filter(users__user=user)
    permission_list = models.Permission2Action2Role.objects.filter(role__in=role_list).values(permission__id,permission__url,permission__menu_id,permission__caption).distinct()
    ##### 将权限挂靠到菜单上 ########
    all_menu_dict = {}
    for row in all_menu_list:
        row[child] = []      # 添加孩子
        row[status] = False # 是否显示菜单
        row[opened] = False # 是否默认打开
        all_menu_dict[row[id]] = row

    for per in permission_list:
        if not per[permission__menu_id]:
            continue

        item = {
            id:per[permission__id],
            caption:per[permission__caption],
            parent_id:per[permission__menu_id],
            url: per[permission__url],
            status: True,
            opened: False
        }
        if re.match(per[permission__url],current_url):
            item[opened] = True
        pid = item[parent_id]
        all_menu_dict[pid][child].append(item)

        # 将当前权限前辈status=True
        temp = pid # 1.父亲ID
        while not all_menu_dict[temp][status]:
            all_menu_dict[temp][status] = True
            temp = all_menu_dict[temp][parent_id]
            if not temp:
                break

        # 将当前权限前辈opened=True
        if item[opened]:
            temp1 = pid # 1.父亲ID
            while not all_menu_dict[temp1][opened]:
                all_menu_dict[temp1][opened] = True
                temp1 = all_menu_dict[temp1][parent_id]
                if not temp1:
                    break
    # ############ 处理菜单和菜单之间的等级关系 ############
    result = []
    for row in all_menu_list:
        pid = row[parent_id]
        if pid:
            all_menu_dict[pid][child].append(row)
        else:
            result.append(row)


    ##################### 结构化处理结果 #####################
    for row in result:
        print(row[caption],row[status],row[opened],row)


    def menu_tree(menu_list):
        tpl1 = """
        <div class=‘menu-item‘>
            <div class=‘menu-header‘>{0}</div>
            <div class=‘menu-body {2}‘>{1}</div>
        </div>
        """
        tpl2 = """
        <a href=http://www.mamicode.com/‘{0}‘ class=‘{1}‘>{2}>"""

        menu_str = ""
        for menu in menu_list:
            if not menu[status]:
                continue
            # menu: 菜单,权限(url)
            if menu.get(url):
                # 权限
                menu_str += tpl2.format(menu[url],active if menu[opened] else "",menu[caption])
            else:
                # 菜单
                if menu[child]:
                    child_html = menu_tree(menu[child])
                else:
                    child_html = ""
                menu_str += tpl1.format(menu[caption], child_html,"" if menu[opened] else hide)

        return menu_str
    menu_html = menu_tree(result)
    return menu_html


# simple_tag
def css():
    v = """
        <style>
        .hide{
            display: none;
        }
        .menu-body{
            margin-left: 20px;
        }
        .menu-body a{
            display: block;
        }
        .menu-body a.active{
            color: red;
        }
    </style>
        """
    return v

# simple_tag
def js():
    v = """
        <script>
        $(function(){

            $(‘.menu-header‘).click(function(){
                $(this).next().removeClass(‘hide‘).parent().siblings().find(‘.menu-body‘).addClass(‘hide‘);

            })

        })
    </script>
    """
    return v
service.py

a:封装以后调用

加入中间件

技术分享
#自定义的中间件加入到setting

MIDDLEWARE = [
    django.middleware.security.SecurityMiddleware,
    django.contrib.sessions.middleware.SessionMiddleware,
    django.middleware.common.CommonMiddleware,
    django.middleware.csrf.CsrfViewMiddleware,
    django.contrib.auth.middleware.AuthenticationMiddleware,
    django.contrib.messages.middleware.MessageMiddleware,
    django.middleware.clickjacking.XFrameOptionsMiddleware,
    rbac.middleware.md.RbacMiddleware,    
]
setting.py
#后端
from rbac import service
 
1. 用户登录后,拿到用户的ID,调用permission_session()函数(函数代码还没写)
   函数获取用户角色的权限,格式如:
   “”“
    user_permission_dict = {
        ‘/ah-index.html‘: ["GET","POST","DEL","Edit"],
        ‘/order.html‘:  ["GET","POST","DEL","Edit"],
        ‘/index-(\d+).html‘:  ["GET","POST","DEL","Edit"],
    }
   ”“”
 
    def login():
      permission_session(用户ID,request)
      return .....
 
2.setting中加入中间件,如上
  
 
3.#获取菜单
    current_url= request.pathinfo
    menu_list = service.menu(用户ID,current_url)
 
4.尽量用simple_tag
    css = servicr.css()
    js = servicr.js()
 
5.前端
 
    {{ css|safe }}
    {{ menu_list|safe }}
    {{ js|safe }}  

  

 

用户角色权限 案例