【TypeScript 学习】TypeScript 枚举范例发散:基于位运算的权限管理 CRUD
TypeScript 枚举范例发散:基于位运算的权限管理 CRUD 操作1 问题由来
TypeScript 枚举范例常用于束缚一组固定的选项(如星期、月份)、表示几种可选的状态(如用户角色、订单状态)、或者替换一些难写的取值(如常用颜色的十六进制值等)。在这些应用场景中,有一类场景在开辟后台管理系统时尤为常见——用户权限管理。假设有三种权限:可读(Readable)、可写(Writable)、可执行(Executable),则可以用 TypeScript 枚举范例表示为:
enum Permission = {
Readable = 0b001, // i.e. 2^0 = 1
Writable = 0b010, // i.e. 2^1 = 2
Executable = 0b100, // i.e. 2^2 = 4
}
于是可以类比 Lunix 中的权限管理,团结位运算实现权限的基本操作(增、删、改、查,即 CRUD)。
2 具体实现
准备工作就是上面定义好的 TS 枚举范例 Permission
2.1 新增权限
新增可通过 位或(|) 运算实现:
/**
* Add a permission to the permission set
*
* @param perms original permission set
* @param permToAdd permission to add
* @returns the new permission set
*/
function addPermission(perms: Permission, permToAdd: Permission): Permission {
return perms | permToAdd;
}
相当于求并集。
2.2 删除权限
既然有新增,自然就有删除。通常有两种写法:
[*]位与(&) + 位非(~):p = p1 & ~p2;
[*]位异或(^):p = p1 ^ p2。
我觉得第二个更简洁,于是实现为:
/**
* Delete a permission from the permission set
*
* @param perms original permission set
* @param permToDel permission to delete
* @returns the new permission set
*/
function delPermission(perms: Permission, permToDel: Permission): Permission {
return perms ^ permToDel;
}
2.3 查询权限(即判断存在与否)
所谓查询,就是判断当前权限集合 perms 是否包含某个权限(或权限组合)permTarget。这可以通过 位与 运算实现:
/**
* Check if the permission set has the target permission
*
* @param perms original permission set
* @param permTarget target permission to check
* @returns true if the permission set has the target permission; otherwise false
*/
function hasPermission(perms: Permission, permTarget: Permission): boolean {
return (perms & permTarget) === permTarget;
}
2.4 修改权限
实际工作中,权限的修改无非是将原来的权限组合 重新替换成 前端传来的新组合。这里为了练习位运算,稍微做了一下调解:令目标函数接收三个参数:当前权限、待删权限、待增权限。也就是说,把权限的 修改 看成是 删除 和 添加 的组合操作,先删后加。
v1.0 版实现如下:
function updatePermission(perms: Permission, permToDel: Permission, permToAdd: Permission): Permission {
return (perms ^ permToDel) | permToAdd;
}
实践 DRY 原则(Don’t Repeat Yourself),复用前面的两个方法 delPermission 和 addPermission,酿成 v2.0 版实现:
function updatePermission(perms: Permission, permToDel: Permission, permToAdd: Permission): Permission {
return addPermission(delPermission(perms, permToDel), permToAdd)
}
结果一测就出问题了:
let perms = addPermission(Permission.Readable, Permission.Executable);// 101
const pDel = addPermission(Permission.Readable, Permission.Writable);// 011
const pAdd = Permission.Executable;// 100
perms = updatePermission(perms, pDel, pAdd);
console.assert(perms === 0b100, `updatePermission failed: \nExpected '0b100', but got '0b${perms.toString(2)}' instead.`);
/* =>
Assertion failed: updatePermission failed:
Expected '0b100', but got '0b110' instead.
*/
缘故原由说来也简单:删除的时候需要先判断一下,否则本来没有的权限,也可能由于位异或操作而被意外引入。于是有了第 3 版实现:
function updatePermission(perms: Permission, permToDel: Permission, permToAdd: Permission): Permission {
// return addPermission(delPermission(perms, permToDel), permToAdd);
const permDelReal = perms & permToDel;
if(hasPermission(perms, permDelReal)) {
const permPreAdd = delPermission(perms, permDelReal);
return addPermission(permPreAdd, permToAdd);
} else {
return addPermission(perms, permToAdd);
}
}
注意到第 3 行,单独的 位与 运算着实是类似求交集的结果(同真为真,别的为假),而且与查询逻辑的部分代码重复了。这说明 位与 运算也是一个原子操作,应该单独提出来:
/**
* Get the shared permission between two permission sets
*
* @param perm1 the 1st permission set
* @param perm2 the 2nd permission set
* @returns the shared permission
*/
function sharedPermission(perm1: Permission, perm2: Permission): Permission {
return perm1 & perm2;
}
于是再次重构 updatePermission 函数,有了第 4 版实现:
/**
* Update the permission set by adding and deleting permissions
*
* @param perms original permission set
* @param permToDel permission to delete
* @param permToAdd permission to add
* @returns the new permission set
*/
function updatePermission(perms: Permission, permToDel: Permission, permToAdd: Permission): number {
const permDelReal = sharedPermission(perms, permToDel);
if(hasPermission(perms, permDelReal)) {
const permPreAdd = delPermission(perms, permDelReal);
return addPermission(permPreAdd, permToAdd);
} else {
return addPermission(perms, permToAdd);
}
}
大功告成。
2.5 完整测试
搞定了 CRUD 操作,最后再完整测试一遍:
// Test cases
let perms = addPermission(Permission.Readable, Permission.Writable);
console.log('perms:', perms.toString(2));// 011
// Test permission deletion
perms = delPermission(perms, Permission.Writable);
console.log('permsAfterDel:', perms.toString(2));// 001
// Test permission addition
perms = addPermission(perms, Permission.Executable);
console.log('permsAfterAdd:', perms.toString(2));// 101
// Test shared permission
console.log('has readable:', hasPermission(perms, Permission.Readable));// true
console.log('has writable:', hasPermission(perms, Permission.Writable));// false
console.log('has executable:', hasPermission(perms, Permission.Executable));// true
// Test permission update
console.log('Before update, perms =', perms.toString(2)); // 101 (i.e. 5)
const pDel = addPermission(Permission.Readable, Permission.Writable);// 011
const pAdd = addPermission(Permission.Executable, Permission.Executable);// 100
console.log('perms to delete =', pDel.toString(2));// 011 (i.e. 3)
console.log('perms to add =', pAdd.toString(2));// 110 (i.e. 6)
perms = updatePermission(perms, pDel, pAdd);
console.assert(perms === 0b100, `updatePermission failed: \nExpected '0b100', but got '0b${perms.toString(2)}'`);
console.log('After update perms =', perms.toString(2));// 110
实测结果如下:
https://i-blog.csdnimg.cn/direct/8f4f99192cb445d5a2904034b74154a7.png#pic_center
3 小结
[*] TypeScript 的枚举范例非常适合做权限管理;
[*] 借助 TypeScript 枚举范例(enum)和基本的位运算(|、&、^、<<、~ 等),可轻松实现权限的 CRUD 操作(修改操作勉强也算轻松吧);
[*] 代码重构过程中,应该将通用的、原子级的操作抽出来,以便复用;
[*] 逻辑或流程设计得再完备,都不可忽视测试环节;信任不能取代监视;
[*] 也可以将权限修改的部分逻辑并入删除方法中(随个人喜欢或特定需求);
[*] 分离出全部原子操作后,就可以在此基础上组合出更多类似修改这样的接口方法(函数式编程的基础);
[*] 定义枚举值阶段,还可以使用 左移 运算符 <<,方便赋值:
enum Permission {
Readable = 1 << 0, // 0b001,// i.e. 2^0 = 1
Writable = 1 << 1, // 0b010,// i.e. 2^1 = 2
Executable = 1 << 2, // 0b100,// i.e. 2^2 = 4
}
// or refactor into a initialization function
/**
* Initialize a permission value with left-shift operation
* @param order the order of the permission
* @returns the permission value
*/
function initPermission(order: number = 0): number | never {
if (order < 0) throw new Error("Order must be a non-negative integer");
if (order > 10) throw new Error("Order must be no greater than 10");
return 1 << order;
}
enum PermissionNew {
Readable = initPermission(),// 0b001,// i.e. 2^0 = 1
Writable = initPermission(1), // 0b010,// i.e. 2^1 = 2
Executable = initPermission(2), // 0b100,// i.e. 2^2 = 4
}
[*] 完整代码已上传 Gitee,欢迎交流。
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。
页:
[1]