其他章節請看:
許可權
本系列已近尾聲,許可權
是後臺系統必不可少的一部分,本篇首先分析spug專案中許可權的實現,最後在將許可權加入到我們的專案中來。
spug 中許可權的分析
許可權示例
比如我要將應用釋出
模組的檢視
許可權分給某使用者(例如 pjl
),可以這樣操作:
-
在角色管理中新建一角色(例如
demo
),然後給該角色配置許可權:
-
新建使用者(
pjl
)並賦予其 demo 許可權
-
pjl 登入後就只能看到自己有許可權的頁面和操作:
入口
上述示例中,以 pjl 登入成功後返回如下資料:
{
"data": {
"id": 2,
"access_token": "74b0fe67d09646ee9ca44fc48c6b457a",
"nickname": "pjl",
"is_supper": false,
"has_real_ip": true,
"permissions": [
"deploy.app.view",
"deploy.repository.view"
]
},
"error": ""
}
其中 permissions 表示該使用者的許可權。
vscode 搜尋 deploy.app.view
有兩處:
- 釋出配置(也是“應用管理”)頁面入口 index.js。如果沒有檢視許可權(
deploy.app.view
)就不展示該頁面內容。
// spug\src\pages\deploy\app\index.js
export default observer(function () {
return (
<AuthDiv auth="deploy.app.view">
<Breadcrumb>
<Breadcrumb.Item>首頁</Breadcrumb.Item>
<Breadcrumb.Item>應用釋出</Breadcrumb.Item>
<Breadcrumb.Item>應用管理</Breadcrumb.Item>
</Breadcrumb>
<ComTable/>
</AuthDiv>
);
})
Tip:AuthDiv 用於控制頁內元件的許可權,裡面透過 hasPermission 判斷是否有對應的許可權,如果沒有,該元件內的子元素則不會顯示。
export default function AuthDiv(props) {
let disabled = props.disabled === undefined ? false : props.disabled;
if (props.auth && !hasPermission(props.auth)) {
disabled = true;
}
return disabled ? null : <div {...props}>{props.children}</div>
}
// 前端頁面的許可權判斷(僅作為前端功能展示的控制,具體許可權控制應在後端實現)
export function hasPermission(strCode) {
const {isSuper, permissions} = Permission;
if (!strCode || isSuper) return true;
for (let or_item of strCode.split('|')) {
if (isSubArray(permissions, or_item.split('&'))) {
return true
}
}
return false
}
- routes.js。裡面定義的就是選單(詳見左側導航)。
auth
指授權,這裡定義的是模組和頁面的檢視許可權。其中deploy.app.view|deploy.repository.view|deploy.request.view
表示只要有其中一個許可權,“應用釋出”就會展示。
// spug\src\routes.js
{
icon: <FlagOutlined />, title: '應用釋出', auth: 'deploy.app.view|deploy.repository.view|deploy.request.view', child: [
{ title: '應用管理', auth: 'deploy.app.view', path: '/deploy/app', component: DeployApp },
{ title: '構建倉庫', auth: 'deploy.repository.view', path: '/deploy/repository', component: DeployRepository },
{ title: '釋出申請', auth: 'deploy.request.view', path: '/deploy/request', component: DeployRequest },
]
},
頁面級的許可權
現在我們已經知道頁面級的許可權解決思路:
- 在定義選單的地方(routes.js)透過 auth 定義
檢視
(spug 中是xx.xx.view
)許可權。如果沒有 auth 則說明該頁面任何人都可以訪問 - 組裝選單時僅取出有許可權的選單頁面
// spug\src\layout\Sider.js
function makeMenu(menu) {
// 僅取出有許可權的選單
if (menu.auth && !hasPermission(menu.auth)) return null;
if (!menu.title) return null;
return menu.child ? _makeSubMenu(menu) : _makeItem(menu)
}
如果直接透過 url 去訪問沒有許可權的頁面會如何:
直接 404。
為什麼是 404?
因為路由陣列(Routes)中只取了有許可權的選單頁面。
// spug\src\layout\index.js
<Switch>
{/* 路由陣列。裡面每項類似這樣:<Route exact key={route.path} path='/home' component={HomeComponent}/> */}
{Routes}
{/* 沒有匹配則進入 NotFound */}
<Route component={NotFound}/>
</Switch>
Routes 的內容請看路由初始化方法:
// 將 routes 中有許可權的路由提取到 Routes 中
function initRoutes(Routes, routes) {
for (let route of routes) {
if (route.component) {
// 如果不需要許可權,或有許可權則放入 Routes
if (!route.auth || hasPermission(route.auth)) {
Routes.push(<Route exact key={route.path} path={route.path} component={route.component}/>)
}
} else if (route.child) {
initRoutes(Routes, route.child)
}
}
}
頁內級許可權
新增、刪除等頁內的許可權如何實現的呢?我們把釋出配置
頁內的新建
許可權放開,後端許可權返回列表中將有: deploy.app.add
vscode 只搜尋到一處匹配deploy.app.add
。
<TableCard
tKey="da"
...
actions={[
<AuthButton
auth="deploy.app.add"
type="primary"
icon={<PlusOutlined/>}
onClick={() => store.showForm()}>新建</AuthButton>
]}
其中 AuthButton 和上文的 AuthDiv
一樣,如果有許可權則顯示其中內容
// spug\src\components\AuthButton.js
export default function AuthButton(props) {
let disabled = props.disabled;
if (props.auth && !hasPermission(props.auth)) {
disabled = true;
}
return disabled ? null : <Button {...props}>{props.children}</Button>
}
於是我們知道頁內級的許可權解決思路:
- 在
功能許可權設定
中定義對應操作許可權的值,選中後把該值傳遞給後端
許可權的值在 codes.js 中定義如下:
// codes.js
{
key: 'deploy',
label: '應用釋出',
pages: [
{
key: 'app',
label: '應用管理',
perms: [
{ key: 'view', label: '檢視應用' },
{ key: 'add', label: '新建應用' },
{ key: 'edit', label: '編輯應用' },
{ key: 'del', label: '刪除應用' },
{ key: 'config', label: '檢視配置' },
]
},
{
key: 'repository',
label: '構建倉庫',
perms: [
{ key: 'view', label: '檢視構建' },
{ key: 'add', label: '新建版本' },
]
},
...
]
},
- 透過 AuthDiv、AuthButton等元件的 auth 匹配,如果沒有許可權則不展示該元件內容。
isSuper
登入後返回許可權中還有個欄位 is_supper
。資料格式就像這樣:
{
"data": {
"id": 1,
"access_token": "6a0cef69a7d64b6f8c755ff341266e76",
"nickname": "管理員",
"is_supper": true,
"has_real_ip": true,
"permissions": [ ]
},
"error": ""
}
is_supper 是 true,permissions 為空陣列。猜測 is_supper 的作用有2個:
- 一個用處(vscode 搜尋
isSuper
)直接返回有許可權,無需其他判斷。就像這樣:
// 前端頁面的許可權判斷(僅作為前端功能展示的控制,具體許可權控制應在後端實現)
export function hasPermission(strCode) {
const {isSuper, permissions} = Permission;
if (!strCode || isSuper) return true;
for (let or_item of strCode.split('|')) {
if (isSubArray(permissions, or_item.split('&'))) {
return true
}
}
return false
}
- 一個用處(vscode 搜尋
is_supper
)是作為最大的許可權再做一些頁面上的操作。例如這裡隱藏該片段:
<Form.Item hidden={store.record.is_supper} label="角色" style={{marginBottom: 0}}>
<Form.Item name="role_ids" style={{display: 'inline-block', width: '80%'}} extra="許可權最大化原則,組合多個角色許可權。">
<Select mode="multiple" placeholder="請選擇">
{roleStore.records.map(item => (
<Select.Option value={item.id} key={item.id}>{item.name}</Select.Option>
))}
</Select>
</Form.Item>
<Form.Item style={{display: 'inline-block', width: '20%', textAlign: 'right'}}>
<Link to="/system/role">新建角色</Link>
</Form.Item>
</Form.Item>
myspug 中許可權的實現
需求
spug 中的許可權整體思路:
- 透過一個配置頁面(
功能許可權設定
彈框)將某些許可權(頁面級和頁內級)賦予某角色,再讓某使用者屬於該角色 - 使用者登入成功後返回資料(
permissions
、is_supper
欄位)告訴前端該使用者的許可權 - 前端在組裝選單和路由時只取有許可權的,就這樣解決了頁面級的許可權問題
- 再透過封裝的 AuthDiv、AuthButton 等元件巢狀頁內級許可權,例如新建,如果沒有許可權則不顯示
所以許可權這裡包含兩條線:
- 一條是使用者透過配置頁面,將許可權賦予某使用者,儲存在後端資料庫
- 另一條是使用者登入後,根據後端返回的許可權顯示有許可權檢視的頁面和操作
需求
:使用者沒有以下三個許可權(報警聯絡人
、報警聯絡組
、角色管理中的新增
)。見下圖3個紅框:
實現效果
最終效果如下:
程式碼實現
頁面級許可權配置
在 routes.js 中定義每個選單(也是路由)的許可權:
export default [
// 無需許可權
{ icon: <DesktopOutlined />, title: '工作臺', path: '/home', component: HomeIndex },
{
icon: <AlertOutlined />, title: '報警中心', auth: 'alarm.alarm.view|alarm.contact.view|alarm.group.view', child: [
{ title: '報警歷史', auth: 'alarm.alarm.view', path: '/alarm/alarm', component: AlarmCenter },
{ title: '報警聯絡人', auth: 'alarm.contact.view', path: '/alarm/contact', component: AlarmCenter },
{ title: '報警聯絡組', auth: 'alarm.group.view', path: '/alarm/group', component: AlarmCenter },
]
},
{
icon: <AlertOutlined />, title: '系統管理', auth: "system.role.view", child: [
{ title: '角色管理', auth: 'system.role.view', path: '/system/role', component: SystemRole },
]
},
{ path: '/welcome/info', component: WelcomeInfo },
]
Tip:例如角色管理
頁面需要system.role.view
這個許可權。命名任意,因為後端返回的許可權是前端傳送給的後端。
hasPermission
更新許可權判斷邏輯。之前統統返回 true:
// 之前
export function hasPermission(strCode) {
return true
}
現在看後端是否返回該許可權:
// 前端頁面的許可權判斷(僅作為前端功能展示的控制,具體許可權控制應在後端實現)
export function hasPermission(strCode) {
const { isSuper, permissions } = Permission;
if (!strCode || isSuper) return true;
for (let or_item of strCode.split('|')) {
if (isSubArray(permissions, or_item.split('&'))) {
return true
}
}
return false
}
// 陣列包含關係判斷
export function isSubArray(parent, child) {
for (let item of child) {
if (!parent.includes(item.trim())) {
return false
}
}
return true
}
頁內級許可權元件
定義兩個元件(AuthButton、AuthDiv),並匯出:
import React from 'react';
import { Button } from 'antd';
import { hasPermission } from '@/libs';
export default function AuthButton(props) {
let disabled = props.disabled;
if (props.auth && !hasPermission(props.auth)) {
disabled = true;
}
return disabled ? null : <Button {...props}>{props.children}</Button>
}
import React from 'react';
import { hasPermission } from '@/libs';
export default function AuthDiv(props) {
let disabled = props.disabled === undefined ? false : props.disabled;
if (props.auth && !hasPermission(props.auth)) {
disabled = true;
}
return disabled ? null : <div {...props}>{props.children}</div>
}
// myspug\src\compoments\index.js
import NotFound from './NotFound';
import TableCard from './TableCard';
import AuthButton from './AuthButton'
import AuthDiv from './AuthDiv'
export {
NotFound,
TableCard,
AuthButton,
AuthDiv,
}
例如新建按鈕就放入 AuthButton 元件中,而整個角色管理的入口模組就放入 AuthDiv 中。
組裝
- mock登入後的資料:
// mock/index.js
{
"data": {
"id": 2,
"access_token": "74b0fe67d09646ee9ca44fc48c6b457a",
"nickname": "pjl",
"is_supper": false,
"has_real_ip": true,
"permissions": ["system.role.view", "alarm.alarm.view"]
},
"error": ""
}
- 角色管理入口頁放入 AuthDiv:
// myspug\src\pages\system\role\index.js
export default function () {
return (
<AuthDiv auth="system.role.view">
<ComTable />
</AuthDiv>
)
}
- 新增按鈕放入 AuthButton:
// myspug\src\pages\system\role\Table.js
<TableCard
rowKey="id"
title="角色列表"
actions={[
<AuthButton type="primary" icon={<PlusOutlined/>} auth="system.role.add" >新增</AuthButton>
]}
擴充套件
spug 中許可權的缺點
spug 中許可權雖然簡單,其中一個缺點是每開發一個新模組,就得更新許可權配置頁面(即功能許可權設定
)和路由(routes.js)配置:
如果將其改為可配置,將減小這部分的工作。
其他章節請看: