CRM【第一篇】: 權限組件之權限控制

1. 問:爲何程序須要權限控制?html

   答:生活中的權限限制,① 看災難片電影《2012》中富人和權貴有權登上諾亞方舟,窮苦老百姓只有等着災難的來臨;② 屌絲們,有沒有想過爲何那些長得漂亮身材好的姑娘在你身邊不存在呢?由於有錢人和漂亮姑娘都是珍貴稀有的,稀有的人在一塊兒玩耍和解鎖各類姿式。而你,無權擁有他們,只能本身玩本身了。
程序開發時的權限控制,對於不一樣用戶使用系統時候就應該有不一樣的功能,如:python

  • 普通員工
  • 部門主管
  • 總監
  • 總裁

因此,只要有不一樣角色的人員來使用系統,那麼就確定須要權限系統。程序員

2. 問:爲何要開發權限組件?web

   答:假設你今年25歲,從今天開始寫代碼到80歲,每一年寫5個項目,那麼你的一輩子就會寫275個項目,保守估計其中應該有150+個都須要用到權限控制,爲了之後再也不重複的寫代碼,因此就開發一個權限組件以便以後55年的歲月中使用。 親,不要太較真哦,你以爲程序員能到80歲麼,哈哈哈哈哈哈哈 
偷偷告訴你:老程序員開發速度快,其中一個緣由是經驗豐富,另一個就是他本身保留了不少組件,新系統開發時,只需把組件拼湊起來基本就能夠完成。sql

3. 問:web開發中權限指的是什麼?數據庫

   答:web程序是經過 url 的切換來查看不一樣的頁面(功能),因此權限指的其實就是URL,對url控制就是對權限的控制。django

結論:一我的有多少個權限就取決於他有多少個URL的訪問權限。bash

權限表結構設計:初版

問答環節中已得出權限就是URL的結論,那麼就能夠開始設計表結構了。session

  • 一個用戶能夠有多個權限。
  • 一個權限能夠分配給多個用戶。

你設計的表結構大概會是這個樣子:app

如今,此時此刻是否是以爲本身設計出的表結構棒棒噠!!!

But,不管是是否認可,你仍是too young too native,由於老漢腚眼一看就有問題....

問題:假設 「老男孩」和「Alex」 這倆貨都是老闆,老闆的權限必定是很是多。那麼試想,若是給這倆貨分配權限時須要在【用戶權限關係表中】添加好多條數據。假設再次須要對老闆的權限進行修改時,又須要在【用戶權限關係表】中找到這倆人全部的數據進行更新,太他媽煩了吧!!! 相似的,若是給其餘相同角色的人來分配權限時,必然會很是繁瑣。

權限表結構設計:第二版

聰明機智的必定在上述的表述中看出了寫門道,若是對用戶進行角色的劃分,而後對角色進行權限的分配,這不就迎刃而解了麼。

  • 一我的能夠有多個角色。
  • 一個角色能夠有多我的。
  • 一個角色能夠有多個權限。
  • 一個權限能夠分配給多個角色。

表結構設計:

 此次調整以後,由原來的【基於用戶的權限控制】轉換成【基於角色的權限控制】,之後再進行分配權限時只須要給指定角色分配一次權限,給衆多用戶再次分配指定角色便可。

from django.db import models


class Permission(models.Model):
    """
    權限表
    """
    title = models.CharField(verbose_name='標題', max_length=32)
    url = models.CharField(verbose_name='含正則的URL', max_length=128)

    def __str__(self):
        return self.title


class Role(models.Model):
    """
    角色
    """
    title = models.CharField(verbose_name='角色名稱', max_length=32)
    permissions = models.ManyToManyField(verbose_name='擁有的全部權限', to='Permission', blank=True)

    def __str__(self):
        return self.title


class UserInfo(models.Model):
    """
    用戶表
    """
    name = models.CharField(verbose_name='用戶名', max_length=32)
    password = models.CharField(verbose_name='密碼', max_length=64)
    email = models.CharField(verbose_name='郵箱', max_length=32)
    roles = models.ManyToManyField(verbose_name='擁有的全部角色', to='Role', blank=True)

    def __str__(self):
        return self.name

