前言
每次的系統設計都是最有趣的地方,過往用 Laravel 有人寫好的套件,設計了兩個 table role、permissions,很直覺,也沒什麼不對,仔細想想,每次加權限絕對不是資料本身增加,而是功能與誰被允許,所以權限本身應該是跟著code,而非資料。
思考脈絡
應該用一個 enum 限縮權限範圍,一個人會有多個權限,用array是很直覺的方法,如果只用一個數字呢?可以把多個數字合成一個數字的方法
質數
使用質數相乘,用因式分解,就可以還原每一個數字。質數的方法得用除法,要檢查一個質數乘積是否包含一個質數,直接拿過來除,餘數為零,國中數學就可以解決,在程式裡面則使用mod。要把每一個權限都配置一個質數,然後乘起來,檢查的時候用除法,看起來還行....
二進制
把每個權限當作一個 boolean,把它轉成十進制存下來,要使用再還原成二進制。要比對的的時候直接用 AND 就可以知道。
小結
看起來用二進制比較符合需求,用數位對應權限,容易看懂。
設計實作
權限管理系統的核心是將每個權限分配一個唯一的二進制位(bit),並使用位元運算來檢查用戶是否擁有特定權限。以下是設計步驟:
定義權限
使用枚舉(Enum)定義所有可能的權限,每個權限對應一個二進制位。
分配權限值
透過左移運算(<<)為每個權限分配唯一的位元值(例如 1 << 0 表示第一位,1 << 1 表示第二位)。
檢查權限
使用位元 AND 運算(&)檢查用戶的權限值是否包含特定權限。
管理權限
透過位元 OR 運算(|)添加權限,AND NOT 運算(& ~)移除權限。
範例實現
以下是一個基於 TypeScript 的權限管理範例,展示如何定義權限並檢查用戶權限。
enum UserPermission {
READ = 1 << 0, // 00000000000001 (1) - 讀取檔案
CREATE = 1 << 1, // 00000000000010 (2) - 建立檔案
UPDATE = 1 << 2, // 00000000000100 (4) - 編輯檔案
DELETE = 1 << 3, // 00000000001000 (8) - 刪除檔案
MOVE = 1 << 4, // 00000000010000 (16) - 移動檔案
DOWNLOAD = 1 << 5, // 00000000100000 (32) - 下載檔案
COPY = 1 << 6, // 00000001000000 (64) - 複製檔案
UPDATE_OCR = 1 << 7, // 00000010000000 (128) - 編輯 OCR
CREATE_COMPLAINT = 1 << 8, // 00000100000000 (256) - 生成書狀
DELETE_PERMANENTLY = 1 << 9, // 00001000000000 (512) - 永久刪除
RESTORE = 1 << 10, // 00010000000000 (1024) - 恢復檔案
CONTROL_PRIVATE = 1 << 11, // 00100000000000 (2048) - 機敏檔案控管
FILE_RECLASSIFY = 1 << 12, // 01000000000000 (4096) - 未分類文件調整
CONTROL_NAS = 1 << 13, // 10000000000000 (8192) - 管理 NAS 設定
}
/**
* 檢查用戶是否擁有指定權限
* @param userPermissions 用戶的權限值(二進制表示)
* @param permission 要檢查的權限
* @returns 是否擁有該權限
*/
export const hasPermission = (
userPermissions: number,
permission: UserPermission,
): boolean => {
return (userPermissions & permission) === permission;
};
// 範例:檢查用戶是否擁有 FILE_RECLASSIFY 權限
const userPermissions = 16383; // 001111111111111 (包含前 14 位權限)
const hasFileReclassify = hasPermission(userPermissions, UserPermission.FILE_RECLASSIFY);
console.log(`用戶是否擁有 FILE_RECLASSIFY 權限:${hasFileReclassify}`); // true
程式碼說明
- Enum 定義:UserPermission 定義了 14 種權限,每個權限使用左移運算分配唯一的二進制位。例如,READ = 1 << 0 表示 00000000000001(十進制 1),FILE_RECLASSIFY = 1 << 12 表示 01000000000000(十進制 4096)。
- 權限檢查:hasPermission 函數使用 & 運算檢查用戶的權限值(userPermissions)是否包含指定的權限。例如,檢查 FILE_RECLASSIFY 時,若 userPermissions & FILE_RECLASSIFY 等於 FILE_RECLASSIFY,則表示用戶擁有該權限。
- 範例測試:用戶權限值 16383(二進制 001111111111111)表示擁有前 14 位權限,因此檢查 FILE_RECLASSIFY(第 13 位)會回傳 true。
進階功能
添加權限
透過 OR 運算(|)為用戶添加權限:
let userPermissions = UserPermission.READ; // 初始只有 READ 權限
userPermissions |= UserPermission.CREATE; // 添加 CREATE 權限
console.log(userPermissions); // 3 (00000000000011)
移除權限
透過 AND NOT 運算(& ~)移除用戶權限:
userPermissions &= ~UserPermission.CREATE; // 移除 CREATE 權限
console.log(userPermissions); // 1 (00000000000001)
但實際上前端用 checkbox 丟進來乘在一塊,把直存起來就好
檢查多個權限
檢查用戶是否同時擁有某些權限:
const requiredPermissions = UserPermission.READ | UserPermission.CREATE;
const hasAllRequired = (userPermissions & requiredPermissions) === requiredPermissions;
console.log(`用戶是否擁有 READ 和 CREATE 權限:${hasAllRequired}`);
注意事項
位元數限制:JavaScript 的數字使用 32 位元或 64 位元(取決於環境),因此最多支援 32 或 64 個權限。若需更多權限,可考慮使用 BigInt 或多個位元欄位。 可讀性與維護:為每個權限提供清晰的命名和註解,避免直接使用魔術數字(magic numbers)。 安全性:確保權限檢查邏輯在伺服器端執行,防止客戶端繞過檢查。 擴展性:新增權限時,應確保新權限的位元值不與現有權限衝突。
結論
本來只是覺得好玩重新設計,後來發現他跟網路常用的 IP 遮罩很像,有碰過電子元件的,就很像只播開關,每一個權限都是一個開關。
文字工作者,寫作時間常常在人類與電腦之間拉鋸,相信閱讀,相信文字與思想所構築的美麗境界