<cite id="9vvnb"></cite>
<cite id="9vvnb"></cite>
<var id="9vvnb"><strike id="9vvnb"></strike></var>
<menuitem id="9vvnb"><strike id="9vvnb"><listing id="9vvnb"></listing></strike></menuitem>
<cite id="9vvnb"><video id="9vvnb"></video></cite>
<cite id="9vvnb"></cite>
<cite id="9vvnb"><video id="9vvnb"></video></cite>
<var id="9vvnb"></var>
<cite id="9vvnb"></cite><cite id="9vvnb"><span id="9vvnb"></span></cite>
<cite id="9vvnb"><span id="9vvnb"><menuitem id="9vvnb"></menuitem></span></cite>

關于系統權限的設計-位操作

本文討論是權限設計的其中一種方向,有它自己的優缺點,不一定適用于所有系統。

一、Linux文件權限

大家都知道,Linux上有三種文件權限:

  • r:表示讀取,對應的數字為 4;
  • w:表示寫入,對應的數字為 2;
  • x:表示執行,對應的數字為 1;

當然還有一種是特殊的是:

  • -:表示無權限,對應的數字為0;

通過這四個數字以及他們的組合,就可以表示任意一種權限:

1+2=3:有執行、寫入權限,沒有讀取權限。

1+4=5:有執行、讀取權限,沒有寫入權限。

2+4=6:有寫入、讀取權限,沒有執行權限。

1+2+4=7:有執行、寫入、讀取權限。

不知道大家有沒有想過,為什么權限的標識要是0/1/2/4這幾個數字呢?為什么不是0/1/2/3?為什么不是6/7/8/9?為什么0/1/2/4組合就可以表示所有權限呢?

有些人會解釋說,如果用0/1/2/3,那3表示的是3本身呢?還是1+2呢?意義不明確呀。用其他的數字又太大,不方便計算,沒有0124簡單。其實這些都不是真正的原因,Linux文件權限設計之所以要用0124,是因為它其實是用二進制表示權限的。

二、用二進制比特位表示權限

二進制是什么想必不用我多說,我們都知道二進制表示的數字,只有0和1兩種數。那么我們就想,如果我用0表示沒有權限,用1表示有權限,這樣豈不是很簡單?

我繼續用Linux文件權限舉例。

用第一個比特位表示是否有執行權限,第二個比特位表示是否有寫入權限,第三個比特位表示是否有讀取權限。每個比特位的0表示沒有權限,1表示有權限。(后文所說的第幾位,都是指從右向左數)

那么我們試一試,如何表示只有寫入權限,沒有讀取和執行權限呢?

按照上面的規則,應該是0010對吧,將這個二進制數字轉成十進制數字,剛好是2。以此類推,就有了0/1/2/4這四個數字。

分別表示:無權限(0000)、執行(0001)、寫入(0010)、讀?。?100)。

再驗證一下,如何表示有執行、讀取權限,沒有寫入權限呢?應該是(0101),將這個二進制數字轉成十進制數字,剛好是5,符合我們上面說的。

所以,只要我們提前約定好每個比特位代表的是什么權限,我們就可以通過一個二進制數字,表示大量的權限及其自由組合,而且非常的節省存儲空間,1個字節,我們就可以存儲8種權限。在Java語言中的int類型有4個字節,一個int值,就可以存儲32種權限,以及2的32次方種權限組合。那么知道了這個知識點,我們如何將它設計進我們的權限系統呢?

三、權限的增刪查

知道了如何存儲權限,但是我們還需要操作權限。而一個權限系統,必然會有三大基礎操作(因為權限非有即無,所以編輯操作等價于添加或刪除):

  • 添加一個權限
  • 刪除一個權限
  • 以及校驗一個權限

那我們如何對一個二進制數字表示的權限,進行增刪查呢?這里就要利用位操作了:

  • | 添加權限
  • ^ 刪除權限(已有權限時)
  • & 校驗權限

用代碼來解釋一下:

    /**
     * 添加權限
     *
     * @param currentPermission 原權限
     * @param permissions       需要添加的權限集合
     * @return 添加完權限后的十進制數字
     */
    public static int addPermissions(int currentPermission, int... permissions) {
        for (int permission : permissions) {
            currentPermission |= permission;
        }
        return currentPermission;
    }

    /**
     * 從已有權限里,刪除權限
     *
     * @param currentPermission 原已有權限
     * @param permissions       需要刪除的權限集合
     * @return 刪除完權限后的十進制數字
     */
    public static int removePermissions(int currentPermission, int... permissions) {
        for (int permission : permissions) {
            currentPermission ^= permission;
        }
        return currentPermission;
    }

    /**
     * 校驗權限
     *
     * @param currentPermission 原權限
     * @param permission        要校驗的權限
     * @return 是否含有權限
     */
    public static boolean hasPermission(int currentPermission, int permission) {
        return (currentPermission & permission) == permission;
    }

