一.简单回顾一下js中的位运算:
1. "&" :与运算,转化为二进制数,如果相同位数都为1则得结果为1,否则为0;
2. "|" :或运算,转化为二进制数,如果相同位数只要有一个为1则得结果为1,否则为0
3. "^" :异或运算,转化为二进制数,如果相同位数不同则得结果为1,否则为0
4. "<<" 异位运算符,1<<1,表示将1左移一位,也就是010,在二进制中代表2;
5.十进制转为别的进制:
var num = 123;
console.log(num.toString(2))
console.log(num.toString(8))//参数传想要转化的位数
别的进制转化为十进制:
var num = 110;
console.log(parseInt(num,2))//二进制转化为十进制
6.看几个简单例子
# 例子1
A = 10001001
B = 10010000
A & B = 10000000
# 例子2
A = 10001001
C = 10001000
A & C = 10001000
二.将以上操作运用于权限设计中:
假设目前系统每个租户都有一个各自的授权码,每种授权码而且是唯一的,结合上面位运算操作可进一步设计
- | (或运算)用来赋予权限
- & (与运算)用来校验权限
- ^ (异或运算)用来删除权限
用户RWX权限(读/写/执行)举例:
1.添加权限
let r = 0b100
let w = 0b010
let x = 0b001
// 给用户赋全部权限(使用前面讲的 | 操作)
let user = r | w | x
// 打印日志看下结果
console.log(user) // 7 对应二进制0b111
0b111后三位都是1,表示拥有了三个权限都拥有。
2.校验权限
// 给用户赋 r w 两个权限
let user = r | w; // user = 6 对应二进制0b110
# 通过 用户权限 & 权限 code === 权限 code 就可以判断出用户是否拥有该权限
// 打印日志看下结果
console.log((user & r) === r) // true 有 r 权限
console.log((user & w) === w) // true 有 w 权限
console.log((user & x) === x) // false 没有 x 权限。
3.删除权限
let r = 0b100
let w = 0b010
let x = 0b001
let user = 0b110 // 有 r w 两个权限
// 执行异或操作,删除 r 权限
user = user ^ r
// 打印日志看下结果
console.log((user & r) === r) // false 没有 r 权限
console.log((user & w) === w) // true 有 w 权限
console.log((user & x) === x) // false 没有 x 权限
console.log(user.toString(2)) // 现在 user 是 0b010
// 再执行一次异或操作 user = user ^ r
// 打印日志看下结果
console.log((user & r) === r) // true 有 r 权限
console.log((user & w) === w) // true 有 w 权限
console.log((user & x) === x) // false 没有 x 权限
console.log(user.toString(2)) // 现在 user 又变回 0b110
如果单纯的想删除权限(而不是无则增,有则减)怎么办呢?
答案是执行 &(~code),先取反,再执行与操作:
let r = 0b100
let w = 0b010
let x = 0b001
let user = 0b110 // 有 r w 两个权限
// 删除 r 权限
user = user & (~r)
// 打印日志看下结果
console.log((user & r) === r) // false 没有 r 权限
console.log((user & w) === w) // true 有 w 权限
console.log((user & x) === x) // false 没有 x 权限
console.log(user.toString(2)) // 现在 user 是 0b010
// 再执行一次
user = user & (~r)
// 再次打印日志看下结果
console.log((user & r) === r) // false 没有 r 权限
console.log((user & w) === w) // true 有 w 权限
console.log((user & x) === x) // false 没有 x 权限
console.log(user.toString(2)) // 现在 user 还是 0b010,并不会新增
三.局限性和解决办法
上述的所有都有前提条件:
1、每种权限码都是唯一的;
2、每个权限码的二进制数形式,有且只有一位值为 1(2^n)
也就是说,权限码只能是 1, 2, 4, 8,...,1024,...而上文提到,一个数字的范围只能在 -(2^53 -1) 和 2^53 -1 之间,JavaScript 的按位操作符又是将其操作数当作 32 位比特序列的。
那么同一个应用下可用的权限数就非常有限了。
这也是该方案的局限性。
四.权限空间
基于权限空间,我们定义两个格式:
1.权限 code
形如 index,pos。其中 pos 表示 32 位二进制数中 1 的位置(其余全是 0); index 表示权限空间,用于突破 JavaScript 数字位数的限制,是从 0 开始的正整数,每个权限code都要归属于一个权限空间。index 和 pos 使用英文逗号隔开。
2.用户权限
字符串,形如 1,16,16。英文逗号分隔每一个权限空间的权限值。例如 1,16,16 的意思就是,权限空间 0 的权限值是 1,权限空间 1 的权限值是 16,权限空间 2 的权限是 16。
引入权限空间的概念突破二进制运算的位数限制。
五.在实际项目的应用
1.定义权限码
// 销售中心
export const Sell_Permission_Data = {
SELL_INTENTION_INDEX: {
value: "3,0",
label: "意向用户列表"
},
SELL_INTENTION_ADD: {
value: "3,1",
label: "添加意向用户"
},
SELL_INTENTION_INFO: {
value: "3,2",
label: "添加意向查看"
},
SELL_INTENTION_FOLLOW: {
value: "3,3",
label: "添加意向跟进"
},
SELL_INTENTION_SIGN: {
value: "3,4",
label: "添加意向报名"
},
SELL_INTENTION_EDIT: {
value: "3,5",
label: "添加意向用户"
},
SELL_INTENTION_DELETE: {
value: "3,6",
label: "添加意向用户"
}
}
2.权限涉及操作
import { PermissionItem } from './permission.class';
import { UserService } from '../../@core/user/user.service';
export class PermissionService {
// 权限 code 即用户在本系统的权限码
public static userCode: string = "";
private permissions: any = {};
get userService(): UserService {
return this.injector.get(UserService);
}
// 获取用户权限码
public getUserPermissionCode() {
if (PermissionService.userCode) {
return PermissionService.userCode;
}
const user = this.userService.read() || {};
const code = user.code || '';
if (!PermissionService.userCode) PermissionService.userCode = code;
return code;
}
// 赋权
public addPermission(permission: PermissionItem): void {
const userPermission: any = PermissionService.userCode ? PermissionService.userCode.split(",") : [];
const [index, pos] = permission.value.split(",");
const _index: number = parseInt(index);
const _pos: number = parseInt(pos);
userPermission[_index] = (userPermission[_index] || 0) | Math.pow(2, _pos)
PermissionService.userCode = userPermission.join(",");
}
// 删权
public delPermission(permission: PermissionItem): void {
const userPermission: any = PermissionService.userCode ? PermissionService.userCode.split(",") : [];
const [index, pos] = permission.value.split(",");
const _index: number = parseInt(index);
const _pos: number = parseInt(pos);
userPermission[_index] = (userPermission[_index] || 0) & (~Math.pow(2, _pos))
PermissionService.userCode = userPermission.join(",");
}
// 鉴权
public hasPermission(permission: PermissionItem): boolean {
const userPermission: any = PermissionService.userCode ? PermissionService.userCode.split(",") : [];
const [index, pos] = permission.value.split(",");
const _index: number = parseInt(index);
const _pos: number = parseInt(pos);
const permissionValue = Math.pow(2, _pos)
return (userPermission[_index] & permissionValue) === permissionValue;
}
// 用户已有权限列表
public listPermission(): Array<string> {
const results: Array<string> = [];
if (!PermissionService.userCode) return results;
Object.values(this.permissions).forEach((permission: any) => {
this.hasPermission(permission) && results.push(permission.label);
});
return results;
}
// 已拥有权限权值
public valuePermission(): Array<string> {
const results: Array<string> = [];
if (!PermissionService.userCode) return results;
Object.values(this.permissions).forEach((permission: any) => {
this.hasPermission(permission) && results.push(permission.value);
});
return results;
}
}
六.结合实际场景应用
前端的权限控制可以减少很大程度上的后端压力,一个复杂的系统如果每个模块的权限设计都采用“用户-角色-权限-角色权限-用户角色”的方式去设计,单是权限模块将会非常复杂,
而且这类控制型操作权限请求频率又高,长期以往系统将会更加复杂且难维护,进制转换不但节省大量空间又减少了请求查询。在我们的项目中如果存在很多交叉状态的控制,可以考虑此方案的可行性,
并结合后端权限模型可以实现更多丰富的可用操作。