react 高效高質量搭建後臺系統 系列 —— 前端許可權

来源:https://www.cnblogs.com/pengjiali/archive/2023/02/16/17127181.html
-Advertisement-
Play Games

其他章節請看: react 高效高質量搭建後臺系統 系列 許可權 本系列已近尾聲,許可權是後臺系統必不可少的一部分,本篇首先分析spug項目中許可權的實現,最後在將許可權加入到我們的項目中來。 spug 中許可權的分析 許可權示例 比如我要將應用發佈模塊的查看許可權分給某用戶(例如 pjl),可以這樣操作: 在角 ...


其他章節請看:

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={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 中的許可權整體思路:

  • 通過一個配置頁面(功能許可權設置彈框)將某些許可權(頁面級和頁內級)賦予某角色,再讓某用戶屬於該角色
  • 用戶登錄成功後返回數據(permissionsis_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
本文版權歸作者和博客園共有,歡迎轉載,但未經作者同意必須保留此段聲明,且在文章頁面明顯位置給出原文連接。

您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 一:背景 1. 講故事 今天和大家聊一套面試中經常被問到的高頻題,對,就是 臨時表 和 表變數 這倆玩意,如果有朋友在面試中回答的不好,可以嘗試看下這篇能不能幫你成功邁過。 二:到底有什麼區別 1. 前置思考 不管是 臨時表 還是 表變數 都帶了 表 這個詞,既然提到了 表 ,按推理自然會落到某一個 ...
  • 作者:京東物流 張廣治 1 背景 傳統的將數據集中存儲至單一數據節點的解決方案,在性能和可用性方面已經難於滿足海量數據的場景,系統最大的瓶頸在於單個節點讀寫性能,許多的資源受到單機的限制,例如連接數、網路IO、磁碟IO等,從而導致它的併發能力不高,對於高併發的要求不滿足。 每到月初國際財務系統壓力巨 ...
  • pdf下載:密碼7281 專欄目錄首頁:【專欄必讀】(考研覆試)資料庫系統概論第五版(王珊)專欄學習筆記目錄導航及課後習題答案詳解 名詞解釋 視圖:視圖是一個虛表,其本質就是一條SELECT語句,而查詢結果被賦予了一個名字,也即視圖名字。或者說視圖本身不包含任何數據,它只包含映射到基表的一個查詢語句 ...
  • 原文地址:Jetpack Compose學習(10)——使用Compose物料清單BOM,更好管理依賴版本 - Stars-One的雜貨小窩 本期講解下關於Android推出的BOM來簡化我們添加compose依賴過於繁雜的問題 本系列以往文章請查看此分類鏈接Jetpack compose學習 介紹 ...
  • 一、鯨鴻動能流量變現服務前置說明 1.接入鯨鴻動能平臺的應用需在應用市場上架。 2.與華為聯運的游戲應用和快游戲禁止接入鯨鴻動能以外的其他廣告內容/插件/SDK等。 3.中國大陸地區僅支持企業認證用戶使用流量變現服務。 4.支持的設備限制: 5.媒體接入流程: 二、媒體服務平臺 數據管理 【首頁】或 ...
  • 現在有不少項目都轉向使用了pnpm,它能幫我們節約磁碟空間並提升包的安裝速度,但是也沒有帶來很多使用習慣上的遷移,npm中的命令在pnpm中大多都是能用的,我們可以方便的切換到pnpm中進行開發。為何不嘗試一下呢?本文就介紹了pnpm中基本的原理,很常用的命令,讓你方便快速的瞭解並使用它。 ...
  • 很多網站都需要添加暗黑模式,怎麼添加呢,有一種簡單的方式 只需在 css 代碼中添加這麼一句 html[theme="dark-mode"] { filter: invert(1) hue-rotate(180deg); } filter:將模糊或顏色偏移等圖形效果應用於元素。濾鏡通常用於調整圖像, ...
  • 這裡給大家分享我在網上總結出來的一些知識,希望對大家有所幫助 前期準備 本篇文章的編寫目的是為了提升TS類型的書寫質量,高質量的類型可以提高項目的可維護性並避免一些潛在的漏洞; 在學習本篇之前需要有一定的TS基礎知識,在此基礎上可以更好的完成各種類型的挑戰,編寫出屬於自己的類型工具; 這裡推薦我之前 ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...