models.py 示例
models.py

小夥子,告訴你一個事實,不經意間,你竟然設計出了一個經典的權限訪問控制系統:rbac(Role-Based Access Control)基於角色的權限訪問控制。你這麼優秀,爲何不來老男孩IT教育?路飛學城也行呀! 哈哈哈哈。

注意:如今的設計還不是最終版,但以後的設計都是在此版本基礎上擴增的,爲了讓你們可以更好的理解,咱們暫且再此基礎上繼續開發,直到遇到沒法知足的狀況,再進行整改。

源碼示例猛擊下載

客戶管理之權限控制

學習知識最好的方式就是試錯,坑踩多了那麼學到的知識天然而然就多了,因此接下里下來咱們用《客戶管理》系統爲示例,提出功能並實現,而且隨着功能愈來愈多,一點點來找出問題,並解決問題。

目錄結構:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
luffy_permission/
├── db.sqlite3
├── luffy_permission
│   ├── __init__.py
│   ├── settings.py
│   ├── urls.py
│   └── wsgi.py
├── manage.py
├── rbac             # 權限組件,便於之後應用到其餘系統
│   ├── __init__.py
│   ├── admin.py
│   ├── apps.py
│   ├── models.py
│   ├── tests.py
│   └── views.py
├── templates
└── web             # 客戶管理業務
     ├── __init__.py
     ├── admin.py
     ├── apps.py
     ├── models.py
     ├── tests.py
     └── views.py
from django.db import models


class Permission(models.Model):
    """
    權限表
    """
    title = models.CharField(verbose_name='標題', max_length=32)
    url = models.CharField(verbose_name='含正則的URL', max_length=128)

    def __str__(self):
        return self.title


class Role(models.Model):
    """
    角色
    """
    title = models.CharField(verbose_name='角色名稱', max_length=32)
    permissions = models.ManyToManyField(verbose_name='擁有的全部權限', to='Permission', blank=True)

    def __str__(self):
        return self.title


class UserInfo(models.Model):
    """
    用戶表
    """
    name = models.CharField(verbose_name='用戶名', max_length=32)
    password = models.CharField(verbose_name='密碼', max_length=64)
    email = models.CharField(verbose_name='郵箱', max_length=32)
    roles = models.ManyToManyField(verbose_name='擁有的全部角色', to='Role', blank=True)

    def __str__(self):
        return self.name

rbac/models.py
rbac/models.py
from django.db import models


class Customer(models.Model):
    """
    客戶表
    """
    name = models.CharField(verbose_name='姓名', max_length=32)
    age = models.CharField(verbose_name='年齡', max_length=32)
    email = models.EmailField(verbose_name='郵箱', max_length=32)
    company = models.CharField(verbose_name='公司', max_length=32)


class Payment(models.Model):
    """
    付費記錄
    """
    customer = models.ForeignKey(verbose_name='關聯客戶', to='Customer')
    money = models.IntegerField(verbose_name='付費金額')
    create_time = models.DateTimeField(verbose_name='付費時間', auto_now_add=True)

web/models.py
web/models.py

《客戶管理》系統截圖:基本增刪改查和Excel導入源碼下載猛擊這裏

以上簡易版客戶管理系統中的URL有:

  • 客戶管理
    • 客戶列表:/customer/list/
    • 添加客戶:/customer/add/
    • 刪除客戶:/customer/list/(?P<cid>\d+)/
    • 修改客戶:/customer/edit/(?P<cid>\d+)/
    • 批量導入:/customer/import/
    • 下載模板:/customer/tpl/
  • 帳單管理
    • 帳單列表:/payment/list/
    • 添加帳單:/payment/add/
    • 刪除帳單:/payment/del/(?P<pid>\d+)/
    • 修改帳單:/payment/edit/<?P<pid>\d+/

那麼接下來,咱們就在權限組件中錄入相關信息:

  • 錄入權限
  • 建立用戶
  • 建立角色
  • 用戶分配角色
  • 角色分配權限

