【TypeScript 学习】TypeScript 枚举类型发散:基于位运算的权限管理 CRUD 操作

文章目录

  • [TypeScript 枚举类型发散:基于位运算的权限管理 CRUD 操作](#TypeScript 枚举类型发散:基于位运算的权限管理 CRUD 操作)
    • [1 问题由来](#1 问题由来)
    • [2 具体实现](#2 具体实现)
      • [2.1 新增权限](#2.1 新增权限)
      • [2.2 删除权限](#2.2 删除权限)
      • [2.3 查询权限(即判定存在与否)](#2.3 查询权限(即判定存在与否))
      • [2.4 修改权限](#2.4 修改权限)
      • [2.5 完整测试](#2.5 完整测试)
    • [3 小结](#3 小结)

TypeScript 枚举类型发散:基于位运算的权限管理 CRUD 操作

1 问题由来

TypeScript 枚举类型常用于约束一组固定的选项(如星期、月份)、表示几种可选的状态(如用户角色、订单状态)、或者替换一些难写的取值(如常用颜色的十六进制值等)。在这些应用场景中,有一类场景在开发后台管理系统时尤为常见------用户权限管理。假设有三种权限:可读(Readable)、可写(Writable)、可执行(Executable),则可以用 TypeScript 枚举类型表示为:

ts 复制代码
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 新增权限

新增可通过 位或(| 运算实现:

ts 复制代码
/**
 * 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

我觉得第二个更简洁,于是实现为:

ts 复制代码
/**
 * 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。这可以通过 位与 运算实现:

ts 复制代码
/**
 * 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 版实现如下:

ts 复制代码
function updatePermission(perms: Permission, permToDel: Permission, permToAdd: Permission): Permission {
  return (perms ^ permToDel) | permToAdd;
}

实践 DRY 原则(Don't Repeat Yourself),复用前面的两个方法 delPermissionaddPermission,变成 v2.0 版实现:

ts 复制代码
function updatePermission(perms: Permission, permToDel: Permission, permToAdd: Permission): Permission {
  return addPermission(delPermission(perms, permToDel), permToAdd)
}

结果一测就出问题了:

ts 复制代码
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: \n  Expected '0b100', but got '0b${perms.toString(2)}' instead.`);

/* => 
Assertion failed: updatePermission failed:
  Expected '0b100', but got '0b110' instead.
*/

原因说来也简单:删除的时候需要先判定一下,不然本来没有的权限,也可能因为位异或操作而被意外引入。于是有了第 3 版实现:

ts 复制代码
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);
  }
}

注意到第 11 行,单独的 位与 运算其实是类似求交集的效果(同真为真,其余为假),并且与查询逻辑的部分代码重复了。这说明 位与 运算也是一个原子操作,应该单独提出来:

ts 复制代码
/**
 * 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 版实现:

ts 复制代码
/**
 * 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 操作,最后再完整测试一遍:

ts 复制代码
// 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

实测结果如下:

3 小结

  • TypeScript 的枚举类型非常适合做权限管理;

  • 借助 TypeScript 枚举类型(enum)和基本的位运算(|&^<<~ 等),可轻松实现权限的 CRUD 操作(修改操作勉强也算轻松吧);

  • 代码重构过程中,应该将通用的、原子级的操作抽出来,以便复用;

  • 逻辑或流程设计得再完备,都不可忽视测试环节;信任不能代替监督;

  • 也可以将权限修改的部分逻辑并入删除方法中(随个人喜好或特定需求);

  • 分离出所有原子操作后,就可以在此基础上组合出更多类似修改这样的接口方法(函数式编程的基础);

  • 定义枚举值阶段,还可以使用 左移 运算符 <<,方便赋值:

    ts 复制代码
    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,欢迎交流。

相关推荐
Filotimo_4 小时前
2.CSS3.(2).html
前端·css
yinuo5 小时前
uniapp微信小程序华为鸿蒙定时器熄屏停止
前端
gnip6 小时前
vite中自动根据约定目录生成路由配置
前端·javascript
Pluchon6 小时前
硅基计划4.0 算法 字符串
java·数据结构·学习·算法
折翅鵬7 小时前
Android 程序员如何系统学习 MQTT
android·学习
前端橙一陈7 小时前
LocalStorage Token vs HttpOnly Cookie 认证方案
前端·spring boot
~无忧花开~7 小时前
JavaScript学习笔记(十五):ES6模板字符串使用指南
开发语言·前端·javascript·vue.js·学习·es6·js
泰迪智能科技017 小时前
图书推荐丨Web数据可视化(ECharts 5)(微课版)
前端·信息可视化·echarts
CodeCraft Studio8 小时前
借助Aspose.Email,使用 Python 读取 Outlook MSG 文件
前端·python·outlook·aspose·email·msg·python读取msg文件
拾贰_C9 小时前
【pycharm---pytorch】pycharm配置以及pytorch学习
pytorch·学习·pycharm