其他章節請看:
react 高效高質量搭建后臺系統 系列
權限
本系列已近尾聲,權限是后臺系統必不可少的一部分,本篇首先分析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=https://www.cnblogs.com/pengjiali/archive/2023/02/16/{item.id} key={item.id}>{item.name}
))}
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)配置:

如果將其改為可配置,將減小這部分的作業,
其他章節請看:
react 高效高質量搭建后臺系統 系列
出處:https://www.cnblogs.com/pengjiali/p/17127181.html
本文著作權歸作者和博客園共有,歡迎轉載,但未經作者同意必須保留此段宣告,且在文章頁面明顯位置給出原文連接,
轉載請註明出處,本文鏈接:https://www.uj5u.com/qiye/544138.html
標籤:其他
上一篇:Vue 組件之間傳遞引數
