最近在做权限验证的时候又重新过了一边位运算的普通应用。发现这些应用的概念因为平时用得比较少所以很容易忘记,而且其中的思考比较难拾起来。这里把位运算的应用转换为我们自己平时可以理解的语言再结合运算过程对比下以加深下记忆。
以下为位运算符的逻辑概念:
---------------------'&'位与运算------------------
二进制值 十进制值
0000000000001111 15 变量1
0000000000000101 5 变量2
0000000000000101 5 运算结果
运算说明:当两个变量相同位的值都为1运算结果对应位置的值才为1其余情况都为0
--------------------- '|'位或运算------------------
二进制值 十进制值
0000000000001111 15 变量1
0000000000000101 5 变量2
0000000000001111 5 运算结果
运算说明:当两个变量相同位的值其中有一个为1则结果对应位置的值就为1其余情况都为0
--------------------- '^'位异或运算------------------
二进制值 十进制值
0000000000001111 15 变量1
0000000000000101 5 变量2
0000000000001010 10 运算结果
运算说明:当两个变量相同位的值不一样时结果对应位置的值为1其余都为0
--------------------- '~'位取反运算------------------
二进制值 十进制值
0000000000001101 13 变量1
1111111111110010 -14 运算结果
运算说明:将变量中所有位的值取反(包括首位的符号位,符号位1表示负数0表示正数)
--------------------- '<< 2'位取反运算------------------
二进制值 十进制值
0000000000000001 1 变量1
0000000000000100 4 运算结果
运算说明: <<2将当前变量所有位向左移动2位右边用0补全
--------------------- '>> 2'位取反运算------------------
二进制值 十进制值
0000000000000100 4 变量1
0000000000000001 1 运算结果
运算说明: >>2将当前变量所有位的值向右移动2位左边用0补全
位运算的应用逻辑:
按照上面的运算逻辑因为二进制中每一位只有0跟1两个值所以可以把每一位的值看做一个开关来使用。只要需求中有非此则彼的状态时都可以使用这种运算方式来保存信息以达到用一个十进制的数字来保存N种不同分类的状态值。
以下以一个增删改查的数据库权限验证实例举例:
-------------增删改查权限-------------
预设权限有:增、删、改、查
权限 二进制值 十进制值
增 1000 8
删 0100 4
改 0010 2
查 0001 1
思考过程:因为有四种不同的状态分别为增、删、改、查所以定义一个有4位的二进制值,
转化为十进制时的值分别就为8、4、2、1附带发现每个值最后都是2的0-3次方。
当假设有三个用户他们的权限值如下:
-------------用户权限-------------
权限范围 二进制值 十进制值
超管 增删改查 1111 15
用户A 增删 1100 12
用户B 改查 0011 3
思考过程:每个用户都有自己特定的权限范围针对每种不同操作的权限在对应的位中的值为1表示允许操作。
神奇的发现不同组合的权限值转化为十进制值的话其实就相当于1、2、4、8这四个值的相加。如12就是8+4=增+删这两种权限
当用户A想执行新增操作及查询操作时计算如下:
-------------用户A权限校验过程-------------
权限值计算使用'&'位与操作
二进制值 十进制值
1000 8 要进行操作的权限值(新增权限)
1100 12 用户权限值
1000 8 计算结果
为什么使用逻辑与运算的思考过程:因为要执行的权限值除了对应位数的值外其余位数值都为0。
就是说以<<要进行操作的权限值 1000>>作为参考对象来讲,除了第四位外其余位数都为0,
就是说任何与之进行'&'运算的二进制数只有第四位才会有可能为1。所以当(1000 & 用户权限值)
这个公式的运算结果等于1000这个二进制值本身时就说明这个用户的权限值允许进行对应的操作。
反之如果不等于1000这个二进制值的话就没有权限操作。这时可能有人会问为什么一定的等于1000才可以呢?
大于0000不也说明了可以吗?比如说有个用户的权限值十进制是24转化成二进制是11000,
与新增权限的1000也是对应的。第四位的结果也是1这不就说明他有权限了吗?但是我们预设的前提是只定义了
增删改查这四种权限哪里来的第五位的值呢?如果这个用户的权限值严格按照当前权限定义来分配的话是不会出
现11000这个权限值的,所以这个用户的权限值很明显是非法的。对于业务上来讲应该尽可能的符合设计要求的
数据才值得信赖所以才会说要等于1000这个操作值才算有权限进行新增操作
-------------为用户B添加权限-------------
权限值计算使用'|'位或操作
二进制值 十进制值
1000 8 要添加的权限值(新增权限)
0011 3 用户权限值
1011 11 计算结果
为什么使用逻辑或运算的思考过程:因为要执行的权限值除了对应位数的值外其余位数值都为0。
就是说以<<要进行操作的权限值 1000>>作为参考对象来讲,被运算数(用户权限值0011)除了第四位的值会被修改为1或者本身就是1保留不变外其余的用户权限值的位数都不会改变。那么问题又来了,为什么不直接把用户权限值的十进制数+8不就好啦,为什么要进行二进制下的位运算?那么如果因为网络原因或者误操作等原
因造成了连续多次向我们的程序发出指令。让代码给用户连续多次添加了新增权限,那么如果不用位运算的话
就会造成将用户的权限值连续自增了多个8。这样就造成了权限混乱,有可能连原来的权限都没了的情况。就是
上面的为什么会有11000这种超过了权限位数的权限值的情况发生。所以位运算还变相的解决了误操作跟并发操作的情况。
-------------为用户A移除权限-------------
权限值计算使用'~'位取反或'^'位异或 及'&'位与操作
二进制值 十进制值
0010 2 要移除的权限值(修改权限)
1101 13 要移除的权限值(0010)与全部权限值(1111)取异或后的结果
(即除了修改权限外的总权限值)
0011 3 用户权限值
0001 11 计算结果
思考过程:要将用户权限值指定位数的值改为0,其余位数的值不变,这是我们想要的最终结果。那么想要
达到这个效果就是要有一个参考值,这个参考值就是除了指定位数的值为0外其余的值都为1。将这个参考值
再与用户的权限值进行逻辑与操作,这样就能达到用户权限值所有位数除了指定位数被修改为0或本身就是0
保留不变外,其他位数的值都会保留原有的值不变了。那么这个参考值怎么计算出来呢?就是我们发现这个
参考值就是要移除的权限值0010取反(即:~ 要移除的权限值 这样的表达式)或者除了指定权限位数外的总权限值(即:全部权限 ^ 要移除的权限 这样的表达式)
以上即为位运算在应用中的简单逻辑思考。针对以上实例自己编写了一个类来实现以上功能:
class Permission{
/*权限值:新增*/
const ROLE_ADD = 8;
/*权限值:删除*/
const ROLE_DELETE = 4;
/*权限值:更新*/
const ROLE_UPDATE = 2;
/*权限值:查找*/
const ROLE_SELECT = 1;
/*不同权限的位移*/
static $position = [
self::ROLE_ADD => 3,
self::ROLE_DELETE => 2,
self::ROLE_UPDATE => 1,
self::ROLE_SELECT => 0,
];
/**
* 检查权限
* @param int $userRole 用户当前权限值
* @param self::ROLE_ADD | self::ROLE_DELETE | self::ROLE_UPDATE | self::ROLE_SELECT | $role_type 要检查的用户权限
* @return bool 是否有权限
*/
public static function checkRole($userRole,$role_type){
decbin(1 << self::$position[$role_type]);
return ($userRole & (1 << self::$position[$role_type])) == $role_type;
}
/**
* 给用户添加权限
* @param int $userRole 用户当前权限值
* @param self::ROLE_ADD | self::ROLE_DELETE | self::ROLE_UPDATE | self::ROLE_SELECT | $role_type 要添加的用户权限
* @return int 添加后的用户新权限值
*/
public static function addRole($userRole,$role_type){
return $userRole | $role_type;
}
/**
* 移除用户权限
* @param int $userRole 用户当前权限值
* @param self::ROLE_ADD | self::ROLE_DELETE | self::ROLE_UPDATE | self::ROLE_SELECT | $role_type 要添加的用户权限
* @return int 移除后的用户新权限值
*/
public static function dropRole($userRole,$role_type){
$total_role = self::ROLE_ADD | self::ROLE_DELETE | self::ROLE_UPDATE | self::ROLE_SELECT;
return ($total_role ^ $role_type) & $userRole;
}
}
$user_role = Permission::ROLE_ADD | Permission::ROLE_UPDATE | Permission::ROLE_SELECT;
var_dump('---------------current role add 8 update 2 select 1----------------');
var_dump($user_role);
var_dump('---------------checkRole----------------');
var_dump(Permission::checkRole($user_role,Permission::ROLE_ADD));
var_dump(Permission::checkRole($user_role,Permission::ROLE_DELETE));
var_dump(Permission::checkRole($user_role,Permission::ROLE_UPDATE));
var_dump(Permission::checkRole($user_role,Permission::ROLE_SELECT));
var_dump('---------------addRole----------------');
var_dump(Permission::addRole($user_role,Permission::ROLE_ADD));
var_dump(Permission::addRole($user_role,Permission::ROLE_DELETE));
var_dump(Permission::addRole($user_role,Permission::ROLE_UPDATE));
var_dump(Permission::addRole($user_role,Permission::ROLE_SELECT));
var_dump('---------------dropRole----------------');
var_dump(Permission::dropRole($user_role,Permission::ROLE_ADD));
var_dump(Permission::dropRole($user_role,Permission::ROLE_DELETE));
var_dump(Permission::dropRole($user_role,Permission::ROLE_UPDATE));
var_dump(Permission::dropRole($user_role,Permission::ROLE_SELECT));
打印结果如下
string(67) "---------------current role add 8 update 2 select 1----------------"
int(11)
string(40) "---------------checkRole----------------"
bool(true)
bool(false)
bool(true)
bool(true)
string(38) "---------------addRole----------------"
int(11)
int(15)
int(11)
int(11)
string(39) "---------------dropRole----------------"
int(3)
int(11)
int(9)
int(10)
以上我一些小心得,记录下来备忘。