一、CRM初始

CRM,客户关系管理系统(Customer Relationship Management)。企业用CRM技术来管理与客户之间的关系,以求提升企业成功的管理方式,其目的是协助企业管理销售循环:新客户的招徕、保留旧客户、提供客户服务及进一步提升企业和客户的关系,并运用市场营销工具,提供创新式的个人化的客户商谈和服务,辅以相应的信息系统或信息技术如数据挖掘和数据库营销来协调所有公司与顾客间在销售、营销以及服务上的交互。

此系统主要是以教育行业为背景,为公司开发的一套客户关系管理系统。考虑到各位童鞋可能处于各行各业,为了扩大的系统使用范围,特此将该项目开发改为组件化开发,让同学们可以日后在自己公司快速搭建类似系统及新功能扩展。

  • 权限系统,一个独立的rbac组件。
  • stark组件,一个独立的curd组件。
  • crm业务,以教育行业为背景并整合以上两个组件开发一套系统。

二、权限组件之权限控制

  1. 问:为什么程序需要权限控制?

答:生活中的权限限制,① 看灾难片电影《2012》中富人和权贵有权登上诺亚方舟,穷苦老百姓只有等着灾难的来临;② 屌丝们,有没有想过为什么那些长得漂亮身材好的姑娘在你身边不存在呢?因为有钱人和漂亮姑娘都是珍贵稀有的,稀有的人在一起玩耍和解锁各种姿势。而你,无权拥有他们,只能自己玩自己了。

程序开发时的权限控制,对于不同用户使用系统时候就应该有不同的功能,如:

  • 普通员工
  • 部门主管
  • 总监
  • 总裁

所以,只要有不同角色的人员来使用系统,那么就肯定需要权限系统。

  1. 问:为什么要开发权限组件?

答:假设你今年25岁,从今天开始写代码到80岁,每年写5个项目,那么你的一生就会写275个项目,保守估计其中应该有150+个都需要用到权限控制,为了以后不再重复的写代码,所以就开发一个权限组件以便之后55年的岁月中使用。 亲,不要太较真哦,你觉得程序员能到80岁么,哈哈哈哈哈哈哈

偷偷告诉你:老程序员开发速度快,其中一个原因是经验丰富,另外一个就是他自己保留了很多组件,新系统开发时,只需把组件拼凑起来基本就可以完成。

  1. 问:web开发中权限指的是什么?

答:web程序是通过 url 的切换来查看不同的页面(功能),所以权限指的其实就是URL,对url控制就是对权限的控制。

结论:一个人有多少个权限就取决于他有多少个URL的访问权限。

权限表结构设计:第一版

问答环节中已得出权限就是URL的结论,那么就可以开始设计表结构了。

  • 一个用户可以有多个权限。
  • 一个权限可以分配给多个用户。

你设计的表结构大概会是这个样子:

Day107 CRM初始,权限组件之权限控制,权限系统表设计 - 图1

Day107 CRM初始,权限组件之权限控制,权限系统表设计 - 图2

Day107 CRM初始,权限组件之权限控制,权限系统表设计 - 图3

现在,此时此刻是不是觉得自己设计出的表结构棒棒哒!!!

But,无论是是否承认,你还是too young too native,因为老汉腚眼一看就有问题….

问题:假设 “老男孩”和“Alex” 这俩货都是老板,老板的权限一定是非常多。那么试想,如果给这俩货分配权限时需要在【用户权限关系表中】添加好多条数据。假设再次需要对老板的权限进行修改时,又需要在【用户权限关系表】中找到这俩人所有的数据进行更新,太他妈烦了吧!!! 类似的,如果给其他相同角色的人来分配权限时,必然会非常繁琐。

权限表结构设计:第二版

聪明机智的一定在上述的表述中看出了写门道,如果对用户进行角色的划分,然后对角色进行权限的分配,这不就迎刃而解了么。

  • 一个人可以有多个角色。
  • 一个角色可以有多个人。
  • 一个角色可以有多个权限。
  • 一个权限可以分配给多个角色。

