python 全棧開發,Day107(CRM初始,權限組件之權限控制,權限系統表設計)


 一、CRM初始

CRM,客戶關系管理系統(Customer Relationship Management)。企業用CRM技術來管理與客戶之間的關系,以求提升企業成功的管理方式,其目的是協助企業管理銷售循環:新客戶的招徠、保留舊客戶、提供客戶服務及進一步提升企業和客戶的關系,並運用市場營銷工具,提供創新式的個人化的客戶商談和服務,輔以相應的信息系統或信息技術如數據挖掘和數據庫營銷來協調所有公司與顧客間在銷售、營銷以及服務上的交互。

此系統主要是以教育行業為背景,為公司開發的一套客戶關系管理系統。考慮到各位童鞋可能處於各行各業,為了擴大的系統使用范圍,特此將該項目開發改為組件化開發,讓同學們可以日后在自己公司快速搭建類似系統及新功能擴展。

  • 權限系統,一個獨立的rbac組件。
  • stark組件,一個獨立的curd組件。
  • crm業務,以教育行業為背景並整合以上兩個組件開發一套系統。

 

二、權限組件之權限控制

1. 問:為什么程序需要權限控制?

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

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

所以,只要有不同角色的人員來使用系統,那么就肯定需要權限系統。

2. 問:為什么要開發權限組件?

   答:假設你今年25歲,從今天開始寫代碼到80歲,每年寫5個項目,那么你的一生就會寫275個項目,保守估計其中應該有150+個都需要用到權限控制,為了以后不再重復的寫代碼,所以就開發一個權限組件以便之后55年的歲月中使用。 親,不要太較真哦,你覺得程序員能到80歲么,哈哈哈哈哈哈哈 
偷偷告訴你:老程序員開發速度快,其中一個原因是經驗豐富,另外一個就是他自己保留了很多組件,新系統開發時,只需把組件拼湊起來基本就可以完成。

3. 問:web開發中權限指的是什么?

   答:web程序是通過 url 的切換來查看不同的頁面(功能),所以權限指的其實就是URL,對url控制就是對權限的控制。

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

權限表結構設計:第一版

問答環節中已得出權限就是URL的結論,那么就可以開始設計表結構了。

  • 一個用戶可以有多個權限。
  • 一個權限可以分配給多個用戶。

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

現在,此時此刻是不是覺得自己設計出的表結構棒棒噠!!!

But,無論是是否承認,你還是too young too native,因為老漢腚眼一看就有問題....

問題:假設 “老男孩”和“Alex” 這倆貨都是老板,老板的權限一定是非常多。那么試想,如果給這倆貨分配權限時需要在【用戶權限關系表中】添加好多條數據。假設再次需要對老板的權限進行修改時,又需要在【用戶權限關系表】中找到這倆人所有的數據進行更新,太他媽煩了吧!!! 類似的,如果給其他相同角色的人來分配權限時,必然會非常繁瑣。

權限表結構設計:第二版

聰明機智的一定在上述的表述中看出了寫門道,如果對用戶進行角色的划分,然后對角色進行權限的分配,這不就迎刃而解了么。

  • 一個人可以有多個角色。
  • 一個角色可以有多個人。
  • 一個角色可以有多個權限。
  • 一個權限可以分配給多個角色。

表結構設計:

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

models.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
View Code

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

注意:現在的設計還不是最終版,但之后的設計都是在此版本基礎上擴增的,為了讓大家能夠更好的理解,我們暫且再此基礎上繼續開發,直到遇到無法滿足的情況,再進行整改。

源碼示例猛擊下載

客戶管理之權限控制

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

目錄結構:

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
View Code

rbac/models.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
View Code

web/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
View Code

《客戶管理》系統截圖:基本增刪改查和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中獲取用戶權限信息,並進行權限驗證。

所有示例中的賬戶信息:

賬戶一:
    用戶名:alex
       密碼:123
 
賬戶二:
    用戶名:wupeiqi
       密碼:123
View Code

 

本文參考鏈接:

https://www.cnblogs.com/wupeiqi/articles/9178982.html

 

 

作業

1. django程序
2. 兩個app 
    - rbac,權限相關所有的東西
        - models.py(三個類5張表)
    - web,隨便寫業務處理
        - models.py 

3. 找URL並使用django admin 錄入到權限表
    urlpatterns = [
        url(r'^customer/list/$', customer.customer_list),
        url(r'^customer/add/$', customer.customer_add),
        url(r'^customer/edit/(?P<cid>\d+)/$', customer.customer_edit),
        url(r'^customer/del/(?P<cid>\d+)/$', customer.customer_del),
        url(r'^customer/import/$', customer.customer_import),
        url(r'^customer/tpl/$', customer.customer_tpl),

        url(r'^payment/list/$', payment.payment_list),
        url(r'^payment/add/$', payment.payment_add),
        url(r'^payment/edit/(?P<pid>\d+)/$', payment.payment_edit),
        url(r'^payment/del/(?P<pid>\d+)/$', payment.payment_del),
    ]
    
4. 角色和用戶管理


