基於位運算的許可權設計
由於這裡許可權是基於 Bit 的所以需要大家對位
以及位操作符
需要有一定的認識
[TOC]
前置知識
- MDN 位運算子
- 單一許可權有且只有一位為 1
- 從右向左,由低到高
操作符速記:
&
按位與:對應位都是 1 則為 1|
按位或:對應位都是 0 則為 0^
按位異或:對應位都相同則是 0,不同則為 1
實際案例
我們以四種許可權的 CRUD 來舉例,使用 4 位的 bit 來進行。這裡有一點需要注意,單一許可權有且只有一位為 1
變數 | 二進位制 | 描述 |
---|---|---|
C | 0b0001 | 增 |
D | 0b0010 | 刪 |
U | 0b0100 | 改 |
R | 0b1000 | 查 |
校驗某些許可權
const curPermission = 0b1001; // 當前使用者的許可權,「增」「查」
const allowCreate = (curPermission & C) === C; // => true
const allowUpdate = (curPermission & U) === U; // => false
從上面程式碼可知,當前使用者的許可權為Ob1001
第一位與第四位是1
,則說明擁有增
和查
的許可權
當使用者的許可權使用按位與
只有相同位都為 1 時才可以得到 1
由圖與程式碼可以看出,我們可以使用&
得到的值對比定義好的變數是否相等可知,當前是否有某個許可權
新增某個許可權
let curPermission = 0b0100; // 當前使用者只有「改」許可權
// C = 0b0001 D = 0b0010
// 新增「增」「刪」的許可權
curPermission = curPermission | C | U; // => 0b0111
最終我們得到的許可權包含原有的「改」以及新加入的「增」「刪」
刪除某個許可權
刪除時我們使用先按位取反再按位與&(~P)
的操作
let curPermission = 0b1110; // 當前使用者許可權,「刪」「改」「查」
// R = 0b1000
curPermission = curPermission & ~C; // => Ob0110 刪除了「查」的許可權
最終我們得到的許可權只有「刪」「改」,已經將「查」許可權刪除
Toggle 操作
使用按位異或無則增有則減(對應位不同為 1,相同為 0),從結果來看實際上是一個 Toggle 操作
let curPermission = 0b1000; // 當前使用者許可權,「查」
// 無則增;C = 0b0001
curPermission ^ C; // => Ob1001 得到的為「增」「查」
// 有則減
curPermission ^ C; // => Ob1001 得到的為「查」,又將「增」許可權刪除了
複合型別
我們可以使用複合操作來進行更方便快捷的操作,可用於上面任意操作,這裡只用校驗
來舉例
當我們的頁面中有下圖中的操作列,既有刪除按鈕也就修改按鈕,就出現下面幾種情況:
- 當有「刪」許可權時顯示
Delete
按鈕 - 當有「改」許可權時顯示
Edit
按鈕 - 當「刪」「改」都沒有時將
operation
列隱藏
const curPermission = 0b1000; // 當前使用者許可權
const D = 0b0010; // 刪
const U = 0b0100; // 改
const DandU = 0b0110; // 刪、改 都有
const allowDelete = (p: number) => (p & D) === D;
const allowUpdate = (p: number) => (p & U) === U;
const allowDeleteAndUpdate = (p: number) => (p & DandU) === DandU;
const COLUMNS = [
{
title: 'operation',
dataIndex: 'operation',
render: () => (
<>
{allowUpdate(curPermission) && <button>Edit</button>}
{allowDelete(curPermission) && <button>Delete</button>}
</>
),
},
];
const retColumns = COLUMNS.filter((x) => {
if (x.dataIndex === 'operation') {
return allowDeleteAndUpdate(curPermission);
}
return true;
});
// retColumns 是我們最終使用的 Table columns 資料
同理,程式碼中的複合型別可以應用至其它的操作,作為作業有心的同學可以 coding
優點
- 一個引數可以代表多種型別,不需要多個許可權編碼
- 可以使用複合型別,比如既有新增又有修改則可以定義
cosnt allowCreateAndUpdate = 0b1010
,使用時(curAccess & allowCreateAndUpdate) === allowCreateAndUpdate
- 可擴充性高,比如再新增一個是否可執行許可權,則可以使用 5 位 bit 0b10000
缺點
位運算子將它的運算元視為32位元的二進位制串 -- 來自 MDN
這樣的話可用的許可權數有限,可以使用結構體
或名稱空間
來進行管控
結構體也就是物件,我們將具體的許可權放在定好的結構體中
const permissionList = [
{
pid: 1, // position id 也就是 位置ID
code: 0b0001, // 對應的編碼
}
];
名稱空間,其實也可以使用上面結構體描述,這裡我們使用字串來進行描述,有一套預設規則:pos,code
const permissionList = ['pos1,0b0001', 'pos2,0b0011'];
在具體使用時根據自己的不同規則編寫好對應許可權的操作方法,提供給具體的業務同學使用
TypeScript 加成
使用 Enum 與位賦值操作符以及 namespace 的靜態方法
/** 定義 */
enum AuthCode {
Read = 0b001, // 也可以寫成 1
Write = 0b010, // 也可以寫成 r << 1 或 2
/** 執行 execute */
Exec = 0b100, // 也可以寫成 r << 2 或 4
// 以下為複合型別
/** 0b011 */
ReadAndWrite = 0b011,
/** Union of all host auth */
HostAuthMask = 0b111,
}
namespace Auth {
/**
* 驗證當前許可權是否存在
* @param validCode - 要驗證的許可權編碼(即使用者返回的編碼)
* @param code - 定義好的許可權編碼
*/
export const validator = (validCode: AuthCode, code: AuthCode): boolean => {
return (validCode & code) === code;
};
// curry 處理 validator()
/**
* 給使用者加入許可權
* @param userCode - 當前使用者擁有的許可權
* @param waitingCode - 待加入給使用者的許可權
* @returns 返回加入許可權後的所有許可權
*/
export const add = (
userCode: AuthCode,
waitingCode: AuthCode | AuthCode[]
): AuthCode => {
let code: number;
if (Array.isArray(waitingCode)) {
code = waitingCode.reduce((acc, cur) => {
return acc | cur;
}, 0);
} else {
code = waitingCode;
}
return userCode | code;
};
/**
* 刪除使用者的許可權
* @param userCode - 當前使用者擁有的許可權
* @param rmCode - 要刪除的許可權
*/
export const remove = (
userCode: AuthCode,
rmCode: AuthCode | AuthCode[]
): AuthCode => {
let code: number;
if (Array.isArray(rmCode)) {
code = rmCode.reduce((acc, cur) => {
return acc | cur;
}, 0);
} else {
code = rmCode;
}
return userCode & ~code;
};
/**
* 使用者許可權 Toggle
* @description 無則增,有則減
* @param userCode - 當前使用者擁有的許可權
* @param tglCode - 要 toggle 的許可權
*/
export const toggle = (userCode: AuthCode, tglCode: AuthCode) => {
return userCode ^ tglCode;
};
}
// test validator
// const userCode = 0b011; // 獲取到使用者在當前頁的許可權碼(讀、寫)
// console.log(Auth.validator(userCode, AuthCode.Read)); // => true; 當前使用者擁有 讀 許可權
// console.log(Auth.validator(userCode, AuthCode.ReadAndWrite)); // => true; 當前使用者擁有 讀寫 許可權
// console.log(Auth.validator(userCode, AuthCode.Exec)); // => false; 當前使用者沒有 執行 許可權
// console.log(Auth.validator(userCode, AuthCode.Read | AuthCode.Exec)); // => false; 當前使用者沒有 讀,執行 許可權;兩個許可權都有才為真
// test add
// let userCode = 0b000; // 獲取到使用者在當前頁的許可權碼(無許可權)
// console.log((userCode = Auth.add(userCode, AuthCode.Read))); // => 1 === 0b001; 給當前使用者加入 讀 許可權
// console.log(Auth.validator(userCode, AuthCode.Read)); // => true; 驗證當前使用者已經擁有 讀 許可權
// console.log(Auth.validator(userCode, AuthCode.Write)); // => false; 驗證當前使用者沒有 寫 許可權
// console.log((userCode = Auth.add(userCode, [AuthCode.Write, AuthCode.Exec]))); // => 7 === 0b111; 給當前使用者加入 寫、執行 許可權
// console.log(Auth.validator(userCode, AuthCode.HostAuthMask)); // => true; 驗證當前使用者擁有所有許可權
// test remove
// let userCode = 0b111; // 獲取到使用者在當前頁的許可權碼(所有許可權)
// console.log(Auth.validator(userCode, AuthCode.HostAuthMask)); // => true; 驗證當前使用者有所用許可權
// console.log((userCode = Auth.remove(userCode, AuthCode.Read))); // => 6 === 0b110; 移除使用者 讀 許可權
// console.log(Auth.validator(userCode, AuthCode.Read)); // => false; 驗證當前使用者已經刪除 讀 許可權
// console.log(
// (userCode = Auth.remove(userCode, [AuthCode.Write, AuthCode.Exec]))
// ); // => 0 === 0b000; 移除使用者 讀、執行 許可權
// console.log(Auth.validator(userCode, AuthCode.Write)); // => false; 驗證當前使用者已經刪除 寫 許可權
// console.log(Auth.validator(userCode, AuthCode.Exec)); // => false; 驗證當前使用者已經刪除 執行 許可權
// test toggle
// let userCode = 0b101; // 獲取到使用者在當前頁的許可權碼(執行、讀)
// console.log((userCode = Auth.toggle(userCode, AuthCode.ReadAndWrite))); // => 6 === 0b110; 當前使用者刪除了 讀,新增了 寫(寫 無則增,讀 有則減)
// console.log(Auth.validator(userCode, AuthCode.Read)); // => false; 驗證當前使用者無 讀 許可權
// console.log(Auth.validator(userCode, AuthCode.Write)); // => true; 驗證當前使用者有 寫 許可權