表结构设计:

Day107 CRM初始,权限组件之权限控制,权限系统表设计 - 图4

Day107 CRM初始,权限组件之权限控制,权限系统表设计 - 图5

Day107 CRM初始,权限组件之权限控制,权限系统表设计 - 图6

Day107 CRM初始,权限组件之权限控制,权限系统表设计 - 图7

Day107 CRM初始,权限组件之权限控制,权限系统表设计 - 图8

这次调整之后,由原来的【基于用户的权限控制】转换成【基于角色的权限控制】,以后再进行分配权限时只需要给指定角色分配一次权限,给众多用户再次分配指定角色即可。

models.py 示例

  1. from django.db import models
  2. class Permission(models.Model):
  3. """
  4. 权限表
  5. """
  6. title = models.CharField(verbose_name='标题', max_length=32)
  7. url = models.CharField(verbose_name='含正则的URL', max_length=128)
  8. def __str__(self):
  9. return self.title
  10. class Role(models.Model):
  11. """
  12. 角色
  13. """
  14. title = models.CharField(verbose_name='角色名称', max_length=32)
  15. permissions = models.ManyToManyField(verbose_name='拥有的所有权限', to='Permission', blank=True)
  16. def __str__(self):
  17. return self.title
  18. class UserInfo(models.Model):
  19. """
  20. 用户表
  21. """
  22. name = models.CharField(verbose_name='用户名', max_length=32)
  23. password = models.CharField(verbose_name='密码', max_length=64)
  24. email = models.CharField(verbose_name='邮箱', max_length=32)
  25. roles = models.ManyToManyField(verbose_name='拥有的所有角色', to='Role', blank=True)
  26. def __str__(self):
  27. return self.name

小伙子,告诉你一个事实,不经意间,你居然设计出了一个经典的权限访问控制系统:rbac(Role-Based Access Control)基于角色的权限访问控制。你这么优秀,为什么不来老男孩IT教育?路飞学城也行呀! 哈哈哈哈。

注意:现在的设计还不是最终版,但之后的设计都是在此版本基础上扩增的,为了让大家能够更好的理解,我们暂且再此基础上继续开发,直到遇到无法满足的情况,再进行整改。

源码示例:猛击下载

客户管理之权限控制

学习知识最好的方式就是试错,坑踩多了那么学到的知识自然而然就多了,所以接下里下来我们用《客户管理》系统为示例,提出功能并实现,并且随着功能越来越多,一点点来找出问题,并解决问题。

目录结构:

  1. luffy_permission/
  2. ├── db.sqlite3
  3. ├── luffy_permission
  4. ├── __init__.py
  5. ├── settings.py
  6. ├── urls.py
  7. └── wsgi.py
  8. ├── manage.py
  9. ├── rbac # 权限组件,便于以后应用到其他系统
  10. ├── __init__.py
  11. ├── admin.py
  12. ├── apps.py
  13. ├── models.py
  14. ├── tests.py
  15. └── views.py
  16. ├── templates
  17. └── web # 客户管理业务
  18. ├── __init__.py
  19. ├── admin.py
  20. ├── apps.py
  21. ├── models.py
  22. ├── tests.py
  23. └── views.py

rbac/models.py

  1. from django.db import models
  2. class Permission(models.Model):
  3. """
  4. 权限表
  5. """
  6. title = models.CharField(verbose_name='标题', max_length=32)
  7. url = models.CharField(verbose_name='含正则的URL', max_length=128)
  8. def __str__(self):
  9. return self.title
  10. class Role(models.Model):
  11. """
  12. 角色
  13. """
  14. title = models.CharField(verbose_name='角色名称', max_length=32)
  15. permissions = models.ManyToManyField(verbose_name='拥有的所有权限', to='Permission', blank=True)
  16. def __str__(self):
  17. return self.title
  18. class UserInfo(models.Model):
  19. """
  20. 用户表
  21. """
  22. name = models.CharField(verbose_name='用户名', max_length=32)
  23. password = models.CharField(verbose_name='密码', max_length=64)
  24. email = models.CharField(verbose_name='邮箱', max_length=32)
  25. roles = models.ManyToManyField(verbose_name='拥有的所有角色', to='Role', blank=True)
  26. def __str__(self):
  27. return self.name
  28. rbac/models.py

