二进制运算在扩展组合属性中的使用
时间:2009-05-07
来源:互联网
注意:这里的格式可能比较乱,请大家下载PDF手册阅读!
关键词 : 扩展属性 扩展会员组 扩展角色 二进制运算 检索
这里的所说的扩展属性, 最常见就是以下几种情况:
1.某个会员不但属于一个主会员组, 而且从属多个扩展会员组, 如何以更好的方式保存某个会员的扩展组,并可以非常方便地查阅出某个扩展组下面的所有用户?或很方便地检索某个用户是否属于某扩展会员组?
2.基于角色的权限控制系统(RBAC)很是流行。系统中有很多角色,但是某个用户拥有多个角色,以何种格式保存用户的角色信息?并可以更方便地检索出某个角色下的所有用户,或判断用户是否属于某个角色?
3.一篇文章属于某个主栏目,但同时又属于其它多个栏目(扩展栏目),如何保存其扩展栏目信息? 在检索文章时,很容易地检索出属于某个栏目的文章?(扩展栏目)
4.在CMS系统中,往往存在推荐位,一篇文章往往属于多个推荐位,如何保存这些每个文章的推荐位信息?并在非常方便地检索出某个推荐位下的所有文章?
以上的问题,都是笔者在开发系统中遇到过的问题.
一 . 在这种一对多的关系中,数据库字段一般的处理方法都是将这些信息保存以逗号分隔(或其它符号相隔)的字符串,如第一个问题,最常见普通的数据库结构可能是这样的:
----会员分组表user_group----
groupid name
1 管理员
2 注册会员
3 VIP会员
4 收费会员
5 金牌会员
6 企业会员
-----会员表 user_list ---
userid username groupid extgroup
1 lee 5 3,4,6
2 sandy 6 2,3,4
简单解释一下,lee主会员组是5,即金牌会员,但他同时属于组3,4,5,6 ,我们用逗号分隔的组ID保存了其扩展组信息; 同理sandy的主会员组是6,但他同时又属于组2,3,4,5 , 我们也用逗号分隔的组ID保存了他的扩展组信息;
但现在存在一个检索的问题
1 .如何检索出所有收费会员(属于组4)? 看看上面的表,给我们检索带来问题,我们无法从上面的表中直接检索出这个信息!
2 .假设组ID是X, 如何判断lee是否属于组X ? 这个比上面简单些,先取出lee的groupid和extgroup,进行合并,得到了3,4,5,6这样的字符串,如果使用PHP程序,你可能需要以下代码片断($X表示需要判断的组的ID):
$groupids = '3,4,5,6' ; //这是从用户lee 的组信息,经过数据库查询取得
if(in_array($x,explode(',',$groupids))) {
//用户属于组$x
}
//不要被上面的代码迷惑了,简化一下表示为:
$groupids = '3,4,5,6' ;
$groupids = explode(',',$groupids);
if(in_array($x,$groupids)) {
//用户属于组$x
}
如果你看不懂上面的代码,建议你先学一下相关知识.
好,现在新的问题产生了! 我如何取得所有收费会员(属于会员组4)列表?看看,从上面的表中,因为扩展组信息不可直接查询,所以我们无法直接得到这些信息,很难受,是吧?你可能想到遍历所有会员记录,对一个有成千上万会员的表来说,这样做的效率可想而知!
所以,针对这个问题,我们重新设计user_list表的extgroup字段,让其成为int/bigint类型,其保存的值如下:
----会员表 user_list ---
userid username groupid extgroup extgroupids
1 lee 5 88 3,4,6
2 sandy 6 28 2,3,4
大家可能迷惑了,88和28是怎么来的?
88 == 23|24|26
28 == 22|23|24
乘幂并进行二进制位的或运算,写成php代码:
88 == pow(2,3)|pow(2,4)|pow(2,6)
28 == pow(2,2)|pow(2,3)|pow(2,4)
如果写成更直接的位运算符:
88 == (2<<2)|(2<<3)|(2<<5)
28 == (2<<1)|(2<<2)|(2<<3)
记住了,某个数左移一位,相当于它的值乘以2, 左移两位,相当于乘以4,两种方法都是殊途同归,曲径通幽的.
当然用逗号分隔的组ID信息,还是有用的,因为我们无法通过88很容易得到3,4,6这样的结果,所以为了修改用户信息,我们还是需要保存一份的.
现在,如何取得所有属于组4的用户呢?非常好办了:
select * from user_list where 4&(pow(2,groupid)) or (extgroup!=0 and 4&(extgroup)) ;
或者简化一下:
select * from user_list where 4&(pow(2,groupid)
不要迷惑了,因为group是十进制,我们需要将其进行二进制运算,才能计算出正确的二进制的或结果,后面的4&(extgroup)就是检查扩展组了,现在,觉得是不是很方便,so easy .
这样,我们就很方便地查阅到了某个会员组(包含扩展组)下的所有会员了.
现在假设会员M的主会员组是 $X, 扩展会员组是$Y(已经通过或运算得到的结果,非逗号分隔的字符串), 我们需要检查M是否属于组$Z(会员组ID为$Z) ,如何做?
if(pow(2,$Z)&(pow(2,$X)|($Y))) {
//会员属于组$Z
}
因为在这里$X, $Z都是原始的整数(没有通过二进制运算),所以需要进行二进制转化运算.
如果深入到基本原理,可以这样认为,一个32bit的整数,共有32个二进制位,那么每一个二进制位都可以用来表示一组信息,因为系统中不可能有重复组ID,故我们进行pow(2,$x)运算,得到一个二进制位的点位符,不会造成二进制位数据覆盖, 说简单一些就是:
pow(2,$x)!=pow(2,$y) 当且仅当$x!=$y,这里的$x, $y相当于会员组ID号,它们不可能有重复.
所以,如果使用inti类型,就是说某个用户可以属于多达32个组:pow(2,32),这在一般系统中已经完全够用,不必担心. 如果不够,请使用long int类型吧.
二 . 回到我们的RBAC(基于角色的权限控制)
在类似的系统中,我们往往会记录某个模块所能操作的角色ID, 我们当然记录角色ID的位运算结果,作为主要的检索条件,可能你的权限会这样设计表:
--- 权限表 user_priv----
id module role roleids
1 report 11 1,2,8
2 print 8 8
如果不明白11和8是怎么来的,请看第一节的说明,已经很详细了,此处不再赘述.
假设当前用户角色ID为8,16,那么可以这样查询出当前用户可以操作的模块:
<?php
$_role = pow(2,8)|pow(2,16);
$sql = "select id,module from user_priv where {$_role}&role";
//查询当前用户是否有权限操作模块$module
$sql = "select id,module from user_priv where module='$module' and {$_role}&role";
?>
不要惊讶,权限检测现在就是这么简单快速!
三 . 回到文章属于多个栏目的问题,在这种系统中,你必然要设计一个栏目(有的也叫分类)表(至于是否无限检分类,与这里的关系不大),如下结构:
-- 文章栏目表 category--
catid name
1 财经
2 法制
3 人物专访
4 环境
5 人文
6 旅游
-- 文章表article --
id catid title content extcatid catids
12 2 法制文章标题 内容 0
13 3 ... ... 3 1,2
14 2 ... ... 48 4,5,6
id为4的文章,主栏目是2,但同时属于栏目4,5,6 ,48==pow(2,4)|pow(2,5)|pow(2,6)
那么,现在要检索属于栏目4的所有文章(包含扩展栏目):
"select * from article where catid=4 or (extcatid!=0 and pow(2,4)&(extcatid))" ;
两部分,一个是检测主栏目,后台是检测扩展栏目,使用extcatid!=0进行了约束,否则,查询出的结果就会有问题了.
或使用下面的方式:
"select * from article where pow(2,4)&(pow(2,catid)|extcatid)" ;
将主栏目ID与扩展栏目先合并,再计算.
看看,多简单. phpcms2008的方法是建立了一个新的表,用来处理这个问题,其实,它如果使用这个方法,会减少不少的表呢.
说到这里,就会有一个问题了,我刚才说到的32位,如果您的栏目很多,多达几十个呢?这时pow(2,50)==1.1258999068426e+015, 是一个非常大的数了,bigint最大值大约是184千亿亿,宽度是20位
大约栏目ID为67的时候,已经是极限,那么用这种方式, 这无疑就是一个限制了.
但另一方面,如果67个栏目还不够用, 你应该考虑一下,是否你的系统架构存在问题?
是否需要建立分站?或是子系统?
四 . 推荐位的问题.每个文章都可能属于多个推荐位,每个推荐位都有一个唯一的数字ID , 这与上面的问题在本质上完全一样的,不过同时将文章所属于的各个推荐位ID进行2的升幂运算后,再或运算得到一个结果,保存下来就可以了.
记住一个原则:扩展属性使用pow(2,val)|pow(2,val) 运算后保存,查询时使用
where pow(2,value) & extvalue 来查询 .
如果您的理解或使用此种方式中有什么疑惑,请联系我,欢迎一起探讨,笔者的信箱是[email protected]
月 影 2009-5-7 于北京

二进制在扩展属性中的使用.pdf (163.7 KB)
作者: xuer 发布时间: 2009-05-07
作者: laopi 发布时间: 2009-05-20
热门阅读
-
office 2019专业增强版最新2021版激活秘钥/序列号/激活码推荐 附激活工具
阅读:74
-
如何安装mysql8.0
阅读:31
-
Word快速设置标题样式步骤详解
阅读:28
-
20+道必知必会的Vue面试题(附答案解析)
阅读:37
-
HTML如何制作表单
阅读:22
-
百词斩可以改天数吗?当然可以,4个步骤轻松修改天数!
阅读:31
-
ET文件格式和XLS格式文件之间如何转化?
阅读:24
-
react和vue的区别及优缺点是什么
阅读:121
-
支付宝人脸识别如何关闭?
阅读:21
-
腾讯微云怎么修改照片或视频备份路径?
阅读:28