使用位運算來做使用者鑑權

YogurtQ發表於2020-11-26

使用位運算來做使用者鑑權其實並不是一件新鮮事,已經有不少人講過了。不過最近在看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中作者也向我們展示了具體的使用方式,感興趣的不妨點選連結去看看。

相關文章