小知识,大挑战!本文正在参与“程序员必备小知识”创作活动。
欢迎来到掘金,这里是小小切图仔_可笑可笑😁😁。
我们接着上一篇的内容继续往下讲,如果想看来龙去脉的可以去回顾上一篇“用户权限还能这么玩,颠覆你的认知(一)”
还记得我们上一篇说到的三个议题吗?
- javaScript的进制位运算
- 权限的判断
- 用后感
一· 进制的位运算
1.2. 位运算
JavaScript 中位运算操作符有
| 运算符 | 用法 | 描述 |
|---|---|---|
| 按位与(AND) | a & b | 对于每一个比特位,只有两个操作数相应的比特位都是 1 时,结果才为 1,否则为 0。 |
| 按位或(OR) | a | b | 对于每一个比特位,当两个操作数相应的比特位至少有一个 1 时,结果为 1,否则为 0。 |
| 按位异或(XOR) | a ^ b | 对于每一个比特位,当两个操作数相应的比特位有且只有一个 1 时,结果为 1,否则为 0。 |
| 按位非(NOT) | ~a | 反转操作数的比特位,即 0 变成 1,1 变成 0。 |
| 左移(Left shift) | a << b | 将 a 的二进制形式向左移 b (< 32) 比特位,右边用 0 填充。 |
| 有符号右移 | a >> b | 将 a 的二进制表示向右移 b (< 32) 位,丢弃被移出的位。 |
| 无符号右移 | a >>> b | 将 a 的二进制表示向右移 b (< 32) 位,丢弃被移出的位,并使用 0 在左侧填充。 |
上一篇我们只讲到了按位异或(^),接下来我们会把剩下的讲完
1.2.5 有符号左移(<<)
有符号左移会将32位二进制数的所有位向左移动指定位数。如:
var num = 2; // 二进制10
num = num << 5; // 二进制1000000,十进制64
如果要求2的n次方,可以这样:
function power(n) {
return 1 << n;
}
power(5); // 32
1的二进制是01,左移5位就是0100000,十进制就是2的5次方32。
1.2.6 有符号右移(>>)
有符号右移会将32位二进制数的所有位向右移动指定位数。如:
var num = 64; // 二进制1000000
num = num >> 5; // 二进制10,十进制2
求一个数的二分之一:
var num = 64 >> 1; // 32
有符号左移与右移不会影响符号位。
1.2.7 无符号右移(>>>)
正数的无符号右移与有符号右移结果是一样的。负数的无符号右移会把符号位也一起移动,而且无符号右移会把负数的二进制码当成正数的二进制码:
var num = -64; // 11111111111111111111111111000000
num = num >>> 5; // 134217726
所以,我们可以利用无符号右移来判断一个数的正负:
function isPos(n) {
return (n === (n >>> 0)) ? true : false;
}
isPos(-1); // false
isPos(1); // true
-1>>>0虽然没有向右移动位数,但-1的二进制码已经变成了正数的二进制码:
11111111111111111111111111111111
所以-1>>>0的值为4294967295。
上面就是我们 JavaScript 的进制位运算了,感兴趣的小伙伴可以继续深入脑补这些知识,我们进制的位运算就先讲到这里,下面我们就会讲到我们第二个议题
二. 权限的判断
我们传统的权限系统里,对权限判断都是用关联关系去维护的,后台要把数据存在很多在表中,存很多相关的字段,比如用户和权限的关联,用户和角色的关联。系统越大,关联关系越多,越难以维护。而引入位运算,可以巧妙的解决该问题。
在讲“位运算在权限系统中的使用”之前,我们先假定两个前提,下文所有的讨论都是基于这两个前提的:
每种权限码都是唯一的(这是显然的)
所有权限码的二进制数形式,有且只有一位值为 1,其余全部为 0(2^n)
如果用户权限和权限码,全部使用二级制数字表示,再结合上面位运算的例子,分析位运算的特点,我们可以发现:
| 可以用来赋予权限
& 可以用来校验权限
为了讲的更明白,这里用 Linux 中的实例分析下,Linux 的文件权限分为读、写和执行,有字母和数字等多种表现形式:
| 权限 | 字母表示 | 数字表示 | 二进制 |
|---|---|---|---|
| 读 | r | 4 | 0b100 |
| 写 | w | 2 | 0b010 |
| 执行 | x | 1 | 0b001 |
可以看到,权限用 1、2、4(也就是 2^n)表示,转换为二进制后,都是只有一位是 1,其余为 0。我们通过几个例子看下,如何利用二进制的特点执行权限的添加,校验和删除。
2.1 添加权限
先与后台确定好权限列表,也就是每一种权限对应一个唯一的权限码
我们是使用了16进制定义,不管是16进制还是二进制都可以,包括菜单、按钮、数据呀,如下:
| 权限英文表示 | 权限的16进制码 | 权限的中文名 |
|---|---|---|
| HighRisk | 0x00001 | 高级风控管理 |
| MiddleRisk | 0x00002 | 中级风控管理 |
| LowRisk | 0x00004 | 低级风控管理 |
定义权限列表,就是新增权限了
let permission = {
HighRisk: 0x00001,
MiddleRisk: 0x00002,
LowRisk: 0x00004
}
// 给用户赋全部权限(使用前面讲的 | 操作)
let user = permission.HighRisk | permission.HighRisk | permission.LowRisk
console.log(user)
// 5
console.log(user.toString(2))
// 101
利用 | 位运算之后,可以直接把预算完之后的结果保存到后台,或者在转一次二进制给后台,这都是你自己跟后台的约定
2.2 校验权限
添加完权限之后,我们就来校验权限:
let permission = {
HighRisk: 0x00001,
MiddleRisk: 0x00002,
LowRisk: 0x00004
}
// 给用户赋 HighRisk MiddleRisk 两个权限
let user = permission.HighRisk | permission.MiddleRisk
// user = 3
// user = 0x00003 (十六进制)
console.log((user & permission.HighRisk) === permission.HighRisk) // true 有 HighRisk 权限
console.log((user & permission.MiddleRisk) === permission.MiddleRisk) // true 有 MiddleRisk 权限
console.log((user & permission.LowRisk) === permission.LowRisk) // false 没有 LowRisk 权限
如前所料,通过 用户权限 & 权限 code === 权限 code 就可以判断出用户是否拥有该权限。
2.3 删除权限
我们讲了用 | 赋予权限,使用 & 判断权限,那么删除权限呢?删除权限的本质其实是将指定位置上的 1 重置为 0。上个例子里用户权限是 0x00003,拥有HighRisk和MiddleRisk两个权限,现在想删除MiddleRisk的权限,本质上就是变成 0x00002即可。
那么具体怎么操作呢?其实有两种方案,最简单的就是异或 ^,按照上文的介绍“当两个操作数相应的比特位有且只有一个 1 时,结果为 1,否则为 0”,所以异或其实是 toggle 操作,无则增,有则减:
let permission = {
HighRisk: 0x00001,
MiddleRisk: 0x00002,
LowRisk: 0x00004
}
let user = 0x00003 1. // 有 HighRisk MiddleRisk 两个权限
// 执行异或操作,删除 MiddleRisk 权限
user = user ^ permission.MiddleRisk
console.log((user & permission.HighRisk) === permission.HighRisk) // true 有 HighRisk 权限
console.log((user & permission.MiddleRisk) === permission.MiddleRisk) // false 没有 MiddleRisk 权限
console.log((user & permission.LowRisk) === permission.LowRisk) // false 没有 LowRisk 权限
console.log(user.toString(16)) // 现在 user 是 0x00002
// 再执行一次异或操作
user = user ^ permission.MiddleRisk
console.log((user & permission.HighRisk) === permission.HighRisk) // true 有 HighRisk 权限
console.log((user & permission.MiddleRisk) === permission.MiddleRisk) // true 有 MiddleRisk 权限
console.log((user & permission.LowRisk) === permission.LowRisk) // false 没有 LowRisk 权限
console.log(user.toString(16)) // 现在 user 又变回 0x00003
那么如果单纯的想删除权限(而不是无则增,有则减)怎么办呢?答案是执行 &(~code),先取反,再执行与操作:
let permission = {
HighRisk: 0x00001,
MiddleRisk: 0x00002,
LowRisk: 0x00004
}
let user = 0x00003 1. // 有 HighRisk MiddleRisk 两个权限
// 执行异或操作,删除 MiddleRisk 权限
user = user & (~permission.MiddleRisk)
console.log((user & permission.HighRisk) === permission.HighRisk) // true 有 HighRisk 权限
console.log((user & permission.MiddleRisk) === permission.MiddleRisk) // false 没有 MiddleRisk 权限
console.log((user & permission.LowRisk) === permission.LowRisk) // false 没有 LowRisk 权限
console.log(user.toString(16)) // 现在 user 是 0x00002
// 再执行一次异或操作
user = user & (~permission.MiddleRisk)
console.log((user & permission.HighRisk) === permission.HighRisk) // true 有 HighRisk 权限
console.log((user & permission.MiddleRisk) === permission.MiddleRisk) // true 有 MiddleRisk 权限
console.log((user & permission.LowRisk) === permission.LowRisk) // false 没有 LowRisk 权限
console.log(user.toString(16)) // 现在 user 还是 0x00002
以上就是我使用进制位运算来做权限判断的实际逻辑,大家可以试一下!!
三· 用后感
如果按照当前使用最广泛的 RBAC 模型设计权限系统,那么一般会有这么几个实体:应用,权限,角色,用户。用户权限可以直接来自权限,也可以来自角色:
一个应用下有多个权限
权限和角色是多对多的关系
用户和角色是多对多的关系
用户和权限是多对多的关系
在此种模型下,一般会有用户与权限,用户与角色,角色与权限的对应关系表。想象一个商城后台权限管理系统,可能会有上万,甚至十几万店铺(应用),每个店铺可能会有数十个用户,角色,权限。随着业务的不断发展,刚才提到的那三张对应关系表会越来越大,越来越难以维护。
而进制转换的方法则可以省略对应关系表,减少查询,节省空间。当然,省略掉对应关系不是没有坏处的,例如下面几个问题:
如何高效的查找我的权限?
如何高效的查找拥有某权限的所有用户?
如何控制权限的有效期?
所以进制转换的方案比较适合刚才提到的应用极其多,而每个应用中用户,权限,角色数量较少的场景。
建议大家可以尝试用这种方法,毕竟能提高更好的效率,何乐而不为呢。
创作不易,请多多支持,点点赞、点点关注与收藏,就是对我最大的支持,感谢观看我的文章,这里是小小切图仔_可笑可笑