為什么上述三個操作可以做到添加、刪除、查詢權限呢?我們需要來復習一下位運算。

位運算
或(|):兩個都為0,結果才為0,否則為1 0 | 0 = 0 0 | 1 = 1 1 | 0 = 1 1 | 1 = 1
異或(^):兩個相同為0,不相同為1 0 ^ 0 = 0 0 ^ 1 = 1 1 ^ 0 = 1 1 ^ 1 = 0
與(&):兩個都為1,結果才為1,否則為0 0 & 0 = 0 0 & 1 = 0 1 & 0 = 0 1 & 1 = 1
取反(~):0變1,1變0 ~0 = 1 ~1 = 0

根據位運算的特點,我們可以發現:

  • 給執行權限里,添加寫入權限,本質應該是將執行權限(0001)的第二個0,變成1,那么只要或(|)一個寫入權限(0010)就好了。(或一個數,就代表將這個數表示的權限,添加到原來的數字里。無論原來的數字有沒有權限都會加進去):

  • 在執行、寫入權限里,刪除寫入權限,本質應該是將執行、寫入權限(0011)的第二個1,變成0,那么只要異或(^)一個寫入權限(0010)就好了。(異或一個數,就代表將這個數表示的權限,從原來的數字里刪除。只有在原來的數字已有權限時才會刪除,否則是添加):

  • 在執行、寫入權限里,判斷是否有寫入權限,其本質應該是判斷執行、寫入權限(0011)的第二位,是否是1,那么只要與(&)一個寫入權限(0010),再將結果和寫入權限(0010)自身比較一下是否相等就好了。(與一個數,如果還等于這個數,就代表原來的數字有這個數表示的權限):

這里特別說明一下“異或”刪除權限的操作,只有原來的數字里已經有權限了,才可以刪除。從異或運算的規則中可以發現,異或運算其實是無則增,有則減的操作。那么如果我想,無論原來的數字有沒有權限,都刪除權限(無論原來是0或1,都變成0),該怎么操作呢?

有三個方法:

第一個方法是,異或操作前,先判斷下有無權限,有權限時再刪除,無權限自然也不需要刪除權限。

    /**
     * 從已有權限里,刪除權限
     *
     * @param currentPermission 原已有權限
     * @param permissions       需要刪除的權限集合
     * @return 刪除完權限后的十進制數字
     */
    public static int removePermissions1(int currentPermission, int... permissions) {
        for (int permission : permissions) {
            if (!hasPermission(currentPermission, permission)) {
                continue;
            }
            currentPermission ^= permission;
        }
        return currentPermission;
    }

第二個方法是,利用添加權限時,無論有沒有權限都會加上去的特點,我們可以先添加,再刪除。

    /**
     * 刪除權限
     *
     * @param currentPermission 原權限
     * @param permissions       需要刪除的權限集合
     * @return 刪除完權限后的十進制數字
     */
    public static int removePermissions2(int currentPermission, int... permissions) {
        for (int permission : permissions) {
            currentPermission = addPermissions(currentPermission, permission);
            currentPermission ^= permission;
        }
        return currentPermission;
    }

第三個方法是,先“取反”運算,再“與”運算

    /**
     * 刪除權限
     *
     * @param currentPermission 原權限
     * @param permissions       需要刪除的權限集合
     * @return 刪除完權限后的十進制數字
     */
    public static int removePermissions3(int currentPermission, int... permissions) {
        for (int permission : permissions) {
            currentPermission &= ~permission;
        }
        return currentPermission;
    }

第三種這個理解起來比較困難,我解釋一下。還是以在執行、寫入權限里,刪除寫入權限為例,先對要刪除的寫入權限(0010)“取反”

再將執行、寫入權限(0011)“與”一個上面取反的結果

可以發現,結果是一樣的。這樣的好處就是,不用判斷原來的數字里,是否含有權限了,也不用先改變原來數字的權限,但沒有第一種和第二種方法那么簡單易懂。

這里推薦使用第三種方法,因為前兩種在某些業務場景下會有并發問題,是線程不安全的(若是如代碼示例那樣,方法參數是基本類型,而非引用類型,那就不會有線程安全問題了,這三種方法都可以)。

四、總結

優點:既然是二進制存儲,位運算操作,肯定有節省空間,效率極高的優點,當然同時也是逼格滿滿。

缺點:權限種類有限,如果用int存儲,最多只能有32種權限類型,用long型則最多有64種權限類型。當然這個也是有解決方案的,反正是在比特位上用二進制存儲權限,那么也不用局限于整數型,也可以將二進制數字用字符串表示,但這樣運算起來比較麻煩,所以這種設計最好還是用于權限種類不太多的情況比較好。

舉一反三,本文的知識點也不僅僅用于權限系統。

所有需要做大量判斷的系統或接口,都可以通過本文的方法,將眾多表達true/false概念的參數,通過一個短短的數字傳遞到系統或接口里,可以節省空間和提高效率。

五、一個權限工具類demo

/**
 * 權限工具類demo
 *
 * @author dijia478
 * @date 2021-11-20 16:52:10
 */
public class PermissionUtils {

    /**
     * 所有權限都沒有
     */
    public static final int NOT_ALL = 0;

    /**
     * 所有權限都有
     */
    public static final int HAVE_ALL = -1;

    /**
     * 執行權限
     */
    public static final int EXECUTE = 1 << 0;

    /**
     * 新建權限
     */
    public static final int CREATE = 1 << 1;

    /**
     * 查詢權限
     */
    public static final int SELECT = 1 << 2;

    /**
     * 修改權限
     */
    public static final int UPDATE = 1 << 3;

    /**
     * 刪除權限
     */
    public static final int DELETE = 1 << 4;

    /**
     * 進行字段校驗
     */
    public static final int CHECK_ITEM = 1 << 5;

    /**
     * 記錄操作日志
     */
    public static final int OPERATE_LOG = 1 << 6;

    /**
     * 發送通知
     */
    public static final int SEND_MSG = 1 << 7;

    /**
     * 使用緩存
     */
    public static final int USE_CACHE = 1 << 8;

    /**
     * 添加權限
     *
     * @param currentPermission 原權限
     * @param permissions       需要添加的權限集合
     * @return 添加完權限后的十進制數字
     */
    public static int addPermissions(int currentPermission, int... permissions) {
        for (int permission : permissions) {
            currentPermission |= permission;
        }
        return currentPermission;
    }

    /**
     * 從已有權限里,刪除權限
     *
     * @param currentPermission 原已有權限
     * @param permissions       需要刪除的權限集合
     * @return 刪除完權限后的十進制數字
     */
    public static int removePermissions1(int currentPermission, int... permissions) {
        for (int permission : permissions) {
            if (!hasPermission(currentPermission, permission)) {
                continue;
            }
            currentPermission ^= permission;
        }
        return currentPermission;
    }

    /**
     * 刪除權限
     *
     * @param currentPermission 原權限
     * @param permissions       需要刪除的權限集合
     * @return 刪除完權限后的十進制數字
     */
    public static int removePermissions2(int currentPermission, int... permissions) {
        for (int permission : permissions) {
            currentPermission = addPermissions(currentPermission, permission);
            currentPermission ^= permission;
        }
        return currentPermission;
    }

    /**
     * 刪除權限
     *
     * @param currentPermission 原權限
     * @param permissions       需要刪除的權限集合
     * @return 刪除完權限后的十進制數字
     */
    public static int removePermissions3(int currentPermission, int... permissions) {
        for (int permission : permissions) {
            currentPermission &= ~permission;
        }
        return currentPermission;
    }

    /**
     * 校驗權限
     *
     * @param currentPermission 原權限
     * @param permission        要校驗的權限
     * @return 是否含有權限
     */
    public static boolean hasPermission(int currentPermission, int permission) {
        return (currentPermission & permission) == permission;
    }

    /**
     * 獲取所有權限都有
     *
     * @return 擁有所有權限的十進制數字,其實就是-1
     */
    public static int getAllPermission() {
        return HAVE_ALL;
    }

    /**
     * 獲取所有權限都沒有
     *
     * @return 沒有所有權限的十進制數字,其實就是0
     */
    public static int getNotAllPermission() {
        return NOT_ALL;
    }

    /**
     * 只獲取需要的權限,除了需要的,其他權限都沒有
     *
     * @param permissions 需要的權限
     * @return 所有需要的權限的十進制數字
     */
    public static int getNeedPermission(int... permissions) {
        return addPermissions(NOT_ALL, permissions);
    }

    /**
     * 只獲取不需要的權限,除了不需要的,其他權限都有
     *
     * @param permissions 不需要的權限
     * @return 所有不需要的權限的十進制數字
     */
    public static int getNotNeedPermission(int... permissions) {
        return removePermissions3(HAVE_ALL, permissions);
    }

}
? 版權聲明
文章版權歸作者所有,歡迎轉載,但必須給出原文鏈接,否則保留追究法律責任的權利
THE END
posted @ 2021-11-20 16:49  dijia478  閱讀(540)  評論(0編輯  收藏  舉報
黄色网站在现免费看_黄色网站在线18P_黄色网站在线播放