這麼一來,用戶登陸時,就能夠根據本身的【用戶】找到全部的角色,再根據角色找到全部的權限,再將權限信息放入session,之後每次訪問時候須要先去session檢查是否有權訪問。

已錄入權限數據源碼下載猛擊這裏

含用戶登陸權限源碼下載:猛擊這裏(簡易版)

含用戶登陸權限源碼下載猛擊這裏

至此,基本的權限控制已經完成,基本流程爲:

  • 用戶登陸,獲取權限信息並放入session
  • 用戶訪問,在中間件從session中獲取用戶權限信息,並進行權限驗證。

全部示例中的帳戶信息:

1
2
3
4
5
6
7
帳戶一:
     用戶名:alex
        密碼: 123
 
帳戶二:
     用戶名:wupeiqi
        密碼: 123

客戶管理之動態「一級」菜單

上述過程當中的菜單是在程序中定義好的,沒法根據用戶權限進行動態配置。

那麼,接下來咱們來完成一個 「單級菜單」的功能:

from django.db import models


class Permission(models.Model):
    """
    權限表
    """
    title = models.CharField(verbose_name='標題', max_length=32)
    url = models.CharField(verbose_name='含正則的URL', max_length=128)

    icon = models.CharField(verbose_name='圖標', max_length=32, null=True, blank=True, help_text='菜單才設置圖標')
    is_menu = models.BooleanField(verbose_name='是不是菜單', default=False)

    def __str__(self):
        return self.title


class Role(models.Model):
    """
    角色
    """
    title = models.CharField(verbose_name='角色名稱', max_length=32)
    permissions = models.ManyToManyField(verbose_name='擁有的全部權限', to='Permission', blank=True)

    def __str__(self):
        return self.title


class UserInfo(models.Model):
    """
    用戶表
    """
    name = models.CharField(verbose_name='用戶名', max_length=32)
    password = models.CharField(verbose_name='密碼', max_length=64)
    email = models.CharField(verbose_name='郵箱', max_length=32)
    roles = models.ManyToManyField(verbose_name='擁有的全部角色', to='Role', blank=True)

    def __str__(self):
        return self.name

表結構
表結構

這樣能夠開發出一個單級菜單的示例:

 

示例源碼下載:猛擊這裏(無默認選中)

示例源碼下載猛擊這裏(含默認選中)

客戶管理之動態「二級」菜單

對於功能比較少的應用程序 「一級菜單」 基本能夠知足需求,可是功能多的程序就須要 「二級菜單」 了,而且訪問時候須要默認選中指定菜單。

from django.db import models


class Menu(models.Model):
    """
    菜單
    """
    title = models.CharField(verbose_name='菜單', max_length=32)
    icon = models.CharField(verbose_name='圖標', max_length=32)

    def __str__(self):
        return self.title


class Permission(models.Model):
    """
    權限表
    """
    title = models.CharField(verbose_name='標題', max_length=32)
    url = models.CharField(verbose_name='含正則的URL', max_length=128)

    menu = models.ForeignKey(verbose_name='菜單', to='Menu', null=True, blank=True, help_text='null表示非菜單')

    def __str__(self):
        return self.title


class Role(models.Model):
    """
    角色
    """
    title = models.CharField(verbose_name='角色名稱', max_length=32)
    permissions = models.ManyToManyField(verbose_name='擁有的全部權限', to='Permission', blank=True)

    def __str__(self):
        return self.title


class UserInfo(models.Model):
    """
    用戶表
    """
    name = models.CharField(verbose_name='用戶名', max_length=32)
    password = models.CharField(verbose_name='密碼', max_length=64)
    email = models.CharField(verbose_name='郵箱', max_length=32)
    roles = models.ManyToManyField(verbose_name='擁有的全部角色', to='Role', blank=True)

    def __str__(self):
        return self.name

表結構
表結構

示例效果:

 

示例源碼下載猛擊這裏

示例源碼下載猛擊這裏(路飛線上錄製代碼示例)

客戶管理之默認展開非菜單URL

