其他章节请看:

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 高效高质量搭建后台系统 系列

react 高效高质量搭建后台系统 系列 —— 前端权限的更多相关文章

  1. react 高效高质量搭建后台系统 系列 —— 脚手架搭建

    其他章节请看: react 高效高质量搭建后台系统 系列 脚手架搭建 本篇主要创建新项目 myspug,以及准备好环境(例如:安装 spug 中用到的包.本地开发和部署.自定义配置 react-app ...

  2. react 高效高质量搭建后台系统 系列 —— 请求数据

    其他章节请看: react 高效高质量搭建后台系统 系列 请求数据 后续要做登录模块(主页),需要先和后端约定JSON数据格式,将 axios 进行封装,实现本地的数据模拟 mockjs. Tip:s ...

  3. react 高效高质量搭建后台系统 系列 —— antd和样式

    其他章节请看: react 高效高质量搭建后台系统 系列 antd 后续要做登录模块(主页),不仅要解决请求数据的问题,还需要完成 antd 配置以及样式的准备. antd 多种主题风格 详情请看 这 ...

  4. react 高效高质量搭建后台系统 系列 —— 登录

    其他章节请看: react 高效高质量搭建后台系统 系列 登录 本篇将完成登录模块.效果和 spug 相同: 需求如下: 登录页的绘制 支持普通登录和LDAP登录 登录成功后跳转到主页,没有登录的情况 ...

  5. react 高效高质量搭建后台系统 系列 —— 系统布局

    其他章节请看: react 高效高质量搭建后台系统 系列 系统布局 前面我们用脚手架搭建了项目,并实现了登录模块,登录模块所依赖的请求数据和antd(ui框架和样式)也已完成. 本篇将完成系统布局.比 ...

  6. 《编写高质量代码--Web前端开发修炼之道》读书笔记

    前言 这两周参加公司的新项目,采用封闭式开发(项目成员在会议室里开发),晚上加班到很晚,所以没时间和精力写原创博客了,今天就分享下这篇<编写高质量代码--Web前端开发修炼之道>读书笔记吧 ...

  7. 使用vue1.0+es6+vue-cli+webpack+iview-ui+jQuery 撸一套高质量的后台管理系统

    首先按照vue.js官网的指令安装: 1.本地安装好node.js 2.根据官方命令行工具 详情 这样一个官方的脚手架工具就已经搭建好了:但是有一点需要注意的是由于现在按照官方的搭建方法是搭建vue2 ...

  8. 建站集成软件包 XAMPP搭建后台系统与微信小程序开发

    下载安装XAMPP软件,运行Apache和MySQL 查看项目文件放在哪个位置可以正常运行 然后访问localhost即可 下载weiphp官网的weiapp(专为微信小程序开发使用)放在htdocs ...

  9. 编写高质量的Python代码系列(八)之部署

    Python提供了一些工具,使我们可以把软件部署到不同的环境中.它也提供了一些模块,令开发者可以把程序编写的更加健壮.本章讲解如何使用Python调试.优化并测试程序,以提升其质量与性能. 第五十四条 ...

  10. 编写高质量的Python代码系列(一)之用Pythonic方式来思考

    Python开发者用Pythonic这个形容词来描述具有特定风格的代码.这种风格是大家在使用Python语言进行编程并相互协作的过程中逐渐形成的习惯.那么,如何以改风格完成常见的Python编程工作呢 ...

随机推荐

  1. <三>使用类模板实现STL Vector

    使用类模板实现STL Vector,点击查看代码 #include <iostream> using namespace std; template<typename T> c ...

  2. 3.5:基于Python的KNN算法简单实现

    〇.目标 1.使用pycharm工具创建项目demo: 2.使用python语言实现KNN算法. 一.创建脚本文件 二.编写KNN算法程序 KNN算法所阐述的核心思想在KNN.py文件的注释部分具有详 ...

  3. windows安装grunt时提示不是内部或外部命令解决方案

    参考:https://www.cnblogs.com/hts-technology/p/8477258.html 安装windows安装elasticsearch-head时 不需要输入grunt s ...

  4. 【知识体系】Kafka文档汇总、组成及架构,配置,常见名词解释,命令行及api操作,官方文档内容,各部分深入,zookeeper和security,监控和运维

    〇.相关资料 1.快速搭建文档: 2.详细讲义 3.在线官方文档:http://kafka.apache.org/documentation/ 4.Kafka知识个人总结 5.KafkaPPT汇报 链 ...

  5. 彻底理解Python中的闭包和装饰器(上)

    什么是闭包 闭包(Closure)其实并不是Python独有的特性,很多语言都有对闭包的支持.(当然,因为Python是笔者除C/C++之外学习的第二门语言,所以也是第一次遇到闭包.)简而言之,闭包实 ...

  6. 学习ASP.NET Core Blazor编程系列十六——排序

    学习ASP.NET Core Blazor编程系列文章之目录 学习ASP.NET Core Blazor编程系列一--综述 学习ASP.NET Core Blazor编程系列二--第一个Blazor应 ...

  7. 注意看,她叫小美,在地址栏输入URL地址后发生了什么?

    注意看,这个用户叫小美,他在地址栏输入了一串URL地址,然后竟然发生了不可思议的事情! 01.输入URL发生了什么? 从输入URL开始,到页面呈现出来,简单来说分为四个步骤: ① 建立连接:建立与服务 ...

  8. go语言的切片特性

    概述: 在使用切片进行赋值的时候,产生新的数组的指针指向原来的数组,只要修改新数组中的元素时,原来数组的元素也会改变. 测试代码: func TestSliceShareMemory(t *testi ...

  9. python实验报告(第12章)

    实验12:GUI界面编程 一.实验目的和要求 1.学会应用常用控件: 2.学会使用BoxSizer布局: 3.学会事件处理. 二.实验环境 软件版本:Python 3.10 64_bit 三.实验过程 ...

  10. ArcGIS工具 - 统计要素数量

    查询和统计是GIS中的重要功能之一.在ArcGIS中可以按属性信息.按空间位置关系进行查询和统计.今天为源GIS给大家分享使用ArcPy编程实现批量统计地理数据库要素类记录数量. 软件应用 统计单个图 ...