一、布尔代数
布尔代数相信大家都接触过,根据维基百科的定义:
The basic operations of Boolean algebra are the following ones:
- And (conjunction), denotedx∧y (sometimesx ANDy or Kxy), satisfiesx∧y = 1 ifx = y = 1 and x∧y = 0 otherwise.
- Or (disjunction), denotedx∨y (sometimesx ORy or Axy), satisfiesx∨y = 0 if x = y = 0 and x∨y = 1 otherwise.
- Not (negation), denoted ¬x (sometimes NOTx, Nx or !x), satisfies ¬x = 0 ifx = 1 and ¬x = 1 ifx = 0.
图片表示这几个运算的关系如下:
二、布尔代数的应用——位运算
平时常用的逻辑二元运算(&&,||)是用布尔代数的最常见应用之一,还有就是这里要介绍的位运算:
位运算就是二进制数值按照位运算符&(与),|(或),~(取反),^(异或),>>(左移),<<(右移)对二进制数值进行运算。比如,7的二进制是111,11的二进制是1011,那么7&11的结果就是二进制的11,即 3 ;7|11结果就是二进制的1111,即 15 ;1<<2 就是 二进制的100 ,即 4。
而位运算也可以解决权限问题,如Linux系统中的文件权限设定,文件或目录的权限分为三种:”可读”、”可写”和”可执行”,分别用 1 、2 、4 来表示。有“可读”且“可写”权限就用 1+2,即3来表示。
设原有权限为a,在a的基础上增加写权限可用运算a|2来实现,如1|2=3;类似的,减少写权限可以用 a&(~2)来实现,如3&(~2)=1。
位运算的运算对象是二进制的位,速度很快,效率很高,而且节省空间,位运算做权限控制又相当地灵活。对与我日常使用的java来说,用Long类型来实现,可以实现64种权限的控制,如果嫌不够多,还可以用java.math.BigInteger来实现。
三、位运算权限管理的简单实现
1.需求目前有 列表、只读、浏览、编辑、创建、共享这五个权限。可以用java的枚举实现对这几个权限的设定(AuthorityType):
public enum AuthorityType {
EDIT(1L<<0), //编辑
READONLY(1L<<1), //只读
READ(1L<<2), //浏览
LIST(1L<<3), //列表
CREATE(1L<<4), //创建
PSHARE(1L<<5); //共享
private Long type;
private AuthorityType(Long type){
this.type=type;
}
public Long getType() {
return type;
}
public static Long getAllType() {
Long l = 0L;
for(AuthorityType a : AuthorityType.values()){
l+=a.getType();
}
return l;
}
}
2.权限判断、设定、取消的代码实现(DocAuthority):
public class DocAuthority {
/*当前具有的权限*/
private Long authority;
private DocAuthority docauth;
/*判断是否有编辑权限*/
public boolean isEdit() {
return (authority & AuthorityType.EDIT.getType()) >= AuthorityType.EDIT.getType();
}
/*设定编辑权限*/
public DocAuthority setEdit(DocAuthority docauth) {
this.authority = authority|AuthorityType.EDIT.getType();
docauth.authority = authority;
return this.docauth = docauth;
}
/*取消编辑权限*/
public DocAuthority removeEdit(DocAuthority docauth) {
this.authority = authority&(~AuthorityType.EDIT.getType());
docauth.authority = authority;
return this.docauth = docauth;
}
/*判断是否有只读权限*/
public boolean isReadOnly() {
return (authority & AuthorityType.READONLY.getType()) >= AuthorityType.READONLY.getType();
}
/*设定只读权限*/
public DocAuthority setReadOnly(DocAuthority docauth) {
this.authority = authority|AuthorityType.READONLY.getType();
docauth.authority = authority;
return this.docauth = docauth;
}
/*取消只读权限*/
public DocAuthority removeReadOnly(DocAuthority docauth) {
this.authority = authority&(~AuthorityType.READONLY.getType());
docauth.authority = authority;
return this.docauth = docauth;
}
/*判断是否有浏览权限*/
public boolean isRead() {
return (authority & AuthorityType.READ.getType()) >= AuthorityType.READ.getType();
}
/*设定浏览权限*/
public DocAuthority setRead(DocAuthority docauth) {
this.authority = authority|AuthorityType.READ.getType();
docauth.authority = authority;
return this.docauth = docauth;
}
/*取消浏览权限*/
public DocAuthority removeRead(DocAuthority docauth) {
this.authority = authority&(~AuthorityType.READ.getType());
docauth.authority = authority;
return this.docauth = docauth;
}
/*判断是否有列表权限*/
public boolean isList() {
return (authority & AuthorityType.LIST.getType()) >= AuthorityType.LIST.getType();
}
/*设定列表权限*/
public DocAuthority setList(DocAuthority docauth) {
this.authority = authority|AuthorityType.LIST.getType();
docauth.authority = authority;
return this.docauth = docauth;
}
/*取消列表权限*/
public DocAuthority removeList(DocAuthority docauth) {
this.authority = authority&(~AuthorityType.LIST.getType());
docauth.authority = authority;
return this.docauth = docauth;
}
/*判断是否有创建权限*/
public boolean isCreate() {
return (authority & AuthorityType.CREATE.getType()) >= AuthorityType.CREATE.getType();
}
/*设定创建权限*/
public DocAuthority setCreate(DocAuthority docauth) {
this.authority = authority|AuthorityType.CREATE.getType();
docauth.authority = authority;
return this.docauth = docauth;
}
/*取消创建权限*/
public DocAuthority removeCreate(DocAuthority docauth) {
this.authority = authority&(~AuthorityType.CREATE.getType());
docauth.authority = authority;
return this.docauth = docauth;
}
/*判断是否有共享权限*/
public boolean isPShare() {
return (authority & AuthorityType.PSHARE.getType()) >= AuthorityType.PSHARE.getType();
}
/*设定共享权限*/
public DocAuthority setPShare(DocAuthority docauth) {
this.authority = authority|AuthorityType.PSHARE.getType();
docauth.authority = authority;
return this.docauth = docauth;
}
/*取消共享权限*/
public DocAuthority removePShare(DocAuthority docauth) {
this.authority = authority&(~AuthorityType.PSHARE.getType());
docauth.authority = authority;
return this.docauth = docauth;
}
/*判断是否有某些权限*/
public boolean isSomeAuth(Long l) {
return (authority & l) >= l;
}
/**设定某些权限
* @param DocAuthority
* @param Long a number less than Long.MAX_VALUE,but bigger than 0
* */
public DocAuthority setAuth(DocAuthority docauth,Long l) {
if((1<<63)-1>=l){
this.authority = authority|l;
docauth.authority = authority;
}
return this.docauth = docauth;
}
/**取消某些权限
* @param DocAuthority
* @param Long a number less than Long.MAX_VALUE,but bigger than 0
* */
public DocAuthority removeAuth(DocAuthority docauth,Long l) {
if(Long.MAX_VALUE>l){
this.authority = authority&(~l);
docauth.authority = authority;
}
return this.docauth = docauth;
}
public DocAuthority getDocauth() {
return docauth;
}
public long getAuthority() {
return authority;
}
public String toString(){
return authority.toString();
}
}
3.对取出数据的权限进行增删操作(改也是由增删权限组成的):
DocAuthority d = new DocAuthority();
//设定编辑权限
d.setEdit(d);
print(d);
//取消编辑权限
d.removeEdit(d);
System.out.println(d.getAuthority());
//设定只读和编辑权限
d.setEdit(d).setReadOnly(d);
print(d);
//设定列表权限
d.setList(d);
System.out.println(d.getAuthority());
//取消只读和编辑权限
d.removeEdit(d).removeReadOnly(d);
print(d);
//设定全部权限
d.setAuth(d, AuthorityType.EDIT.getType() + AuthorityType.CREATE.getType() + AuthorityType.READ.getType() + AuthorityType.READONLY.getType() + AuthorityType.LIST.getType() + AuthorityType.PSHARE.getType());
System.out.println(d.getAuthority());
//取消全部权限
d.removeAuth(d, AuthorityType.getAllType());
print(d);
//设定全部权限
d.setAuth(d, AuthorityType.getAllType());
System.out.println(d.getAuthority());
//是否具有某些权限,如编辑和创建权限
d.isSomeAuth(AuthorityType.EDIT.getType() + AuthorityType.CREATE.getType());
System.out.println(d.isSomeAuth(AuthorityType.EDIT.getType() + AuthorityType.CREATE.getType()));
四、位运算权限管理进阶
1. 第三节实现了权限的增删改,而查询一般都是查数据库,目前常见的数据库都是支持位运算的(orace,mysql,sqlserver,postgresql等)。但是Hibernate却偏偏不支持位运算。那我们要如何达到自己的目的呢?
常见的方式是利用Hibernate支持的自定义SQLFunction,定义一个bitand(a,b)的SQLFunction,最后配置自定义方言的Dialect并配置到hibernate配置文件。以mysql为例(参考:https://forum.hibernate.org/viewtopic.php?t=940978)
1).实现一个自定义的SQLFunction:
public class BitAndFunction implements SQLFunction {
public Type getReturnType(Type type, Mapping mapping) {
return Hibernate.INTEGER;
}
public boolean hasArguments() {
return true;
}
public boolean hasParenthesesIfNoArguments() {
return true;
}
public String render(List args, SessionFactoryImplementor factory)
throws QueryException {
if (args.size() != 2) {
throw new IllegalArgumentException(
"BitAndFunction requires 2 arguments!");
}
return args.get(0).toString() + " & " + args.get(1).toString();
}
}
2).创建自己的方言 BitwiseAndSqlDialect:
public class BitwiseAndSqlDialect extends MySQLInnoDBDialect {
public BitwiseAndSqlDialect() {
super();
registerFunction("bitand", new BitAndFunction());
}
}
3).注册自己的方言:
hibernate.dialect=dbtest.sql.BitwiseAndSqlDialect
4)创建一个简单的po:
public class DocAclPo implements Serializable {
private static final long serialVersionUID = 1L;
private Long id;
private Long numId;
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public Long getNumId() {
return numId;
}
public void setNumId(Long numId) {
this.numId = numId;
}
}
5).设置一个简单的查询:
try {
tx = session.beginTransaction(); // 开始一个事务
//bitand
//数据库查询 比如:具有列表和创建权限(对应8和16)的数据
Long authEdit = AuthorityType.LIST.getType();
Long authCreate = AuthorityType.CREATE.getType();
Query query = session.createQuery("from DocAclPo as c where bitand(c.numId,"+(complexauth)+")>="+complexauth+" order by c.id");
List<DocAclPo> list = query1.list();
for(int i=0 ; i<list.size(); i++){
System.out.println("DocAclPo.id is "+ list.get(i).getId()+" , DocAclPo.numId is "+list.get(i).getNumId());
}
tx.commit(); // 提交事务
结果为:
Hibernate: select docaclpo0_.id as id0_, docaclpo0_.num_id as num2_0_ from doc_acl docaclpo0_ where docaclpo0_.num_id & 24>=24 order by docaclpo0_.id
DocAclPo.id is 1 , DocAclPo.numId is 63
DocAclPo.id is 8 , DocAclPo.numId is 24
测试数据库中所有记录为:
1 63
2 19
3 20
4 18
5 21
6 22
7 23
8 24
9 32
6).缺点:
这么做可以满足需求,但是总体来说变化较大,因为使用了自己定义的数据库方言和函数,如果系统不止应用在常用的数据库(oracle.mysql,sqlserver,postgresql等),而是用在不支持位运算的数据库中,或者应用支持的数据库较多,要写N多种方言,再或者项目要求不能用自己的方言,那就不好处理了。
下面提出一种新的方案——使用HQL支持的MOD方法来操作。来先看例子,再看原理。
2.MOD方案
1).创建一个DocAuthUtil拼装查询字符串:
public class DocAuthUtil {
/*权限字符串拼接方法AND*/
public static String setSearchAnd(String authority , AuthorityType ... authType){
StringBuffer sb= new StringBuffer();
for (AuthorityType a : authType){
Long at = a.getType();
sb.append("MOD("+ authority + "," +(at<<1)+")>="+at+" and ");
}
return "("+sb.substring(0, sb.length()-5)+")";
}
/*权限字符串拼接方法OR*/
public static String setSearchOr(String authority , AuthorityType ... authType){
StringBuffer sb= new StringBuffer();
for (AuthorityType a : authType){
Long at = a.getType();
sb.append("MOD("+ authority + "," +(at<<1)+")>="+at+" or ");
}
return "("+sb.substring(0, sb.length()-4)+")";
}
}
2).设定测试查询语句:
tx = session.beginTransaction(); // 查询具有列表和创建权限的数据
String qString ="from DocAclPo as c where 1=1 and"+DocAuthUtil.setSearchAnd("c.numId", AuthorityType.LIST,AuthorityType.CREATE)+" order by c.id";
Query query1 = session.createQuery(qString);
List<DocAclPo> list = query.list();
for(int i=0 ; i<list.size(); i++){
System.out.println("DocAclPo.id is "+ list.get(i).getId()+" , DocAclPo.numId is "+list.get(i).getNumId());
}
tx.commit(); // 提交事务
结果为:
Hibernate: select docaclpo0_.id as id0_, docaclpo0_.num_id as num2_0_ from doc_acl docaclpo0_ where 1=1 and mod(docaclpo0_.num_id, 16)>=8 and mod(docaclpo0_.num_id, 32)>=16 order by docaclpo0_.id
DocAclPo.id is 1 , DocAclPo.numId is 63
DocAclPo.id is 8 , DocAclPo.numId is 24
3).解释:
根据util中的设定,查询的权限值都是AuthorityType中的枚举,而AuthorityType的值都是2的整次幂,如1、2、4、8、16、32。假设实际具有的权限auth中数值最大的一个为32,如果查询权限32+16+4=52中是否有8 ,就可以用52÷(8×2)的余数是否大于等于8判断即可。因为auth的最大值为32+16+8+4+2+1总值为 2^6-1=63<32×2,16+8+4+2+1=2^5-1=31<16×2,所以at<<1 再取模后,必然小于等于at,且只当包含at时才有等于成立。因此算法中用了 "MOD("+ authority + "," +(at<<1)+")>="+at这种简单算法。
4).优点:
杜绝了方案1中的自定义函数和更改默认方言的不便;使用Hibernate中的预置函数MOD,可以使用数据库中对应字段的索引,不需要进行多数据库方言的开发等。
五、总结
1.布尔代数非常简单明了,但是却可以用来做一些很有用的事情,比如逻辑判断,比如索引,比如这里的位运算判定权限。
2.在特定的情况下,解决问题的方式不止一种,就如这里的MOD方式,在一定程度上可以替换位运算。知识是重要,但结合实际运用知识更重要。