web/models.py

  1. from django.db import models
  2. class Customer(models.Model):
  3. """
  4. 客户表
  5. """
  6. name = models.CharField(verbose_name='姓名', max_length=32)
  7. age = models.CharField(verbose_name='年龄', max_length=32)
  8. email = models.EmailField(verbose_name='邮箱', max_length=32)
  9. company = models.CharField(verbose_name='公司', max_length=32)
  10. class Payment(models.Model):
  11. """
  12. 付费记录
  13. """
  14. customer = models.ForeignKey(verbose_name='关联客户', to='Customer')
  15. money = models.IntegerField(verbose_name='付费金额')
  16. create_time = models.DateTimeField(verbose_name='付费时间', auto_now_add=True)
  17. web/models.py

《客户管理》系统截图:基本增删改查和Excel导入源码下载猛击这里

Day107 CRM初始,权限组件之权限控制,权限系统表设计 - 图9

Day107 CRM初始,权限组件之权限控制,权限系统表设计 - 图10

Day107 CRM初始,权限组件之权限控制,权限系统表设计 - 图11

以上简易版客户管理系统中的URL有:

  • 客户管理
    • 客户列表:/customer/list/
    • 添加客户:/customer/add/
    • 删除客户:/customer/list/(?P\d+)/
    • 修改客户:/customer/edit/(?P\d+)/
    • 批量导入:/customer/import/
    • 下载模板:/customer/tpl/
  • 账单管理
    • 账单列表:/payment/list/
    • 添加账单:/payment/add/
    • 删除账单:/payment/del/(?P\d+)/
    • 修改账单:/payment/edit/<?P\d+/

那么接下来,我们就在权限组件中录入相关信息:

  • 录入权限
  • 创建用户
  • 创建角色
  • 用户分配角色
  • 角色分配权限

这么一来,用户登录时,就可以根据自己的【用户】找到所有的角色,再根据角色找到所有的权限,再将权限信息放入session,以后每次访问时候需要先去session检查是否有权访问。

已录入权限数据源码下载猛击这里

含用户登录权限源码下载猛击这里(简易版)

含用户登录权限源码下载猛击这里

至此,基本的权限控制已经完成,基本流程为:

  • 用户登录,获取权限信息并放入session
  • 用户访问,在中间件从session中获取用户权限信息,并进行权限验证。

所有示例中的账户信息:

  1. 账户一:
  2. 用户名:alex
  3. 密码:123
  4. 账户二:
  5. 用户名:wupeiqi
  6. 密码:123

本文参考链接:

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

作业

  1. 1. django程序
  2. 2. 两个app
  3. - rbac,权限相关所有的东西
  4. - models.py(三个类5张表)
  5. - web,随便写业务处理
  6. - models.py
  7. 3. URL并使用django admin 录入到权限表
  8. urlpatterns = [
  9. url(r'^customer/list/$', customer.customer_list),
  10. url(r'^customer/add/$', customer.customer_add),
  11. url(r'^customer/edit/(?P<cid>\d+)/$', customer.customer_edit),
  12. url(r'^customer/del/(?P<cid>\d+)/$', customer.customer_del),
  13. url(r'^customer/import/$', customer.customer_import),
  14. url(r'^customer/tpl/$', customer.customer_tpl),
  15. url(r'^payment/list/$', payment.payment_list),
  16. url(r'^payment/add/$', payment.payment_add),
  17. url(r'^payment/edit/(?P<pid>\d+)/$', payment.payment_edit),
  18. url(r'^payment/del/(?P<pid>\d+)/$', payment.payment_del),
  19. ]
  20. 4. 角色和用户管理
  21. 5. 写代码
  22. a. 用户登陆
  23. - 获取用户信息放入session
  24. - 获取当前用户所有的权限并写入session
  25. b. 编写中间件做权限信息校验
  26. - 获取当前请求URL
  27. - 获取当前用户的所有权限
  28. - 权限校验

请下载github代码

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

录入数据

修改rbac目录下的admin.py,注册表

  1. from django.contrib import admin
  2. # Register your models here.
  3. from rbac import models
  4. admin.site.register(models.Permission)
  5. admin.site.register(models.Role)
  6. admin.site.register(models.UserInfo)

创建超级用户

  1. python manage.py createsuperuser

登录admin后台,开始录入数据

Day107 CRM初始,权限组件之权限控制,权限系统表设计 - 图12

先增加用户,再增加角色,最后设置权限

注意,权限来自于urls.py

Day107 CRM初始,权限组件之权限控制,权限系统表设计 - 图13

添加url完成之后,绑定角色和权限

Day107 CRM初始,权限组件之权限控制,权限系统表设计 - 图14

Day107 CRM初始,权限组件之权限控制,权限系统表设计 - 图15

相关权限表关系

Day107 CRM初始,权限组件之权限控制,权限系统表设计 - 图16

表记录(大概)

Day107 CRM初始,权限组件之权限控制,权限系统表设计 - 图17

测试ORM

这个作业,主要是能得到用户的授权url列表。如果这都不能查询出来,那么作业可以放弃了!

先不着急写页面,用脚本测试orm

修改rbac目录下的tests.py

  1. from django.test import TestCase
  2. # Create your tests here.
  3. import os
  4. if __name__ == "__main__":
  5. # 设置django环境
  6. os.environ.setdefault("DJANGO_SETTINGS_MODULE", "luffy_permission.settings")
  7. import django
  8. django.setup()
  9. from rbac import models
  10. # 固定用户名和密码
  11. user = 'xiao'
  12. pwd = '123'
  13. # 查询表,用户名和密码是否匹配
  14. obj = models.UserInfo.objects.filter(name=user, password=pwd).first()
  15. role = obj.roles.all() # 查询当前用户的所有角色
  16. permissions_list = [] # 空列表,用来存放用户能访问的url列表
  17. for i in role: # 循环角色
  18. per = i.permissions.all() # 查看当前用户所有角色的所有权限
  19. # print(i.permissions.all())
  20. for j in per:
  21. # print(j.url)
  22. # 将所有授权的url添加到列表中
  23. permissions_list.append(j.url)
  24. print(permissions_list)

使用Pycharm执行输出:

  1. ['^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+)/$']

注意:这里面的url都是正则表达式!通过它来验证用户的url是否授权!

登录页面

先做用户登录

修改web目录下的urls.py,增加路径

  1. from django.conf.urls import url
  2. from web.views import customer,payment,auth,login
  3. urlpatterns = [
  4. # 客户管理
  5. url(r'^customer/list/$', customer.customer_list),
  6. url(r'^customer/add/$', customer.customer_add),
  7. url(r'^customer/edit/(?P<cid>\d+)/$', customer.customer_edit),
  8. url(r'^customer/del/(?P<cid>\d+)/$', customer.customer_del),
  9. url(r'^customer/import/$', customer.customer_import),
  10. url(r'^customer/tpl/$', customer.customer_tpl),
  11. # 账单管理
  12. url(r'^payment/list/$', payment.payment_list),
  13. url(r'^payment/add/$', payment.payment_add),
  14. url(r'^payment/edit/(?P<pid>\d+)/$', payment.payment_edit),
  15. url(r'^payment/del/(?P<pid>\d+)/$', payment.payment_del),
  16. # 登录相关
  17. url(r'^$', login.login), # 前端
  18. url(r'^login/$', login.login),
  19. url(r'^auth/$', auth.AuthView.as_view({'post': 'login'})), # 认证api
  20. ]

在web目录下的views目录下,创建文件login.py

  1. from django.shortcuts import render, redirect,HttpResponse
  2. def login(request):
  3. return render(request,"login.html")

使用session

在web目录下的views目录下,创建文件auth.py

  1. from rest_framework.response import Response
  2. from rest_framework.views import APIView
  3. from rest_framework.viewsets import ViewSetMixin
  4. from rbac import models
  5. from utils.response import BaseResponse
  6. class AuthView(ViewSetMixin,APIView):
  7. authentication_classes = [] # 空列表表示不认证
  8. def login(self,request,*args,**kwargs):
  9. """
  10. 用户登陆认证
  11. :param request:
  12. :param args:
  13. :param kwargs:
  14. :return:
  15. """
  16. response = BaseResponse() # 默认状态
  17. try:
  18. user = request.data.get('username')
  19. pwd = request.data.get('password')
  20. # print(user,pwd)
  21. # 验证用户和密码
  22. obj = models.UserInfo.objects.filter(name=user,password=pwd).first()
  23. if not obj: # 判断查询结果
  24. response.code = 1002
  25. response.error = '用户名或密码错误'
  26. else:
  27. role = obj.roles.all() # 查询当前用户的所有角色
  28. permissions_list = [] # 定义空列表
  29. for i in role: # 循环角色
  30. per = i.permissions.all() # 查看当前用户所有角色的所有权限
  31. # print(i.permissions.all())
  32. for j in per:
  33. # print(j.url)
  34. # 将所有授权的url添加到列表中
  35. permissions_list.append(j.url)
  36. # print(permissions_list)
  37. response.code = 1000
  38. # 增加session
  39. request.session['url'] = permissions_list
  40. except Exception as e:
  41. response.code = 10005
  42. response.error = '操作异常'
  43. # print(response.dict)
  44. return Response(response.dict)