因爲不少URL都是不能做爲菜單,因此當點擊該類功能時,是沒法默認展開菜單的,如:

  • 刪除
  • 修改
  • ...

此類頁面只能經過菜單頁面二次點擊才能跳轉,此時也應該爲他們默認展開原菜單。

from django.db import models


class Menu(models.Model):
    """
    菜單
    """
    title = models.CharField(verbose_name='菜單', max_length=32)
    icon = models.CharField(verbose_name='圖標', max_length=32)

    def __str__(self):
        return self.title


class Permission(models.Model):
    """
    權限表
    """
    title = models.CharField(verbose_name='標題', max_length=32)
    url = models.CharField(verbose_name='含正則的URL', max_length=128)

    pid = models.ForeignKey(verbose_name='默認選中權限', to='Permission', related_name='ps', null=True, blank=True,
                            help_text="對於沒法做爲菜單的URL,能夠爲其選擇一個能夠做爲菜單的權限,那麼訪問時,則默認選中此權限",
                            limit_choices_to={'menu__isnull': False})
    
    menu = models.ForeignKey(verbose_name='菜單', to='Menu', null=True, blank=True, help_text='null表示非菜單')

    def __str__(self):
        return self.title


class Role(models.Model):
    """
    角色
    """
    title = models.CharField(verbose_name='角色名稱', max_length=32)
    permissions = models.ManyToManyField(verbose_name='擁有的全部權限', to='Permission', blank=True)

    def __str__(self):
        return self.title


class UserInfo(models.Model):
    """
    用戶表
    """
    name = models.CharField(verbose_name='用戶名', max_length=32)
    password = models.CharField(verbose_name='密碼', max_length=64)
    email = models.CharField(verbose_name='郵箱', max_length=32)
    roles = models.ManyToManyField(verbose_name='擁有的全部角色', to='Role', blank=True)

    def __str__(self):
        return self.name

表結構
表結構

功能完成後的示例以下:

示例源碼下載猛擊這裏

示例源碼下載猛擊這裏(路飛線上錄製代碼示例)

客戶管理之訪問路徑導航

若是想要保留放的地址,那麼就能夠經過在權限配置中獲取此功能,示例以下:

 

示例源碼下載猛擊這裏

客戶管理之 權限粒度控制按鈕級別

不一樣用戶登陸系統時候,根據權限不一樣來控制是否限制指定按鈕,如:

示例源碼下載猛擊這裏 

示例源碼下載猛擊這裏(路飛線上錄製代碼示例)

客戶管理之 編輯權限和分配權限

給用戶進行權限的分配。

1. 用戶和角色管理

 

示例源碼下載猛擊這裏(用戶和角色管理)

2. 一級菜單

 

示例源碼下載猛擊這裏(菜單和權限管理之一級菜單)

3. 二級菜單管理

 

示例源碼下載猛擊這裏(菜單和權限管理之二級菜單)

4. 三級菜單管理(權限管理)

示例源碼下載猛擊這裏(菜單和權限管理之三級菜單)

5. django formset示例

源碼示例下載猛擊這裏(django formset實現批量添加和編輯)

6. 批量操做權限的顯示

源碼示例下載猛擊這裏(批量操做權限的顯示)

7. 批量操做權限:添加、刪除、更新

  

源碼示例下載猛擊這裏(權限的批量增刪改) 

8. 權限分配

源碼示例下載猛擊這裏(權限分配)

RBAC組件應用及文檔

