Python爬蟲之Selenium警告框(彈窗)處理
1. 內容概要
本文主要討論以下兩個問題:
- JavaScript的位運算:先簡單回顧下位運算,平時用的少,相信不少人和我一樣忘的差不多了
- 許可權設計:根據位運算的特點,設計一個許可權系統(新增、刪除、判斷等)
2.JavaScript位運算
2.1. Number
在講位運算之前,首先簡單看下 JavaScript 中的 Number,下文需要用到。
在 JavaScript 裡,數字均為基於 IEEE 754 標準的雙精度 64 位的浮點數,引用維基百科的圖片,它的結構長這樣:
- sign bit(符號): 用來表示正負號
- exponent(指數): 用來表示次方數
- mantissa(尾數): 用來表示精確度
也就是說一個數字的範圍只能在 -(2^53 -1) 至 2^53 -1 之間。
既然講到這裡,就多說一句:0.1 + 0.2 算不準的原因也在於此。浮點數用二進位制表達時是無窮的,且最多 53 位,必須截斷,進而產生誤差。最簡單的解決辦法就是放大一定倍數變成整數,計算完成後再縮小。不過更穩妥的辦法是使用下文將會提到的 math.js等工具庫。
此外還有四種數字進位制:
// 十進位制
123456789
0
// 二進位制:字首 0b,0B
0b10000000000000000000000000000000 // 2147483648
0b01111111100000000000000000000000 // 2139095040
0B00000000011111111111111111111111 // 8388607
// 八進位制:字首 0o,0O(以前支援字首 0)
0o755 // 493
0o644 // 420
// 十六進位制:字首 0x,0X
0xFFFFFFFFFFFFFFFFF // 295147905179352830000
0x123456789ABCDEF // 81985529216486900
0XA // 10
好了,Number 就說這麼多,接下來看 JavaScript 中的位運算。
2.2. 位運算
按位操作符將其運算元當作 32 位的位元序列(由 0 和 1 組成)操作,返回值依然是標準的 JavaScript 數值。JavaScript 中的按位操作符有:
運算子 | 用法 | 描述 |
---|---|---|
按位與(AND) | a & b | 對於每一個位元位,只有兩個運算元相應的位元位都是 1 時,結果才為 1,否則為 0。 |
按位或(OR) | a \| b | 對於每一個位元位,當兩個運算元相應的位元位至少有一個 1 時,結果為 1,否則為 0。 |
按位異或(XOR) | a ^ b | 對於每一個位元位,當兩個運算元相應的位元位有且只有一個 1 時,結果為 1,否則為 0。 |
按位非(NOT) | ~a | 反轉運算元的位元位,即 0 變成 1,1 變成 0。 |
左移(Left shift) | a << b | 將 a 的二進位制形式向左移 b (< 32) 位元位,右邊用 0 填充。 |
有符號右移 | a >> b | 將 a 的二進位制表示向右移 b (< 32) 位,丟棄被移出的位。 |
無符號右移 | a >>> b | 將 a 的二進位制表示向右移 b (< 32) 位,丟棄被移出的位,並使用 0 在左側填充。 |
下面舉幾個例子,主要看下AND和OR:
# 例子1
A = 10001001
B = 10010000
A | B = 10011001
# 例子2
A = 10001001
C = 10001000
A | C = 10001001
# 例子1
A = 10001001
B = 10010000
A & B = 10000000
# 例子2
A = 10001001
C = 10001000
A & C = 10001000
3. 位運算在許可權系統中的使用
傳統的許可權系統裡,存在很多關聯關係,如使用者和許可權的關聯,使用者和角色的關聯。系統越大,關聯關係越多,越難以維護。而引入位運算,可以巧妙的解決該問題。
在講“位運算在許可權系統中的使用”之前,我們先假定兩個前提,下文所有的討論都是基於這兩個前提的:
- 每種許可權碼都是唯一的(這是顯然的)
- 所有許可權碼的二進位制數形式,有且只有一位值為 1,其餘全部為 0(2^n)
如果使用者許可權和許可權碼,全部使用二級制數字表示,再結合上面AND和OR的例子,分析位運算的特點,不難發現:
- |可以用來賦予許可權
- &可以用來校驗許可權
為了講的更明白,這裡用 Linux 中的例項分析下,Linux 的檔案許可權分為讀、寫和執行,有字母和數字等多種表現形式:
許可權 | 字母表示 | 數字表示 | 二進位制 |
---|---|---|---|
讀 | r | 4 | 0b100 |
寫 | w | 2 | 0b010 |
執行 | x | 1 | 0b001 |
可以看到,許可權用 1、2、4(也就是2^n)表示,轉換為二進位制後,都是隻有一位是 1,其餘為 0。我們通過幾個例子看下,如何利用二進位制的特點執行許可權的新增,校驗和刪除。
3.1. 新增許可權
let r = 0b100
let w = 0b010
let x = 0b001
// 給使用者賦全部許可權(使用前面講的 | 操作)
let user = r | w | x
console.log(user)
// 7
console.log(user.toString(2))
// 111
// r = 0b100
// w = 0b010
// r = 0b001
// r|w|x = 0b111
可以看到,執行r | w | x後,user的三位都是 1,表明擁有了全部三個許可權。
Linux 下出現許可權問題時,最粗暴的解決方案就是 chmod 777 xxx,這裡的 7就代表了:可讀,可寫,可執行。而三個 7 分別代表:檔案所有者,檔案所有者所在組,所有其他使用者。
3.2. 校驗許可權
剛才演示了許可權的新增,下面演示許可權校驗:
let r = 0b100
let w = 0b010
let x = 0b001
// 給使用者賦 r w 兩個許可權
let user = r | w
// user = 6
// user = 0b110 (二進位制)
console.log((user & r) === r) // true 有 r 許可權
console.log((user & w) === w) // true 有 w 許可權
console.log((user & x) === x) // false 沒有 x 許可權
如前所料,通過使用者許可權 & 許可權 code === 許可權 code就可以判斷出使用者是否擁有該許可權。
3.3. 刪除許可權
我們講了用|賦予許可權,使用&判斷許可權,那麼刪除許可權呢?刪除許可權的本質其實是將指定位置上的 1 重置為 0。上個例子裡使用者許可權是0b110,擁有讀和寫兩個許可權,現在想刪除讀的許可權,本質上就是將第三位的 1 重置為 0,變為0b010:
let r = 0b100
let w = 0b010
let x = 0b001
let user = 0b010;
console.log((user & r) === r) // false 沒有 r 許可權
console.log((user & w) === w) // true 有 w 許可權
console.log((user & x) === x) // false 沒有 x 許可權
那麼具體怎麼操作呢?其實有兩種方案,最簡單的就是異或^,按照上文的介紹“當兩個運算元相應的位元位有且只有一個 1 時,結果為 1,否則為 0”,所以異或其實是 toggle 操作,無則增,有則減:
let r = 0b100
let w = 0b010
let x = 0b001
let user = 0b110 // 有 r w 兩個許可權
// 執行異或操作,刪除 r 許可權
user = user ^ r
console.log((user & r) === r) // false 沒有 r 許可權
console.log((user & w) === w) // true 有 w 許可權
console.log((user & x) === x) // false 沒有 x 許可權
console.log(user.toString(2)) // 現在 user 是 0b010
// 再執行一次異或操作
user = user ^ r
console.log((user & r) === r) // true 有 r 許可權
console.log((user & w) === w) // true 有 w 許可權
console.log((user & x) === x) // false 沒有 x 許可權
console.log(user.toString(2)) // 現在 user 又變回 0b110
那麼如果單純的想刪除許可權(而不是無則增,有則減)怎麼辦呢?答案是執行&(~code),先取反,再執行與操作:
let r = 0b100
let w = 0b010
let x = 0b001
let user = 0b110 // 有 r w 兩個許可權
// 刪除 r 許可權
user = user & (~r)
console.log((user & r) === r) // false 沒有 r 許可權
console.log((user & w) === w) // true 有 w 許可權
console.log((user & x) === x) // false 沒有 x 許可權
console.log(user.toString(2)) // 現在 user 是 0b010
// 再執行一次
user = user & (~r)
console.log((user & r) === r) // false 沒有 r 許可權
console.log((user & w) === w) // true 有 w 許可權
console.log((user & x) === x) // false 沒有 x 許可權
console.log(user.toString(2)) // 現在 user 還是 0b010,並不會新增
4. 侷限性和解決辦法
前面我們回顧了 JavaScript 中的 Number 和位運算,並且瞭解了基於位運算的許可權系統原理和 Linux 檔案系統許可權的例項。
上述的所有都有前提條件:1、每種許可權碼都是唯一的;2、每個許可權碼的二進位制數形式,有且只有一位值為 1(2^n)。也就是說,許可權碼只能是 1, 2, 4, 8,...,1024,...而上文提到,一個數字的範圍只能在 -(2^53 -1) 和 2^53 -1 之間,JavaScript 的按位操作符又是將其運算元當作32 位位元序列的。那麼同一個應用下可用的許可權數就非常有限了。這也是該方案的侷限性。
為了突破這個限制,這裡提出一個叫“許可權空間”的概念,既然許可權數有限,那麼不妨就多開闢幾個空間來存放。
基於許可權空間,我們定義兩個格式:
- 許可權 code,字串,形如index,pos。其中pos表示 32 位二進位制數中 1 的位置(其餘全是 0);index表示許可權空間,用於突破 JavaScript 數字位數的限制,是從 0 開始的正整數,每個許可權code都要歸屬於一個許可權空間。index和pos使用英文逗號隔開。
- 使用者許可權,字串,形如1,16,16。英文逗號分隔每一個許可權空間的許可權值。例如1,16,16的意思就是,許可權空間 0 的許可權值是 1,許可權空間 1 的許可權值是 16,許可權空間 2 的許可權是 16。
幹說可能不好懂,直接上程式碼:
// 使用者的許可權 code
let userCode = ""
// 假設系統裡有這些許可權
// 純模擬,正常情況下是按順序的,如 0,0 0,1 0,2 ...,儘可能佔滿一個許可權空間,再使用下一個
const permissions = {
SYS_SETTING: {
value: "0,0", // index = 0, pos = 0
info: "系統許可權"
},
DATA_ADMIN: {
value: "0,8",
info: "資料庫許可權"
},
USER_ADD: {
value: "0,22",
info: "使用者新增許可權"
},
USER_EDIT: {
value: "0,30",
info: "使用者編輯許可權"
},
USER_VIEW: {
value: "1,2", // index = 1, pos = 2
info: "使用者檢視許可權"
},
USER_DELETE: {
value: "1,17",
info: "使用者刪除許可權"
},
POST_ADD: {
value: "1,28",
info: "文章新增許可權"
},
POST_EDIT: {
value: "2,4",
info: "文章編輯許可權"
},
POST_VIEW: {
value: "2,19",
info: "文章檢視許可權"
},
POST_DELETE: {
value: "2,26",
info: "文章刪除許可權"
}
}
// 新增許可權
const addPermission = (userCode, permission) => {
const userPermission = userCode ? userCode.split(",") : []
const [index, pos] = permission.value.split(",")
userPermission[index] = (userPermission[index] || 0) | Math.pow(2, pos)
return userPermission.join(",")
}
// 刪除許可權
const delPermission = (userCode, permission) => {
const userPermission = userCode ? userCode.split(",") : []
const [index, pos] = permission.value.split(",")
userPermission[index] = (userPermission[index] || 0) & (~Math.pow(2, pos))
return userPermission.join(",")
}
// 判斷是否有許可權
const hasPermission = (userCode, permission) => {
const userPermission = userCode ? userCode.split(",") : []
const [index, pos] = permission.value.split(",")
const permissionValue = Math.pow(2, pos)
return (userPermission[index] & permissionValue) === permissionValue
}
// 列出使用者擁有的全部許可權
const listPermission = userCode => {
const results = []
if (!userCode) {
return results
}
Object.values(permissions).forEach(permission => {
if (hasPermission(userCode, permission)) {
results.push(permission.info)
}
})
return results
}
const log = () => {
console.log(`userCode: ${jsON.stringify(userCode, null, " ")}`)
console.log(`許可權列表: ${listPermission(userCode).join("; ")}`)
console.log("")
}
userCode = addPermission(userCode, permissions.SYS_SETTING)
log()
// userCode: "1"
// 許可權列表: 系統許可權
userCode = addPermission(userCode, permissions.POST_EDIT)
log()
// userCode: "1,,16"
// 許可權列表: 系統許可權; 文章編輯許可權
userCode = addPermission(userCode, permissions.USER_EDIT)
log()
// userCode: "1073741825,,16"
// 許可權列表: 系統許可權; 使用者編輯許可權; 文章編輯許可權
userCode = addPermission(userCode, permissions.USER_DELETE)
log()
// userCode: "1073741825,131072,16"
// 許可權列表: 系統許可權; 使用者編輯許可權; 使用者刪除許可權; 文章編輯許可權
userCode = delPermission(userCode, permissions.USER_EDIT)
log()
// userCode: "1,131072,16"
// 許可權列表: 系統許可權; 使用者刪除許可權; 文章編輯許可權
userCode = delPermission(userCode, permissions.USER_EDIT)
log()
// userCode: "1,131072,16"
// 許可權列表: 系統許可權; 使用者刪除許可權; 文章編輯許可權
userCode = delPermission(userCode, permissions.USER_DELETE)
userCode = delPermission(userCode, permissions.SYS_SETTING)
userCode = delPermission(userCode, permissions.POST_EDIT)
log()
// userCode: "0,0,0"
// 許可權列表:
userCode = addPermission(userCode, permissions.SYS_SETTING)
log()
// userCode: "1,0,0"
// 許可權列表: 系統許可權
除了通過引入許可權空間的概念突破二進位制運算的位數限制,還可以使用math.js的bignumber,直接運算超過 32 位的二進位制數,具體可以看它的文件,這裡就不細說了。
5. 適用場景和問題
如果按照當前使用最廣泛的RBAC模型設計許可權系統,那麼一般會有這麼幾個實體:應用,許可權,角色,使用者。使用者許可權可以直接來自許可權,也可以來自角色:
- 一個應用下有多個許可權
- 許可權和角色是多對多的關係
- 使用者和角色是多對多的關係
- 使用者和許可權是多對多的關係
在此種模型下,一般會有使用者與許可權,使用者與角色,角色與許可權的對應關係表。想象一個商城後臺許可權管理系統,可能會有上萬,甚至十幾萬店鋪(應用),每個店鋪可能會有數十個使用者,角色,許可權。隨著業務的不斷髮展,剛才提到的那三張對應關係表會越來越大,越來越難以維護。
而進位制轉換的方法則可以省略對應關係表,減少查詢,節省空間。當然,省略掉對應關係不是沒有壞處的,例如下面幾個問題:
- 如何高效的查詢我的許可權?
- 如何高效的查詢擁有某許可權的所有使用者?
- 如何控制權限的有效期?
所以進位制轉換的方案比較適合剛才提到的應用極其多,而每個應用中使用者,許可權,角色數量較少的場景。
資源搜尋網站大全 https://www.renrenfan.com.cn 廣州VI設計公司https://www.houdianzi.com
6. 其他方案
除了二進位制方案,當然還有其他方案可以達到類似的效果,例如直接使用一個1和0組成的字串,許可權點對應index,1表示擁有許可權,0表示沒有許可權。舉個例子:新增 0、刪除 1、編輯 2,使用者A擁有新增和編輯的許可權,則 userCode 為 101;使用者B擁有全部許可權,userCode 為 111。這種方案比二進位制轉換簡單,但是浪費空間。
還有利用質數的方案,許可權點全部為質數,使用者許可權為他所擁有的全部許可權點的乘積。如:許可權點是 2、3、5、7、11,使用者許可權是 5 * 7 * 11 = 385。這種方案麻煩的地方在於獲取質數(新增許可權點)和質因數分解(判斷許可權),許可權點特別多的時候就快成 RSA 了,如果只有增刪改查個別幾個許可權,倒是可以考慮。