5. 寫代碼 
    a. 用戶登陸 
        - 獲取用戶信息放入session 
        - 獲取當前用戶所有的權限並寫入session 
    b. 編寫中間件做權限信息校驗
        - 獲取當前請求URL
        - 獲取當前用戶的所有權限
        - 權限校驗
View Code

請下載github代碼

https://github.com/987334176/luffy_permission/archive/v1.0.zip

 

錄入數據

修改rbac目錄下的admin.py,注冊表

from django.contrib import admin

# Register your models here.
from rbac import models

admin.site.register(models.Permission)
admin.site.register(models.Role)
admin.site.register(models.UserInfo)
View Code

創建超級用戶

python manage.py createsuperuser

登錄admin后台,開始錄入數據

先增加用戶,再增加角色,最后設置權限

注意,權限來自於urls.py

添加url完成之后,綁定角色和權限

 

相關權限表關系

 

表記錄(大概)

 

測試ORM

這個作業,主要是能得到用戶的授權url列表。如果這都不能查詢出來,那么作業可以放棄了!

先不着急寫頁面,用腳本測試orm

修改rbac目錄下的tests.py

from django.test import TestCase

# Create your tests here.
import os

if __name__ == "__main__":
    # 設置django環境
    os.environ.setdefault("DJANGO_SETTINGS_MODULE", "luffy_permission.settings")
    import django
    django.setup()

    from rbac import models

    # 固定用戶名和密碼
    user = 'xiao'
    pwd = '123'
    # 查詢表,用戶名和密碼是否匹配
    obj = models.UserInfo.objects.filter(name=user, password=pwd).first()
    role = obj.roles.all()  # 查詢當前用戶的所有角色
    permissions_list = []  # 空列表,用來存放用戶能訪問的url列表

    for i in role:  # 循環角色
        per = i.permissions.all()  # 查看當前用戶所有角色的所有權限
        # print(i.permissions.all())
        for j in per:
            # print(j.url)
            # 將所有授權的url添加到列表中
            permissions_list.append(j.url)

    print(permissions_list)
View Code

使用Pycharm執行輸出:

['^customer/list/$', '^customer/add/$', '^customer/edit/(?P<cid>\\d+)/$', '^customer/del/(?P<cid>\\d+)/$', '^customer/import/$', '^customer/tpl/$', '^payment/list/$', '^payment/add/$', '^payment/edit/(?P<pid>\\d+)/$', '^payment/del/(?P<pid>\\d+)/$']
View Code

注意:這里面的url都是正則表達式!通過它來驗證用戶的url是否授權!

 

登錄頁面

先做用戶登錄

修改web目錄下的urls.py,增加路徑

from django.conf.urls import url
from web.views import customer,payment,auth,login

urlpatterns = [
    # 客戶管理
    url(r'^customer/list/$', customer.customer_list),
    url(r'^customer/add/$', customer.customer_add),
    url(r'^customer/edit/(?P<cid>\d+)/$', customer.customer_edit),
    url(r'^customer/del/(?P<cid>\d+)/$', customer.customer_del),
    url(r'^customer/import/$', customer.customer_import),
    url(r'^customer/tpl/$', customer.customer_tpl),
    # 賬單管理
    url(r'^payment/list/$', payment.payment_list),
    url(r'^payment/add/$', payment.payment_add),
    url(r'^payment/edit/(?P<pid>\d+)/$', payment.payment_edit),
    url(r'^payment/del/(?P<pid>\d+)/$', payment.payment_del),
    # 登錄相關
    url(r'^$', login.login),  # 前端
    url(r'^login/$', login.login),
    url(r'^auth/$', auth.AuthView.as_view({'post': 'login'})),  # 認證api
]
View Code

在web目錄下的views目錄下,創建文件login.py

from django.shortcuts import render, redirect,HttpResponse

def login(request):
    return render(request,"login.html")
View Code

使用session

在web目錄下的views目錄下,創建文件auth.py

from rest_framework.response import Response
from rest_framework.views import APIView
from rest_framework.viewsets import ViewSetMixin
from rbac import models
from utils.response import BaseResponse

class AuthView(ViewSetMixin,APIView):
    authentication_classes = []  # 空列表表示不認證

    def login(self,request,*args,**kwargs):
        """
        用戶登陸認證
        :param request:
        :param args:
        :param kwargs:
        :return:
        """
        response = BaseResponse()  # 默認狀態
        try:
            user = request.data.get('username')
            pwd = request.data.get('password')
            # print(user,pwd)
            # 驗證用戶和密碼
            obj = models.UserInfo.objects.filter(name=user,password=pwd).first()
            if not obj:  # 判斷查詢結果
                response.code = 1002
                response.error = '用戶名或密碼錯誤'
            else:
                role = obj.roles.all()  # 查詢當前用戶的所有角色
                permissions_list = []  # 定義空列表

                for i in role:  # 循環角色
                    per = i.permissions.all()  # 查看當前用戶所有角色的所有權限
                    # print(i.permissions.all())
                    for j in per:
                        # print(j.url)
                        # 將所有授權的url添加到列表中
                        permissions_list.append(j.url)

                # print(permissions_list)
                response.code = 1000

                # 增加session
                request.session['url'] = permissions_list

        except Exception as e:
            response.code = 10005
            response.error = '操作異常'

        # print(response.dict)
        return Response(response.dict)
