二進制是什麼?
在數學和數字電路中指以2為基數的記數系統,以2為基數代表系統是二進位制的。這一系統中,通常用兩個不同的數字0和1來表示。在計算機中,最常用的是二進制,因為組成計算機系統的邏輯電路通常隻有開和關這兩個狀态,用0和1很好表示這兩種狀态。
二進制的使用
二進制可以控制系統的權限,說的簡單點,即通過二進制0/1的位置來控制權限,因為某個功能要麼有權限,要麼沒有權限
例如有一個場景:使用者登入某個用戶端(平闆、手機、網頁),要通過用戶端的來源判定他是否有通路權限,我們可以通過二進制的權限位置,從最後一位往前(0位、1位、2位),設定3種權限:
可以通路平闆:00000001
可以通路手機:00000010
可以通路網頁:00000100
假設我們配置使用者user1,可以通過平闆和網頁通路,即用二進制表示為00000101,用十進制表示為:5
當使用者使用手機登入時,擷取應用的權限00000010(一般每個應用調用登入接口都會攜帶一個appKey,這個appKey綁定了它所屬的權限,根據appKey可查到對應的權限),十進制為:2,那怎麼判定使用者是否可以登入到目前應用呢?
通過&運算,功能權限 & 使用者擁有的權限 == 功能權限?
即:2 & 5,轉換為二進制:
00000010
& 00000101
-------------
00000000
&運算,隻有兩個位都是1,對應位才是1,
隻有當2&5 == 2時,表示目前使用者有手機登入權限。
二進制存儲的優點
我們設計一個權限系統,一般都會基于RBAC模型,即使用者-角色-權限,表設計如下:

正常做法,使用者通過一個角色關聯多個權限,資料存儲如下:
這僅僅是一個角色,如果一個系統需要關聯的角色很多,那麼這張表将會存儲大量的資料
通過使用二進存儲可以大大減少資料量的存儲,我們知道JAVA中Long類型可以轉換為64位二進制,
00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000
如果把每一個位置來表示一個權限,除去最左邊的符号位以外,可以表示63種權限,即一個角色最多可以關聯63種權限,如果一個角色關聯了63種權限,那麼我們隻需要插入一條資料即可,但在複雜的系統中,63種權限肯定是不夠的,可以通過設計一個權限空間來進一步區分,比如最初權限空間為0可以表示63種,當超過63種權限,權限空間遞增,然後再可以表示63種權限,是以我們對之前的角色、權限表進行改造:
角色-權限新增/删除
當使用者在界面上新增,或删除權限,我們又該如何更新二進制權限的資料?
增權重限
比如有一個角色,對應的權限空間為0,權限二進制是00001111,需要添加一個,權限空間為0,二進制為:00100000,二進制位置為5,使用二進制的“|”運算符,規則如下:
原二進制權限 | 要添加的二進制權限
通過二進制位得到要添加的二進制權限
1 << 5 = 00100000
再和00001111 進行或運算
00100000
| 00001111
-----------------
00101111
00101111即為最新的權限
删除
還是以上面新增為例,00101111需要删除添加的權限00100000。
原則:
原二進制權限 & (~要删除的二進制權限)
也就是:
00101111 & (~00100000)
~00100000 表示取反,得到11011111,然後和原二進制做&操作
11011111
& 00001111
-----------------
00001111
判斷使用者是否具有權限
判斷使用者權限同上面使用者通過用戶端通路的例子,原則:
功能權限 & 使用者擁有的功能權限 == 功能權限
通過角色查詢權限
資料庫表都存儲的是二進制資料,我們如何通過一個角色查詢到它所有的權限呢?
先上SQL:
SELECT
*
FROM
角色表 r
LEFT JOIN 角色-權限關聯表 rp ON r.角色ID = rp.角色ID
LEFT JOIN 權限表 p ON p.權限空間 = rp.權限空間
WHERE
CONV(rp.權限二進制,2,10) >> p.權限二進制位 & 1 = 1
and r.角色名稱 = ?;
重點介紹CONV函數:
CONV(N,from_base,to_base) 表示轉換進制, N是列名或值, from_base是從什麼進制,to_base是轉到什麼進制
CONV(rp.權限二進制,2,10) 就是從二進制的01串變成十進制的數
比如:權限二進制為00001111,通過CONV轉換為十進制得到16
有一條權限資料為:00001000,他的二進制權限位置為3,将 16 >> 3 得到 00000001,即将所在的權限位置移動到了最後一位,隻要和1做 & 操作,得到的結果如果是1,那麼表示包含了這條權限資料
根據權限查角色
SELECT
*
FROM
權限表 p
LEFT JOIN 角色-權限關聯表 rp ON p.權限空間 = rp.權限空間
LEFT JOIN 角色表 r ON r.角色Id = rp.角色Id
WHERE
CONV(rp.權限二進制,2,10) >> p.權限二進制位 & 1 = 1
and p.權限名稱 = ?
通過二進制存儲資料的好處,大大降低了資料量,原來63條資料可以隻用一條資料就可以代替,進行權限判斷時,原來需要從幾十甚至上百的資料一條一條判斷,現在僅僅需要進行位運算就可以了,位運算一般比前面的邏輯高效的多。
但也有缺點,資料庫存儲的都是二進制,可讀性降低了,如果項目交接給其它同僚,還得對于權限空間和權限位置這些字段含義詳細介紹,并且查詢使用到了各種函數轉換,這些字段的索引也失效了
總結:一般對于使用者權限功能個數确定,比如一個供應商有幾十萬門店,使用者、角色、權限數量不多,可以考慮使用二進制來控制權限提升效率、減少存儲資料量。
本文來源于公衆号《百川分享會》:baichuanshare