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

文章目录

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

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),复用前面的两个方法 delPermissionaddPermission,变成 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: \n  Expected '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);
  }
}

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

/**
 * 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

实测结果如下:

TS 测试结果

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,欢迎交流。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/789440.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

鸿蒙开发:Universal Keystore Kit(密钥管理服务)【加解密(C/C++)】

加解密(C/C) 以AES 256密钥为例&#xff0c;完成加解密。具体的场景介绍及支持的算法规格。 在CMake脚本中链接相关动态库 target_link_libraries(entry PUBLIC libhuks_ndk.z.so)开发步骤 生成密钥 指定密钥别名。初始化密钥属性集。调用OH_Huks_GenerateKeyItem生成密钥)…

[Linux安全运维] Linux用户以及权限管理

Linux用户以及权限管理 Linux用户和组 用户信息文件pasawd /etc/passwd文件用于存储用户的信息 :用于分割不同的字段信息 字段示例&#xff08;第一行&#xff09;含义说明1root用户名2x密码占位符x代表用户有密码存储在shadow文件中无内容代表用户登录系统不需要密码30UID…

一款24小时实时检测的六氟化硫气体泄漏报警系统

尽管当前工业生产模式越来越趋于自动化、智能化&#xff0c;但安全生产仍然是时下屡被提及的话题。在配电室等使用六氟化硫气体的众多领域中&#xff0c;由于气体泄漏而引发的中毒、火灾、爆炸、窒息事故仍高发频发。因此&#xff0c;安装六氟化硫气体泄漏报警监测系统仍是企业…

C语言 | Leetcode C语言题解之第226题翻转二叉树

题目&#xff1a; 题解&#xff1a; struct TreeNode* invertTree(struct TreeNode* root) {if (root NULL) {return NULL;}struct TreeNode* left invertTree(root->left);struct TreeNode* right invertTree(root->right);root->left right;root->right le…

如何探索高效知识管理:FlowUs知识库体验很好

在当今信息爆炸的时代&#xff0c;有效的知识管理对于个人和团队的发展至关重要。FlowUs 知识库作为一款创新的知识管理工具&#xff0c;正逐渐成为众多用户的首选&#xff0c;为他们带来了高效、便捷和有条理的知识管理体验。 FlowUs 知识库的一大特色在于其简洁直观的界面设计…

基于单片机的温控光控智能窗帘设计探讨

摘 要&#xff1a; 文章使用的核心原件是 AT89C52 单片机&#xff0c;以此为基础进行模块化的设计&#xff0c;在整个设计中通过加入光检测模块和温度检测模块&#xff0c;从而对室内的温度和光照强度进行检测&#xff0c;然后将检测得到的数据传输给单片机&#xff0c;单片机…

【自用】【高昆轮概率论与数理统计笔记】2.1 分布函数的概念与性质

不定期更新&#xff0c;前面的章节会在学完后补回来&#xff0c;重新学学概率&#xff0c;当年考研考的数学二&#xff0c;没有概率基础&#xff0c;想自己补补&#xff0c;视频课是高昆轮老师讲的浙大四版概率论教材的视频课&#xff0c;地址&#xff1a; 第一章&#xff1a;h…

印尼“支付宝” DANA 如何借力 OceanBase 实现3个“关键零”

当前&#xff0c;移动支付在东南亚正迅猛发展&#xff0c;据谷歌、淡马锡与贝恩公司发布的报告预测&#xff0c;东盟地区蓬勃兴起的移动支付市场有望在2030年突破至2万亿美元的交易规模。 在此背景下&#xff0c;DANA作为印尼——东南亚最大经济体中的一员&#xff0c;秉持着推…

基于vue的引入登录界面

以下是一些常见的登录页面布局&#xff1a; 1. 中心布局 - 登录表单位于页面的中心位置&#xff0c;通常包括用户名输入框、密码输入框、登录按钮等元素。页面背景简洁&#xff0c;以突出登录表单。 - 这种布局常见于大多数网站和应用&#xff0c;简洁明了&#xff0c;用户注意…

Android 性能优化之内存优化

文章目录 Android 性能优化之内存优化内存问题内存抖动内存泄露内存溢出 检测工具Memory ProfilerMemory AnalyzerLeakCanary 内存管理机制JavaAndroid 解决内存抖动问题模拟问题代码使用Memory Profiler工具检测优化技巧 内存泄露问题模拟问题代码使用LeakCanary工具检测优化技…

深入了解Rokid UXR2.0 SDK内置的Unity AR Glass开发组件

本文将了解到Rokid AR开发组件 一、RKCameraRig组件1.脚本属性说明2.如何使用 二、PointableUI组件1.脚本属性说明2.如何使用 三、PointableUICurve组件1.脚本属性说明2.如何使用 四、RKInput组件1.脚本属性说明2.如何使用 五、RKHand组件1.脚本属性说明2.如何使用3.如何禁用手…

数据结构与算法-动态规划-三角形最小路径和

三角形最小路径和 给定一个三角形 triangle &#xff0c;找出自顶向下的最小路径和。 每一步只能移动到下一行中相邻的结点上。相邻的结点 在这里指的是 下标 与 上一层结点下标 相同或者等于 上一层结点下标 1 的两个结点。也就是说&#xff0c;如果正位于当前行的下标 i &…

web 网络安全

Web网络安全是网络安全的一个重要分支&#xff0c;专注于保护Web应用程序、服务和网站免受各种网络威胁。学习Web网络安全涉及多个层面的知识和技能&#xff0c;以下是一些主要的学习领域&#xff1a; 一、XSS攻击 全称:&#xff1a;Cross Site Script &#xff08;跨站脚本&a…

交叉熵损失函数的使用目的(很肤浅的理解)

第一种使用方法 import torch from torch import nn # Example of target with class indices loss nn.CrossEntropyLoss() input torch.randn(3, 5, requires_gradTrue) target torch.empty(3, dtypetorch.long).random_(5) output loss(input, target) output.backward(…

今天,纷享AI正式发布,开启智能CRM新纪元

纷享销客作为国产CRM中连续四年保持近40%增长的领先品牌&#xff0c;一直在探索AICRM领域的数字化变革。 7月10日&#xff0c;纷享AI产品正式上线。与通用大模型不同&#xff0c;纷享AI是在合规之下&#xff0c;开放性的接入各种大模型平台&#xff0c;并结合纷享销客在营销服…

百度搜索框制作HTML+CSS

样品图 自制效果图&#xff08;附注释&#xff09; <!DOCTYPE html> <html lang"en"><head><!-- 定义文档的字符编码为UTF-8&#xff0c;以支持中文等多语言字符 --><meta charset"UTF-8" /><!-- 设置页面在不同设备上的…

人形机器人头部结构设计

我又回来啦&#xff01;电机部分的教程会继续更新咯~ 前几天做了成图增材赛道&#xff0c;也算4个月以来本人做过最复杂的结构项目。 不知结果会怎么样&#xff0c;但我也尽全力啦&#xff01; 把说明书发在这里&#xff0c;STL已发GitHub&#xff0c;链接&#xff1a; zysampo…

探索东芝 TCD1304DG 线性图像传感器的功能

主要特性 高灵敏度和低暗电流 TCD1304DG 具有高灵敏度和低暗电流&#xff0c;非常适合需要精确和可靠图像捕捉的应用。传感器包含 3648 个光敏元件&#xff0c;每个元件尺寸为 8 m x 200 m&#xff0c;确保了出色的光灵敏度和分辨率。 电子快门功能 内置的电子快门功能是 T…

Java 期末速成

其他题 import java.util.*; public class Test {public static void main(String[] args) {Scanner scanner new Scanner(System.in);int arr[] new int[100];int value scanner.nextInt();int s scanner.nextLine(); // 键盘输入多个字符int result 0;System.out.print…

C++ 调用Halcon引擎,脚本调试代码

一&#xff0c;背景&#xff1a;C调用halcon最常见的方式便是转C代码&#xff0c;然后封装成函数或者类库。另外一种方式是调用Halcon脚本&#xff0c;不需要转换成C代码&#xff0c;Debug的时候&#xff0c;可以直接跳入halcon脚本&#xff0c;单步查看每一行算法执行情况&…