View Code

 

在web目錄下的templates目錄下,創建login.html

{% load staticfiles %}
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
    <script src="{% static 'js/jquery-3.3.1.min.js' %} "></script>
    <script src="{% static 'plugins/sweetalert/sweetalert-dev.js' %} "></script>
    <link rel="stylesheet" href="{% static 'plugins/sweetalert/sweetalert.css' %}">
</head>
<body>
<div style="height: 100px;"></div>
<form action="/auth/" method="post">
    <lable>用戶名</lable>
    <input type="text" name="username" id="user">
    <lable>密碼</lable>
    <input type="password" name="password" id="pwd">
    <input type="button" id="sub" value="登錄">
</form>
<script>
    $("#sub").click(function () {
        $.ajax({
            url: "/auth/",
            type: "post",
            data: {
                username: $("#user").val(),
                password: $("#pwd").val(),
            },
            success: function (data) {
                console.log(data);
                if (data.code == 1000) { //判斷json的狀態
                    swal({
                        title: '登錄成功',
                        type: 'success',  //展示成功的圖片
                        timer: 500,  //延時500毫秒
                        showConfirmButton: false  //關閉確認框
                    }, function () {
                        window.location.href = "/customer/list/";  //跳轉
                    });
                } else {
                    swal("登錄失敗!", data.error,
                        "error");
                    {#window.location = "/backend/add_category/";#}
                }
            },
            error: function (data) {
                console.log('登錄異常');
            }
        })

    });

</script>
</body>
</html>
View Code

在web目錄下的templates目錄下,創建error.html

{% load staticfiles %}
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
<script src="{% static 'js/jquery-3.3.1.min.js' %} "></script>
{#<link rel="stylesheet" href="http://mishengqiang.com/sweetalert/css/sweetalert.css">#}
<link rel="stylesheet" href="{% static 'plugins/sweetalert/sweetalert.css' %}">
{#<script src="https://cdn.bootcss.com/jquery/3.3.1/jquery.min.js"></script>#}
<script src="{% static 'js/jquery-3.3.1.min.js' %}"></script>
{#<script src="http://mishengqiang.com/sweetalert/js/sweetalert-dev.js"></script>#}
<script src="{% static 'plugins/sweetalert/sweetalert-dev.js' %}"></script>
<div>
    {#獲取錯誤信息#}
    <input type="hidden" id="msg" value="{{ msg }}">
    <input type="hidden" id="url" value="{{ url }}">
</div>

<script>
    $(function () {
        var msg = $("#msg").val();
        var url = $("#url").val();
        console.log(msg);
        console.log(url);

        if (msg.length > 0) {  //判斷是否有錯誤信息
            swal({
                title: msg,
                text: "1秒后自動關閉。",
                type: 'error',
                timer: 1000,
                showConfirmButton: false
            }, function () {
                window.location.href = url;  //跳轉指定url
            });

        }


    })
</script>

</body>
</html>
View Code

 

中間件驗證

在web目錄下創建文件middlewares.py

from django.utils.deprecation import MiddlewareMixin
from django.shortcuts import render, redirect, HttpResponse
from luffy_permission import settings
import os

class AuthMD(MiddlewareMixin):  # 驗證登錄
    white_list = ['/','/login/','/auth/','/admin/' ]  # 白名單
    # black_list = ['/black/', ]  # 黑名單
    ret = {"status": 0, 'url': '', 'msg': ''}  # 默認狀態

    def process_request(self, request):  # 請求之前
        request_url = request.path_info  # 獲取請求路徑
        # get_full_path()表示帶參數的路徑
        # print(request.path_info, request.get_full_path())

        # 判斷請求路徑不在白名單中
        if request_url not in self.white_list:
            import re
            per_url = request.session.get("url")  # 獲取用戶session中的url列表
            # print(per_url)
            if per_url:
                for i in per_url:  # 循環url列表
                    # 使用正則匹配。其中i為正則表達式,request_url.lstrip('/')表示去除左邊的'/'
                    result = re.match(i, request_url.lstrip('/'))
                    # print(result)
                    if result:  # 判斷匹配結果
                        print('授權通過',request_url)
                        return None  # return None表示可以繼續走下面的流程
                    # else:
                    #     print('授權不通過',request_url)
                    #     # return redirect('/login/')
    

                # 錯誤頁面提示
                self.ret['msg'] = "未授權,禁止訪問!"
                self.ret['url'] = "/login/"
                path = os.path.join(settings.BASE_DIR, 'web/templates/error.html'),
                return render(request, path, self.ret)  # 渲染錯誤頁面
View Code

在中間件中,只要return none,就會進入下一個中間件!

修改settings.py,注冊中間件

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',
    'web.middlewares.AuthMD',  # 自定義中間件AuthMD
]
View Code

 

測試登錄,訪問頁面

http://127.0.0.1:8000/login/

訪問一個不存在url

完整代碼,請查看github

https://github.com/987334176/luffy_permission/archive/v1.1.zip

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM