使用位運算來做用戶鑒權

使用位運算來做用戶鑒權其實並不是一件新鮮事,已經有不少人講過了。不過最近在看vue3源碼的時候發現vue3在對VisualDOM做patch操作的時候竟然也使用了位運算進行flag的判斷,便忽然來了興趣,想要好好說道說道。

首先看看來看看vue3源碼,已經去除了不必要的注釋

patchFlags是VisualDOM中對vnode的類型標記,在更新DOM樹的時候會根據vnode的類型來使用不同的更新策略,這裡不展開說了,我們主要看這裡對類型的定義。
(patchFlags.ts Github源碼地址)

// Patch flags can be combined using the | bitwise operator and can be checked
// using the & operator, e.g.
//
//   const flag = TEXT | CLASS
//   if (flag & TEXT) { ... }
//
// Check the `patchElement` function in './renderer.ts' to see how the
// flags are handled during diff.

export const enum PatchFlags {
  TEXT = 1,
  CLASS = 1 << 1,
  STYLE = 1 << 2,
  PROPS = 1 << 3,
  FULL_PROPS = 1 << 4,
  HYDRATE_EVENTS = 1 << 5,
  STABLE_FRAGMENT = 1 << 6,
  KEYED_FRAGMENT = 1 << 7,
  UNKEYED_FRAGMENT = 1 << 8,
  NEED_PATCH = 1 << 9,
  DYNAMIC_SLOTS = 1 << 10,

  // SPECIAL FLAGS -------------------------------------------------------------

  // Special flags are negative integers. They are never matched against using
  // bitwise operators (bitwise matching should only happen in branches where
  // patchFlag > 0), and are mutually exclusive. When checking for a special
  // flag, simply check patchFlag === FLAG.
  HOISTED = -1,
  BAIL = -2
}

// dev only flag -> name mapping
export const PatchFlagNames = {
  [PatchFlags.TEXT]: `TEXT`,
  [PatchFlags.CLASS]: `CLASS`,
  [PatchFlags.STYLE]: `STYLE`,
  [PatchFlags.PROPS]: `PROPS`,
  [PatchFlags.FULL_PROPS]: `FULL_PROPS`,
  [PatchFlags.HYDRATE_EVENTS]: `HYDRATE_EVENTS`,
  [PatchFlags.STABLE_FRAGMENT]: `STABLE_FRAGMENT`,
  [PatchFlags.KEYED_FRAGMENT]: `KEYED_FRAGMENT`,
  [PatchFlags.UNKEYED_FRAGMENT]: `UNKEYED_FRAGMENT`,
  [PatchFlags.DYNAMIC_SLOTS]: `DYNAMIC_SLOTS`,
  [PatchFlags.NEED_PATCH]: `NEED_PATCH`,
  [PatchFlags.HOISTED]: `HOISTED`,
  [PatchFlags.BAIL]: `BAIL`
}

可以看到,除了最後兩種特殊類型外共有11種類型,每一種的值都是依次將1左移一位得到的。
在文檔開頭的注釋里作者甚至貼心的給出了用法:使用「|」來進行組合賦值,使用「&」來進行檢查。

下面我們來寫一個簡單的例子

以常用的Linux命令chmod 777為例,7代表可讀可寫可執行,寫成二進位就是0111。具體如下:

許可權 十進位 二進位
x (執行) 1 0001
w (寫入) 2 0010
r(讀取) 4 0100

其中,每一種許可權的數值就是將1左移1到3位得到的。
不難理解,如果是可讀可執行則對應0101,值為5;可寫可執行對應0011,值為3,就此我們來寫一個簡單的鑒權系統。

const Permissions = {
	X: 1,
	W: 1 << 1, // 0010 -> 2
	R: 1 << 2, // 0100 -> 4
};

let userPermission = 0; // 初始為0,即無許可權

/** 賦權 */
userPermission |= Permissions.X; // 賦予可執行許可權,此時 userPermission 為 0001
userPermission |= Permissions.W; // 賦予寫許可權,此時 userPermission 為 0011

/** 鑒權 */
if ( userPermission & Permissions.X ) { // 0011 & 0001 結果為1,返回真
	console.log('此用戶有可執行許可權');
}
if ( userPermission & Permissions.W ) { // 0011 & 0010 結果為2,返回真
	console.log('此用戶有寫許可權');
}
if ( userPermission & Permissions.R ) { // 0011 & 0100 結果為0,返回假
	console.log('此用戶有讀許可權');
}

可以直接按F12將程式碼粘貼過去運行,可以看到瀏覽器最後輸出了「此用戶有可執行許可權「和」此用戶有寫許可權」。
具體是怎麼實現的呢?
我們的Permissions共有3中許可權,而用戶許可權userPermission默認為0,代表無許可權。
在賦權操作中,將userPermission 與Permissions進行或運算,此時userPermission對應位上的0會變成1,即代表用戶擁有了此許可權。
而在鑒權操作中,將userPermission 與Permissions進行與運算,此時若userPermission對應位上的數字為1,則會返回一個大於0的值,也就是真值,代表用戶擁有此許可權;而用戶沒有此許可權時返回的結果就是0了,鑒權為假。
如此,一個簡單的用戶鑒權系統就完成了。

renderer.ts中作者也向我們展示了具體的使用方式,感興趣的不妨點擊鏈接去看看。

Tags: