需求讨论

权限设计

代码设计

自定义权限钩子 

 

业务场景分析


假设我们在开发一个培训机构的 客户关系管理系统,系统分客户管理、学员管理、教学管理3个大模块,每个模块大体功能如下

客户管理
销售人员可以录入客户信息,对客户进行跟踪,为客户办理报名手续
销售人员可以修改自己录入的客户信息
客户信息不能删除
销售主管可以查看销售报表


学员管理 
学员可以在线报名 
学员可以查看自己的报名合同、学习有效期
学员可以在线提交作业 、查看自己的成绩

教学管理
管理员可以创建新课程、班级
讲师可以创建上课纪录
讲师可以在线点名、批作业


从上面的需求中, 我们至少提取出了5个角色,普通销售、销售主管、学员、讲师、管理员, 他们能做的事情都是不一样的


如何设计一套权限组件来实现对上面各种不同功能进行有效的权限控制呢?我们肯定不能LOW到为每个动作都一堆代码来控制权限对吧? 这些表面上看着各种不尽相同的功能,肯定是可以提取出一些相同的规律的,仔细分析,其实每个功能本质上都是一个个的动作,如果能把动作再抽象中具体权限条目,然后把这些权限条目 再跟用户关联,每个用户进行这个动作,就检查他没有这个权限,不就实现权限的控制了么?由于这个系统是基于WEB的B/S架构,我们可以把每个动作的构成 提取成以下的元素

一个动作 = 一条权限 = 一个url + 一种请求方法(get/post/put...) + 若干个请求参数

那我们接下来需要做的,就是把 一条条的权限条目定义出来,然后跟用户关联上就可以了!

 

开发中需要的权限定义

什么是权限?

权限 就是对 软件系统 中 各种资源 的 访问和操作的控制!

什么是资源?

在软件系统中,数据库、内存、硬盘里数据都是资源,资源就是数据!

 

动作

资源本身是静态的, 必须通过合适的动作对其进行访问和操作,我们说要控制权限,其实本质上是要对访问 软件中各种数据资源的动作进行控制 

动作又可以分为2种:

资源操作动作:访问和操作各种数据资源,比如访问数据库或文件里的数据

业务逻辑事件动作:访问和操作的目的不是数据源本身,而是借助数据源而产生的一系列业务逻辑,比如批量往远程 主机上上传一个文件,你需要从数据库中访问主机列表,但你真正要操作的是远程的主机,这个远程的主机,严格意义上来并不是你的数据资源,而是这个资源代表的实体。

 

权限授权

  • 权限的使用者可以是具体的个人、亦可以是其它程序, 这都没关系,我们可以把权限的授权主体,统称为用户, 无论这个用户后面是具体的人,还是一个程序,对权限控制组件来讲,都不影响 。
  • 权限必然是需要分组的,把一组权限 分成一个组,授权给特定的一些用户,分出来的这个组,就可以称为角色。
  • 权限 应该是可以叠加的!

 

 

权限组件的设计与代码实现

我们把权限组件的实现分3步,权限条目的定义, 权限条目与用户的关联,权限组件与应用的结合

 

权限条目的定义

我们前面讲过以下概念, 现在需要做的,就是把我们系统中所有的需要控制的权限 所对应的动作 提取成 一条条 url+请求方法+参数的集合就可以

一个动作 = 一条权限 = 一个url + 一种请求方法(get/post/put...) + 若干个请求参数

 

以下是提取出来的几条权限

perm_dic={
 
    'crm_table_index':['table_index','GET',[],{},],  #可以查看CRM APP里所有数据库表
    'crm_table_list':['table_list','GET',[],{}],    #可以查看每张表里所有的数据
    'crm_table_list_view':['table_change','GET',[],{}],#可以访问表里每条数据的修改页
    'crm_table_list_change':['table_change','POST',[],{}], #可以对表里的每条数据进行修改
 
    }

字典里的key是权限名, 一会我们需要用过这些权限名来跟用户进行关联

  • 后面values列表里第一个值如'table_index'是django中的url name,在这里必须相对的url name, 而不是绝对url路径,因为考虑到django url正则匹配的问题,搞绝对路径,不好控制。 
  • values里第2个值是http请求方法
  • values里第3个[]是要求这个请求中必须带有某些参数,但不限定对数的值是什么
  • values里的第4个{}是要求这个请求中必须带有某些参数,并且限定所带的参数必须等于特定的值

 