"""
 
1. 將rbac組件拷貝項目。
 
 
2. 將rbac/migrations目錄中的數據庫遷移記錄刪除
 
 
3. 業務系統中用戶表結構的設計
 
    業務表結構中的用戶表須要和rbac中的用戶有繼承關係,如:
 
    rbac/models.py
        class UserInfo(models.Model):
            # 用戶表
            name = models.CharField(verbose_name='用戶名', max_length=32)
            password = models.CharField(verbose_name='密碼', max_length=64)
            email = models.CharField(verbose_name='郵箱', max_length=32)
            roles = models.ManyToManyField(verbose_name='擁有的全部角色', to=Role, blank=True) 嚴重提醒 Role 不要加引號
 
            def __str__(self):
                return self.name
 
            class Meta:
                # django之後再作數據庫遷移時,再也不爲UserInfo類建立相關的表以及表結構了。
                # 此類能夠當作"父類",被其餘Model類繼承。
                abstract = True
 
    業務/models.py
        class UserInfo(RbacUserInfo):
            phone = models.CharField(verbose_name='聯繫方式', max_length=32)
            level_choices = (
                (1, 'T1'),
                (2, 'T2'),
                (3, 'T3'),
            )
            level = models.IntegerField(verbose_name='級別', choices=level_choices)
 
            depart = models.ForeignKey(verbose_name='部門', to='Department')
 
4. 講業務系統中的用戶表的路徑寫到配置文件。
 
    # 業務中的用戶表
    RBAC_USER_MODLE_CLASS = "app01.models.UserInfo"
 
    用於在rbac分配權限時,讀取業務表中的用戶信息。
 
 
5. 業務邏輯開發
    將全部的路由都設置一個name,如:
            url(r'^login/$', account.login, name='login'),
            url(r'^logout/$', account.logout, name='logout'),
 
            url(r'^index/$', account.index, name='index'),
 
            url(r'^user/list/$', user.user_list, name='user_list'),
            url(r'^user/add/$', user.user_add, name='user_add'),
            url(r'^user/edit/(?P<pk>\d+)/$', user.user_edit, name='user_edit'),
            url(r'^user/del/(?P<pk>\d+)/$', user.user_del, name='user_del'),
            url(r'^user/reset/password/(?P<pk>\d+)/$', user.user_reset_pwd, name='user_reset_pwd'),
 
            url(r'^host/list/$', host.host_list, name='host_list'),
            url(r'^host/add/$', host.host_add, name='host_add'),
            url(r'^host/edit/(?P<pk>\d+)/$', host.host_edit, name='host_edit'),
            url(r'^host/del/(?P<pk>\d+)/$', host.host_del, name='host_del'),
    用於反向生成URL以及粒度控制到按鈕級別的權限控制。
 
6. 權限信息錄入
    - 在url中添加rbac的路由分發,注意:必須設置namespace
        urlpatterns = [
            ...
            url(r'^rbac/', include('rbac.urls', namespace='rbac')),
 
        ]
 
    - rbac提供的地址進行操做
        - http://127.0.0.1:8000/rbac/menu/list/
        - http://127.0.0.1:8000/rbac/role/list/
        - http://127.0.0.1:8000/rbac/distribute/permissions/
 
    相關配置:自動發現URL時,排除的URL:
 
        # 自動化發現路由中URL時,排除的URL
        AUTO_DISCOVER_EXCLUDE = [
            '/admin/.*',
            '/login/',
            '/logout/',
            '/index/',
        ]
 
 
7. 編寫用戶登陸的邏輯【進行權限初始化】
 
    from django.shortcuts import render, redirect
    from app01 import models
    from rbac.service.init_permission import init_permission
 
 
    def login(request):
        if request.method == 'GET':
            return render(request, 'login.html')
 
        user = request.POST.get('username')
        pwd = request.POST.get('password')
 
        user_object = models.UserInfo.objects.filter(name=user, password=pwd).first()
        if not user_object:
            return render(request, 'login.html', {'error': '用戶名或密碼錯誤'})
 
        # 用戶權限信息的初始化
        init_permission(user_object, request)
 
        return redirect('/index/')
 
 
    相關配置: 權限和菜單的session key:
 
        setting.py
            PERMISSION_SESSION_KEY = "luffy_permission_url_list_key"
            MENU_SESSION_KEY = "luffy_permission_menu_key"
 
8. 編寫一個首頁的邏輯
 
    def index(request):
        return render(request, 'index.html')
 
 
    相關配置:須要登陸但無需權限的URL
 
        # 須要登陸但無需權限的URL
        NO_PERMISSION_LIST = [
            '/index/',
            '/logout/',
        ]
 
9. 經過中間件進行權限校驗
 
    # 權限校驗
    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.middlewares.rbac.RbacMiddleware',
    ]
 
    # 白名單,無需登陸就能夠訪問
    VALID_URL_LIST = [
        '/login/',
        '/admin/.*'
    ]
 
 
10. 粒度到按鈕級別的控制
 
        {% extends 'layout.html' %}
        {% load rbac %}
 
        {% block content %}
            <div class="luffy-container">
                <div class="btn-group" style="margin: 5px 0">
 
                    {% if request|has_permission:'host_add' %}
                        <a class="btn btn-default" href="{% memory_url request 'host_add' %}">
                            <i class="fa fa-plus-square" aria-hidden="true"></i> 添加主機
                        </a>
                    {% endif %}
 
                </div>
                <table class="table table-bordered table-hover">
                    <thead>
                    <tr>
                        <th>主機名</th>
                        <th>IP</th>
                        <th>部門</th>
                        {% if request|has_permission:'host_edit' or request|has_permission:'host_del' %}
                            <th>操做</th>
                        {% endif %}
 
                    </tr>
                    </thead>
                    <tbody>
                    {% for row in host_queryset %}
                        <tr>
                            <td>{{ row.hostname }}</td>
                            <td>{{ row.ip }}</td>
                            <td>{{ row.depart.title }}</td>
                            {% if request|has_permission:'host_edit' or request|has_permission:'host_del' %}
                                <td>
                                    {% if request|has_permission:'host_edit' %}
                                        <a style="color: #333333;" href="{% memory_url request 'host_edit' pk=row.id %}">
                                            <i class="fa fa-edit" aria-hidden="true"></i></a>
                                    {% endif %}
                                    {% if request|has_permission:'host_del' %}
                                        <a style="color: #d9534f;" href="{% memory_url request 'host_del' pk=row.id %}"><i
                                                class="fa fa-trash-o"></i></a>
                                    {% endif %}
                                </td>
                            {% endif %}
                        </tr>
                    {% endfor %}
                    </tbody>
                </table>
            </div>
 
        {% endblock %}
 
 
 
 
總結,目的是但願在任意系統中應用權限系統。
    - 用戶登陸 + 用戶首頁 + 用戶註銷 業務邏輯
    - 項目業務邏輯開發
        注意:開發時候靈活的去設置layout.html中的兩個inclusion_tag
            <div class="pg-body">
                <div class="left-menu">
                    <div class="menu-body">
                        {% multi_menu request %}  # 開發時,去掉;上下時,取回。
                    </div>
                </div>
                <div class="right-body">
                    <div>
                        {% breadcrumb request %} # 開發時,去掉;上下時,取回。
                    </div>
                    {% block content %} {% endblock %}
                </div>
            </div>
    - 權限信息的錄入
    - 配置文件
        # 註冊APP
        INSTALLED_APPS = [
            ...
            'rbac.apps.RbacConfig'
        ]
        # 應用中間件
        MIDDLEWARE = [
            ...
            'rbac.middlewares.rbac.RbacMiddleware',
        ]
 
        # 業務中的用戶表
        RBAC_USER_MODLE_CLASS = "app01.models.UserInfo"
        # 權限在Session中存儲的key
        PERMISSION_SESSION_KEY = "luffy_permission_url_list_key"
        # 菜單在Session中存儲的key
        MENU_SESSION_KEY = "luffy_permission_menu_key"
 
        # 白名單
        VALID_URL_LIST = [
            '/login/',
            '/admin/.*'
        ]
 
        # 須要登陸但無需權限的URL
        NO_PERMISSION_LIST = [
            '/index/',
            '/logout/',
        ]
 
        # 自動化發現路由中URL時,排除的URL
        AUTO_DISCOVER_EXCLUDE = [
            '/admin/.*',
            '/login/',
            '/logout/',
            '/index/',
        ]
 
    - 粒度到按鈕級別的控制
"""
View Code

最終版組件下載:rbac.zip 

源碼示例下載rbac組件應用之主機管理系統【auto_luffy.zip】

相關文章
相關標籤/搜索