基于二进制控制系统权限

二进制是什么?

在数学和数字电路中指以2为基数的记数系统,以2为基数代表系统是二进位制的。这一系统中,通常用两个不同的数字0和1来表示。在计算机中,最常用的是二进制,因为组成计算机系统的逻辑电路通常只有开和关这两个状态,用0和1很好表示这两种状态。

二进制的使用

二进制可以控制系统的权限,说的简单点,即通过二进制0/1的位置来控制权限,因为某个功能要么有权限,要么没有权限

例如有一个场景:用户登录某个客户端(平板、手机、网页),要通过客户端的来源判定他是否有访问权限,我们可以通过二进制的权限位置,从最后一位往前(0位、1位、2位),设置3种权限:

可以访问平板:00000001
可以访问手机:00000010
可以访问网页:00000100

假设我们配置用户user1,可以通过平板和网页访问,即用二进制表示为00000101,用十进制表示为:5

当用户使用手机登录时,获取应用的权限00000010(一般每个应用调用登录接口都会携带一个appKey,这个appKey绑定了它所属的权限,根据appKey可查到对应的权限),十进制为:2,那怎么判定用户是否可以登录到当前应用呢?

通过&运算,功能权限 & 用户拥有的权限 == 功能权限?
即:2 & 5,转换为二进制:

  00000010
& 00000101
-------------
  00000000

&运算,只有两个位都是1,对应位才是1,
只有当2&5 == 2时,表示当前用户有手机登录权限。

二进制存储的优点

我们设计一个权限系统,一般都会基于RBAC模型,即用户-角色-权限,表设计如下:
基于二进制控制系统权限

常规做法,用户通过一个角色关联多个权限,数据存储如下:
基于二进制控制系统权限

这仅仅是一个角色,如果一个系统需要关联的角色很多,那么这张表将会存储大量的数据

通过使用二进存储可以大大减少数据量的存储,我们知道JAVA中Long类型可以转换为64位二进制,

00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000

如果把每一个位置来表示一个权限,除去最左边的符号位以外,可以表示63种权限,即一个角色最多可以关联63种权限,如果一个角色关联了63种权限,那么我们只需要插入一条数据即可,但在复杂的系统中,63种权限肯定是不够的,可以通过设计一个权限空间来进一步区分,比如最初权限空间为0可以表示63种,当超过63种权限,权限空间递增,然后再可以表示63种权限,所以我们对之前的角色、权限表进行改造:
基于二进制控制系统权限

角色-权限新增/删除

当用户在界面上新增,或删除权限,我们又该如何更新二进制权限的数据?

增加权限

比如有一个角色,对应的权限空间为0,权限二进制是00001111,需要添加一个,权限空间为0,二进制为:00100000,二进制位置为5,使用二进制的“|”运算符,规则如下:

原二进制权限 | 要添加的二进制权限

通过二进制位得到要添加的二进制权限

1 << 5 = 00100000
再和00001111 进行或运算

  00100000
| 00001111
-----------------
  00101111

00101111即为最新的权限

删除

还是以上面新增为例,00101111需要删除添加的权限00100000。
原则:

原二进制权限 & (~要删除的二进制权限)
也就是:
00101111 & (~00100000)
~00100000 表示取反,得到11011111,然后和原二进制做&操作

  11011111
& 00001111
-----------------
  00001111

判断用户是否具有权限
判断用户权限同上面用户通过客户端访问的例子,原则:

功能权限 & 用户拥有的功能权限 == 功能权限

通过角色查询权限

数据库表都存储的是二进制数据,我们如何通过一个角色查询到它所有的权限呢?
先上SQL:

SELECT
*
FROM
 角色表 r
LEFT JOIN 角色-权限关联表 rp ON r.角色ID = rp.角色ID
LEFT JOIN 权限表 p ON p.权限空间 = rp.权限空间
WHERE
CONV(rp.权限二进制,2,10) >> p.权限二进制位 & 1 = 1
and r.角色名称 = ?;

重点介绍CONV函数:

CONV(N,from_base,to_base) 表示转换进制, N是列名或值, from_base是从什么进制,to_base是转到什么进制
CONV(rp.权限二进制,2,10) 就是从二进制的01串变成十进制的数

比如:权限二进制为00001111,通过CONV转换为十进制得到16
有一条权限数据为:00001000,他的二进制权限位置为3,将 16 >> 3 得到 00000001,即将所在的权限位置移动到了最后一位,只要和1做 & 操作,得到的结果如果是1,那么表示包含了这条权限数据

根据权限查角色

SELECT
*
FROM
权限表 p
LEFT JOIN 角色-权限关联表 rp ON p.权限空间 = rp.权限空间
LEFT JOIN 角色表 r ON r.角色Id = rp.角色Id
WHERE
CONV(rp.权限二进制,2,10) >> p.权限二进制位 & 1 = 1
and p.权限名称 = ?

通过二进制存储数据的好处,大大降低了数据量,原来63条数据可以只用一条数据就可以代替,进行权限判断时,原来需要从几十甚至上百的数据一条一条判断,现在仅仅需要进行位运算就可以了,位运算一般比前面的逻辑高效的多。
但也有缺点,数据库存储的都是二进制,可读性降低了,如果项目交接给其它同事,还得对于权限空间和权限位置这些字段含义详细介绍,并且查询使用到了各种函数转换,这些字段的索引也失效了
总结:一般对于用户权限功能个数确定,比如一个供应商有几十万门店,用户、角色、权限数量不多,可以考虑使用二进制来控制权限提升效率、减少存储数据量。

本文来源于公众号《百川分享会》:baichuanshare

基于二进制控制系统权限

上一篇:高性能日记--show profile剖析sql语句


下一篇:分布式事务seata的AT模式介绍