有的同学看了上面的几条权限定义后,提出疑问,说你这个权限的控制好像还是粗粒度的, 比如我想控制用户只能访问 客户 表里的 一条或多条特定的用户怎么办?

哈,这个问题很好,但很容易解决呀,只需要在[] or {}里指定参数就可呀,比如要求http请求参数中必须包括指定的参数,举个例子, 我的客户表如下:

class Customer(models.Model):
    '''存储所有客户信息'''
    #客户在咨询时,多是通过qq,所以这里就把qq号做为唯一标记客户的值,不能重复
    qq = models.CharField(max_length=64,unique=True,help_text=u'QQ号必须唯一')
    qq_name = models.CharField(u'QQ名称',max_length=64,blank=True,null=True)
    #客户只要没报名,你没理由要求人家必须告诉你真实姓名及其它更多私人信息呀
    name = models.CharField(u'姓名',max_length=32,blank=True,null=True)
    sex_type = (('male',u''),('female',u''))
    sex = models.CharField(u"性别",choices=sex_type,default='male',max_length=32)
    birthday = models.DateField(u'出生日期',max_length=64,blank=True,null=True,help_text="格式yyyy-mm-dd")
    phone = models.BigIntegerField(u'手机号',blank=True,null=True)
    email = models.EmailField(u'常用邮箱',blank=True,null=True)
    id_num = models.CharField(u'身份证号',blank=True,null=True,max_length=64)
    source_type = (('qq',u"qq群"),
                   ('referral',u"内部转介绍"),
                   ('website',u"官方网站"),
                   ('baidu_ads',u"百度广告"),
                   ('qq_class',u"腾讯课堂"),
                   ('school_propaganda',u"高校宣讲"),
                   ('51cto',u"51cto"),
                   ('others',u"其它"),
                   )
    #这个客户来源渠道是为了以后统计各渠道的客户量\成单量,先分类出来
    source = models.CharField(u'客户来源',max_length=64, choices=source_type,default='qq')
    #我们的很多新客户都是老学员转介绍来了,如果是转介绍的,就在这里纪录是谁介绍的他,前提这个介绍人必须是我们的老学员噢,要不然系统里找不到
    referral_from = models.ForeignKey('self',verbose_name=u"转介绍自学员",help_text=u"若此客户是转介绍自内部学员,请在此处选择内部\学员姓名",blank=True,null=True,related_name="internal_referral")
    #已开设的课程单独搞了张表,客户想咨询哪个课程,直接在这里关联就可以
    course = models.ForeignKey("Course",verbose_name=u"咨询课程")
    class_type_choices = (('online', u'网络班'),
                          ('offline_weekend', u'面授班(周末)',),
                          ('offline_fulltime', u'面授班(脱产)',),
                          )
    class_type = models.CharField(u"班级类型",max_length=64,choices=class_type_choices)
    customer_note = models.TextField(u"客户咨询内容详情",help_text=u"客户咨询的大概情况,客户个人信息备注等...")
    work_status_choices = (('employed','在职'),('unemployed','无业'))
    work_status = models.CharField(u"职业状态",choices=work_status_choices,max_length=32,default='employed')
    company = models.CharField(u"目前就职公司",max_length=64,blank=True,null=True)
    salary = models.CharField(u"当前薪资",max_length=64,blank=True,null=True)
    status_choices = (('signed',u"已报名"),('unregistered',u"未报名"))
    status = models.CharField(u"状态",choices=status_choices,max_length=64,default=u"unregistered",help_text=u"选择客户此时的状态")
    #课程顾问很得要噢,每个招生老师录入自己的客户
    consultant = models.ForeignKey("UserProfile",verbose_name=u"课程顾问")
    date = models.DateField(u"咨询日期",auto_now_add=True)

    def __str__(self):
        return u"QQ:%s -- Name:%s" %(self.qq,self.name)

Customer表
Customer表

相关文章: