CRM(客户关系管理)
一、什么是CRM(客户关系管理)
1.客户关系管理是指企业为提高核心竞争力,利用相应的信息技术以及互联网技术协调企业与顾客间在销售、营销和服务上的交互,从而提升其管理方式,向客户提供创新式的个性化的客户交互和服务的过程。其最终目标是吸引新客户、保留老客户以及将已有客户转为忠实客户,增加市场。(百度百科)
2.客户关系管理(CRM)有三层含义:
(1)体现为新态企业管理的指导思想和理念
(2)是创新的企业管理模式和运营机制
(3)是企业管理中信息技术、软硬件系统集成的管理方法和应用解决方案的总和。(百度百科)
图片来源:https://www.zhihu.com/question/30124083
图片来源:https://www.zhihu.com/question/30124083
二、CRM项目组成
1.rbac权限组件
2.stark通用增删改查组件
3.CRM业务
三、rbac权限组件
3.1 rbac权限组件的由来
1.为什么程序需要权限控制?
- 在日常生活当中,我们每个人都扮演者自己的角色,在我们的程序当中,我们也要对登录进来的用户进行角色的划分,这样就能方便我们对不同的用户分配不同的权限,这时候我们就需要权限控制。
2.为什么我们要将这样一个程序开发成组件的形式
- 在我们开发生涯中,可能会遇到一些功能类似,逻辑结构稍有不用,架构基本相同的业务,我们就可以将频繁使用到的功能模块开发成一个可以重复使用的组件
- 开发组件的过程虽然繁琐,但是一旦开发完成,对于我们以后开发类似的业务时,只需要利用这个组件进行一些配置,就可以快速开发出我们想要的功能
3.在我们系统当中,什么是权限控制
- 说白了,权限控制就是我们对用户访问的url进行控制,我们通过代码对用户请求的url进行过滤,将用户拥有的权限给予,将用户没有的权限去掉,这样我们就能控制用户的访问行为。
3.2 rbac权限组件表结构设计
# 基于角色的权限控制
用户表:
ID Name
角色表
ID title
用户角色关系表:
ID 用户ID 角色ID
权限表:
ID Url含正则的url
角色权限关系表:
ID 角色ID 权限ID
菜单表:
ID Title菜单名称 Icon菜单图标
3.3 rbac权限系统实现流程
1. 如何实现的权限系统?
粒度控制到按钮级别的权限控制
- 用户登陆成功之后,将权限和菜单信息放入session
- 每次请求时,在中间件中做权限校验
- inclusion_tag实现的动态菜单
2. 如何实现控制到按钮的呢?
用户登陆时,用户所拥有的权限 别名==django 路由name 构造成一个字典;
在页面中写了一个 django模板的filter来进行判断是否显示;
3. 为什么要在中间件中做校验呢?
所有请求在到达视图函数之前,必须经过中间件,所以在中间件中对请求做处理比较简单;
4. 修改权限之后,如想应用最新权限
- 我们:需要重新登陆。
- 不用重新登陆,如何完成?更新涉及的所有用户的session信息
- 当用户登录前,对用户访问的url进行白名单验证,用户登录后将用户拥有的权限初始化,存入到session当中,然后通过中间键过滤掉不能访问的权限
3.4 技术点总结
- orm查询
- 去空,去除没有的权限
- 去重,去除重复的权限
- 中间件
- 利用中间能轻松对请求进行管理
- inclusion_tag
- 创建
- from django.template import Library
- register = Library()
- @register.inclusion_tag(\'rbac/menu.html\')
- def menu(request):
- pass
- 使用
- {% load \'rbac\' %}
- {{ menu request }}
- 母版
- layout.html
- {% block %}{% endblock %}
- 引入静态文件
- {% load staticfiles%}
- {% static \'\' %}
- session
- 利用session能方便的存取权限信息
- filter
- 有序字典
- Python 3.5(含)以前字典不能保证有序
- from collections import OrderedDict
- dict1 = OrderedDDict()
- settings配置
- 利用settings设置session存储的key,这样我们就不需要频繁改动
- namespace
- 在多个app中有相同的视图函数或者url时,我们可以通过设置命名空间来区分不同app下的相同的视图函数名的视图
- 构造数据结构
- permission_dict = {\'permission_name\': {\'id\': 1, \'title\': \'LLL\'...}}
- 父子结构
- menu_dict = {\'menu_id\': {\'title\': \'rrrr\', \'icon\': \'asda\', \'url\': \'asdasd\', \'children\': [menu_node]}}
- ModelForm
- 利用ModelForm能快速根据表中的字段生成我们想要的表单
- 表单的验证
- admin
- django自带的后代管理,我们可以通过admin快速管理我们的数据库
- icon爬虫
- 利用爬虫爬取fontawesome网站的图标,为我们菜单提供图标库
- make-safe
- 保证我们向前端传递一个前端需要去渲染的html格式的文本能够被浏览器识别成安全的html字符串
- 浏览器默认开启了预防xss攻击
- 下载文件
- formset
- 批量表单处理
点击查看rbac使用手册
使用rbac组件时,应用遵循以下规则:
-
清除rbac/migrations目录下所有数据库迁移记录(保留__init__.py)
-
在项目路由系统中注册rabc相关的路由信息,如:
urlpatterns = [
...
url(r\'^rbac/\', include(\'rbac.urls\',namespace=\'rbac\')),
]
-
注册app
-
让业务的用户表继承权限的UserInfo表
如:
rbac:
class UserInfo(models.Model):
"""
用户表
"""
username = 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)
class Meta:
abstract = True
crm:
from rbac.models import UserInfo as RbacUserInfo
class UserInfo(RbacUserInfo):
"""
员工表
"""
name = models.CharField(verbose_name=\'真实姓名\', max_length=16)
phone = models.CharField(verbose_name=\'手机号\', max_length=32)
gender_choices = (
(1,\'男\'),
(2,\'女\'),
)
gender = models.IntegerField(verbose_name=\'性别\',choices=gender_choices,default=1)
depart = models.ForeignKey(verbose_name=\'部门\', to="Department")
def __str__(self):
return self.name
-
数据库迁移
-
rbac提供URL
urlpatterns = [
url(r\'^menu/list/$\', permission.menu_list, name=\'menu_list\'), # rbac:menu_list
url(r\'^menu/add/$\', permission.menu_add, name=\'menu_add\'),
url(r\'^menu/edit/(?P<pk>\d+)/$\', permission.menu_edit, name=\'menu_edit\'),
url(r\'^menu/del/(?P<pk>\d+)/$\', permission.menu_del, name=\'menu_del\'),
url(r\'^permission/add/$\', permission.permission_add, name=\'permission_add\'),
url(r\'^permission/edit/(?P<pk>\d+)/$\', permission.permission_edit, name=\'permission_edit\'),
url(r\'^permission/del/(?P<pk>\d+)/$\', permission.permission_del, name=\'permission_del\'),
url(r\'^multi/permissions/$\', permission.multi_permissions, name=\'multi_permissions\'),
url(r\'^distribute/permissions/$\', permission.distribute_permissions, name=\'distribute_permissions\'),
url(r\'^role/list/$\', permission.role_list, name=\'role_list\'),
url(r\'^role/edit/(?P<pk>\d+)/$\', permission.role_edit, name=\'role_edit\'),
url(r\'^role/del/(?P<pk>\d+)/$\', permission.role_del, name=\'role_del\'),
]
- 配置文件写上用户表的类的路径
USER_MODEL_PATH = "crm.models.UserInfo"
- 录入权限信息
http://127.0.0.1:8000/rbac/menu/list/
http://127.0.0.1:8000/rbac/multi/permissions/?type=update
- 权限分配
创建角色:http://127.0.0.1:8000/rbac/role/list/
权限分配:http://127.0.0.1:8000/rbac/distribute/permissions/
- 权限验证+自动生成菜单
a.权限信息初始化
init_permission(user_obj,request)
MENU_SESSION_KEY = "u8fkksjdfkjsf"
PERMISSION_SESSION_KEY = "u8fkkfffffsjdfkjsf"
b.中间件请求进行权限校验
\'rbac.middleware.rbac.RbacMiddleware\'
PERMISSION_VALID_URL = [
\'/login/\',
\'/admin/.*\',
]
c.inclusion_tag
{% load rbac %}
{% menu request %}
{% breadcrumb request %}
d.引入css和js
<link rel="stylesheet" href="{% static \'rbac/css/rbac.css\' %} "/>
<script src="{% static \'rbac/js/rbac.js\' %} "></script>
- 按钮控制
方式一:在前端页面判断
{% load rbac %}
{% if request|has_permission:\'xxx:xxx_x_x\' %}
<a>添加</a>
{% endif %}
方式二:在stark组件中通过基类的方式实现
权限类:
from django.conf import settings
from stark.service.stark import StarkConfig
class RbacPermission(object):
def get_add_btn(self):
name = "%s:%s" %(self.site.namespace,self.get_add_url_name,)
permission_dict = self.request.session.get(settings.PERMISSION_SESSION_KEY)
if name in permission_dict:
return super().get_add_btn()
def get_list_display(self):
val = super().get_list_display()
permission_dict = self.request.session.get(settings.PERMISSION_SESSION_KEY)
edit_name = "%s:%s" %(self.site.namespace,self.get_change_url_name,)
del_name = "%s:%s" %(self.site.namespace,self.get_del_url_name,)
if edit_name not in permission_dict:
val.remove(StarkConfig.display_edit)
if del_name not in permission_dict:
val.remove(StarkConfig.display_del)
return val
配置类:
class CourseConfig(RbacPermission,StarkConfig):
list_display = [\'id\',\'name\']
site.register(models.Course,CourseConfig)
四、stark组件
4.1 简介
- stark组件能够帮助我们快速开发出快速实现数据库表的增删改查的组件。
- 组件基于django中admin源码开发
4.2 组成
4.2.1 django运行机制
# 我们新建一个名为stark(自己随便取)的app
# 在这个app下的apps.py中,只要我们写下以下代码,django每次运行之前,都会去每个APP下找名为stark的py文件并去执行它
class StarkConfig(AppConfig):
name = \'stark\'
def ready(self):
from django.utils.module_loading import autodiscover_modules
# 当程序启动时,去每个app目录下找stark.py并加载。
autodiscover_modules(\'stark\')
4.2.2 基于包导入的方式实现单例模式
- 我们在stark app下新建一个service目录来存放我们的主要功能
class StarkConfig(objects):
def __init__(self, name, age):
self.name = name
self.age = age
def func(self):
print(self.name, self.age)
class AdminSite(objects):
def __init__(self, model_class):
self.registry = {}
self.model_class = model_class
def register(self, stark_config=None):
if not stark_config:
stark_config = StarkConfig
site = AdminSite()
- 我们新建另外一个app01,然后在其目录下新建一个stark.py文件
from stark.service.stark import site
from app01 import model
site.register(model.UserInfo)
4.2.3 django路由原理
test1 urls.py
urlpatterns = [
url(r\'^index/\', views.index),
url(r\'^rbac/\', include(\'rbac.urls)),
]
test2.py
urlpatterns = [
url(r\'^login/\', views.login)
]
# 没加namespace
rbac/login
# 加namespaece
rbac:rbac/login
- include本质
([], app_name, namespace)
点击查看stark使用手册
使用stark组件需要完成一下几个步骤:
- 拷贝stark app到任何系统。
- 在目标project中注册stark app,如:
INSTALLED_APPS = [
...
\'stark.apps.StarkConfig\',
]
- 如果想要使用stark组件,则需要在目标app的根目录中创建 stark.py
- 配置路由信息
from stark.service.stark import site
urlpatterns = [
...
url(r\'^stark/\', site.urls),
]
- 接下来就可以使用stark组件进行快速增删改查,示例:
from crm import models
from stark.service.stark import site, StarkConfig
from django.utils.safestring import mark_safe
from django.conf.urls import url
from django.shortcuts import HttpResponse
from django.urls import reverse
from crm.config.class_list import ClassListConfig
class UserInfoConfig(StarkConfig):
def display_gender(self, row=None, header=False):
if header:
return \'性别\'
return row.get_gender_display()
def display_detail(self,row=None, header=False):
if header:
return \'查看详细\'
return mark_safe(\'<a href="%s">%s</a>\' %(reverse(\'stark:crm_userinfo_detail\',kwargs={\'pk\':row.id}),row.name,))
list_display = [
display_detail,
display_gender,
\'phone\',
\'email\',
\'depart\',
StarkConfig.display_edit,
StarkConfig.display_del
]
def extra_url(self):
info = self.model_class._meta.app_label, self.model_class._meta.model_name
urlpatterns = [
url(r\'^(?P<pk>\d+)/detail/$\', self.wrapper(self.detail_view), name=\'%s_%s_detail\' % info),
]
return urlpatterns
def detail_view(self,request,pk):
"""
查看详细页面
:param request:
:param pk:
:return:
"""
return HttpResponse(\'详细页面...\')
search_list = [\'name\',\'depart__title\']
site.register(models.UserInfo, UserInfoConfig)
site.register(models.UserInfo, UserInfoConfig,prev=\'pri\')
- 组件内部扩展:
list_display
get_list_display
action_list
order_by
model_form_class
....