在web目录下的templates目录下,创建login.html

  1. {% load staticfiles %}
  2. <!DOCTYPE html>
  3. <html lang="en">
  4. <head>
  5. <meta charset="UTF-8">
  6. <title>Title</title>
  7. <script src="{% static 'js/jquery-3.3.1.min.js' %} "></script>
  8. <script src="{% static 'plugins/sweetalert/sweetalert-dev.js' %} "></script>
  9. <link rel="stylesheet" href="{% static 'plugins/sweetalert/sweetalert.css' %}">
  10. </head>
  11. <body>
  12. <div style="height: 100px;"></div>
  13. <form action="/auth/" method="post">
  14. <lable>用户名</lable>
  15. <input type="text" name="username" id="user">
  16. <lable>密码</lable>
  17. <input type="password" name="password" id="pwd">
  18. <input type="button" id="sub" value="登录">
  19. </form>
  20. <script>
  21. $("#sub").click(function () {
  22. $.ajax({
  23. url: "/auth/",
  24. type: "post",
  25. data: {
  26. username: $("#user").val(),
  27. password: $("#pwd").val(),
  28. },
  29. success: function (data) {
  30. console.log(data);
  31. if (data.code == 1000) { //判断json的状态
  32. swal({
  33. title: '登录成功',
  34. type: 'success', //展示成功的图片
  35. timer: 500, //延时500毫秒
  36. showConfirmButton: false //关闭确认框
  37. }, function () {
  38. window.location.href = "/customer/list/"; //跳转
  39. });
  40. } else {
  41. swal("登录失败!", data.error,
  42. "error");
  43. {#window.location = "/backend/add_category/";#}
  44. }
  45. },
  46. error: function (data) {
  47. console.log('登录异常');
  48. }
  49. })
  50. });
  51. </script>
  52. </body>
  53. </html>

在web目录下的templates目录下,创建error.html

  1. {% load staticfiles %}
  2. <!DOCTYPE html>
  3. <html lang="en">
  4. <head>
  5. <meta charset="UTF-8">
  6. <title>Title</title>
  7. </head>
  8. <body>
  9. <script src="{% static 'js/jquery-3.3.1.min.js' %} "></script>
  10. {#<link rel="stylesheet" href="http://mishengqiang.com/sweetalert/css/sweetalert.css">#}
  11. <link rel="stylesheet" href="{% static 'plugins/sweetalert/sweetalert.css' %}">
  12. {#<script src="https://cdn.bootcss.com/jquery/3.3.1/jquery.min.js"></script>#}
  13. <script src="{% static 'js/jquery-3.3.1.min.js' %}"></script>
  14. {#<script src="http://mishengqiang.com/sweetalert/js/sweetalert-dev.js"></script>#}
  15. <script src="{% static 'plugins/sweetalert/sweetalert-dev.js' %}"></script>
  16. <div>
  17. {#获取错误信息#}
  18. <input type="hidden" id="msg" value="{{ msg }}">
  19. <input type="hidden" id="url" value="{{ url }}">
  20. </div>
  21. <script>
  22. $(function () {
  23. var msg = $("#msg").val();
  24. var url = $("#url").val();
  25. console.log(msg);
  26. console.log(url);
  27. if (msg.length > 0) { //判断是否有错误信息
  28. swal({
  29. title: msg,
  30. text: "1秒后自动关闭。",
  31. type: 'error',
  32. timer: 1000,
  33. showConfirmButton: false
  34. }, function () {
  35. window.location.href = url; //跳转指定url
  36. });
  37. }
  38. })
  39. </script>
  40. </body>
  41. </html>

中间件验证

在web目录下创建文件middlewares.py

  1. from django.utils.deprecation import MiddlewareMixin
  2. from django.shortcuts import render, redirect, HttpResponse
  3. from luffy_permission import settings
  4. import os
  5. class AuthMD(MiddlewareMixin): # 验证登录
  6. white_list = ['/','/login/','/auth/','/admin/' ] # 白名单
  7. # black_list = ['/black/', ] # 黑名单
  8. ret = {"status": 0, 'url': '', 'msg': ''} # 默认状态
  9. def process_request(self, request): # 请求之前
  10. request_url = request.path_info # 获取请求路径
  11. # get_full_path()表示带参数的路径
  12. # print(request.path_info, request.get_full_path())
  13. # 判断请求路径不在白名单中
  14. if request_url not in self.white_list:
  15. import re
  16. per_url = request.session.get("url") # 获取用户session中的url列表
  17. # print(per_url)
  18. if per_url:
  19. for i in per_url: # 循环url列表
  20. # 使用正则匹配。其中i为正则表达式,request_url.lstrip('/')表示去除左边的'/'
  21. result = re.match(i, request_url.lstrip('/'))
  22. # print(result)
  23. if result: # 判断匹配结果
  24. print('授权通过',request_url)
  25. return None # return None表示可以继续走下面的流程
  26. # else:
  27. # print('授权不通过',request_url)
  28. # # return redirect('/login/')
  29. # 错误页面提示
  30. self.ret['msg'] = "未授权,禁止访问!"
  31. self.ret['url'] = "/login/"
  32. path = os.path.join(settings.BASE_DIR, 'web/templates/error.html'),
  33. return render(request, path, self.ret) # 渲染错误页面

在中间件中,只要return none,就会进入下一个中间件!

修改settings.py,注册中间件

  1. MIDDLEWARE = [
  2. 'django.middleware.security.SecurityMiddleware',
  3. 'django.contrib.sessions.middleware.SessionMiddleware',
  4. 'django.middleware.common.CommonMiddleware',
  5. 'django.middleware.csrf.CsrfViewMiddleware',
  6. 'django.contrib.auth.middleware.AuthenticationMiddleware',
  7. 'django.contrib.messages.middleware.MessageMiddleware',
  8. 'django.middleware.clickjacking.XFrameOptionsMiddleware',
  9. 'web.middlewares.AuthMD', # 自定义中间件AuthMD
  10. ]

测试登录,访问页面

http://127.0.0.1:8000/login/

Day107 CRM初始,权限组件之权限控制,权限系统表设计 - 图18

访问一个不存在url

Day107 CRM初始,权限组件之权限控制,权限系统表设计 - 图19

完整代码,请查看github

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