其他章節請看: react 高效高質量搭建後臺系統 系列 許可權 本系列已近尾聲,許可權是後臺系統必不可少的一部分,本篇首先分析spug項目中許可權的實現,最後在將許可權加入到我們的項目中來。 spug 中許可權的分析 許可權示例 比如我要將應用發佈模塊的查看許可權分給某用戶(例如 pjl),可以這樣操作: 在角 ...
其他章節請看:
許可權
本系列已近尾聲,許可權
是後臺系統必不可少的一部分,本篇首先分析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)配置:
如果將其改為可配置,將減小這部分的工作。
其他章節請看:
出處:https://www.cnblogs.com/pengjiali/p/17127181.html
本文版權歸作者和博客園共有,歡迎轉載,但未經作者同意必須保留此段聲明,且在文章頁面明顯位置給出原文連接。