权限设计小技巧 - 位运算

我们在Linux系统中,最常见的文件权限,就是124了吧,777是不是看的很多,哈哈哈哈

那么如果在一个系统中,权限是区域固定的,是否也可以用这种模式呢

其实原理就是采用了二进制的位运算,通过1、2、4就能表示7以内的所有数字,通过十进制会发现如果要添加一个权限,是要思考很久的,但是转换为

那几个位运算

https://www.php.net/manual/zh/language.operators.bitwise.php

这里我们暂时只会用到

例子 名称 结果
$a & $b And(按位与) 将把 $a 和 $b 中都为 1 的位设为 1。
$a | $b Or(按位或) 将把 $a 和 $b 中任何一个为 1 的位设为 1。
$a << $b Shift left(左移) 将 $a 中的位向左移动 $b 次(每一次移动都表示“乘以 2”)。

tips:这部分的计算,应该大家都会吧,不聊了,哈哈哈哈

二进制的“与(&)”和“或( | )”操作的简单解释。

&操作

1
2
3
4
5
6
      A= 10001001
B= 10010000
A&B结果是 10000000
A= 10001001
C= 10001000
A&C结果是 10001000

| 操作

1
2
3
4
5
6
7
      A= 10001001
B= 10010000
A|B结果是 10011001

A= 10001001
C= 10001000
A|C结果是 10001001

所以,我们可以这样利用二进制控制权限

  1. 我们可以利用二进制的 “位” 来控制权限,一个 “位” 代表一个权限,位上为1代表用该权限,为0代表没有这个权限
  2. 然后利用二进制的 “或( | )”来给角色添加权限,利用二进制的 “与(&)” 操作来验证是否拥有某个权限。

示例

如我们现在有3个权限(就用3位的二进制,左侧补0),先把二进制和十进制都表示出来

权限名称 二进制 十进制
权限1 001 1
权限2 010 2
权限3 100 4

写一个验证脚本.php看看

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
<?php

$list = [
0b001=>'权限1',
0b010=>'权限2',
0b100=>'权限3',
];

// 添加权限
$b1 = 0b0001 | 0b0010;
var_dump(dec2bin($b1)); // string(3) "011"

// 检测权限
var_dump(dec2bin($b1 & 0b001)); // string(3) "001"
var_dump(dec2bin($b1 & 0b010)); // string(3) "010"
var_dump(dec2bin($b1 & 0b100)); // string(3) "000"

function dec2bin(int $num, $digit = 3): string
{
return sprintf("%0{$digit}s", decbin($num));
}

可以看到

  • 通过“或( | )”来设置权限
  • 通过 “与(&)”来验证权限,如果是0的话,代表无权限

思考一下

扩展权限,完善功能

如果现在有个权限十进制,如何反推有什么权限呢

如现在的权限数字是6,那么如何得到有什么权限呢

  1. 先转换为二进制,就是110
  2. 在一开始设计权限的时候,从右到左就是权限1、权限2、权限3
  3. 那么这个110,就代表具有的权限有权限3、权限2

如果现在增加一个权限,那么十进制的数字是多少呢

可以看到,我们用二进制的位来控制权限,那么增加1位就好了,也就是1000,换成十进制就是8,easy

权限名称 二进制 十进制
权限1 0001 1
权限2 0010 2
权限3 0100 4
权限4 1000 8

或许

这个我觉得可以不一定只是用作权限设计,也可以用到平时的状态上面,如一个打卡系统,状态如下,其中正常的场景直接用0来表示,那么位只会用到6位

使用左移来初始化权限数字,方便很多

1
2
3
4
5
6
7
8
9
10
11
12
13
<?php
$list = [
'迟到', '早退', '缺上班卡', '缺下班卡', '地点异常', '设备异常'
];

$data = [];
$start = 1;
foreach ($list as $value) {
$data[$start] = $value;
$start = $start << 1;
}

var_dump($data);
状态 二进制 十进制
正常 000000 0
迟到 000001 1
早退 000010 2
缺上班卡 000100 4
缺下班卡 001000 8
地点异常 010000 16
设备异常 100000 32

比如可以用12=4+8,来标识缺上班卡并且缺下班卡,就可以代表旷工

获取具有的权限进行展示

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
<?php

$list = [
'迟到', '早退', '缺上班卡', '缺下班卡', '地点异常', '设备异常'
];

// 状态值设计
$data = [];
$start = 1;
foreach ($list as $value) {
$data[$start] = $value;
$start = $start << 1;
}

// 现在有个权限是28,获取它的权限返回
$num = 28;
$digit = count($list);
$numBin = dec2bin($num, $digit);
$hasAuth = [];
foreach (array_reverse(str_split($numBin)) as $index => $has) {
if ($has) {
$authority = $list[$index] ?? '';
$key = array_search($authority, $data);
if ($key) {
$hasAuth[$key] = $authority;
}
}
}

echo $num .' 具有的权限有:'. implode('|', $hasAuth); // 28 具有的权限有:缺上班卡|缺下班卡|地点异常

function dec2bin(int $num, $digit = 6): string
{
return sprintf("%0{$digit}s", decbin($num));
}

可能还有更多用法

// todo