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
)就不展示该页面内容。
// spugsrcpagesdeployappindex.js
export default observer(function () {
return (
首页
应用发布
应用管理
);
})
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 : {props.children}
}
// 前端页面的权限判断(仅作为前端功能展示的控制,具体权限控制应在后端实现)
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
表示只要有其中一个权限,“应用发布”就会展示。
// spugsrcroutes.js
{
icon: , 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 则说明该页面任何人都可以访问 - 组装菜单时仅取出有权限的菜单页面
// spugsrclayoutSider.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)中只取了有权限的菜单页面。
// spugsrclayoutindex.js
{/* 路由数组。里面每项类似这样: */}
{Routes}
{/* 没有匹配则进入 NotFound */}
Routes 的内容请看路由初始化方法:
// 将 routes 中有权限的路由提取到 Routes 中
function initRoutes(Routes, routes) {
for (let route of routes) {
if (route.component) {
// 如果不需要权限,或有权限则放入 Routes
if (!route.auth || hasPermission(route.auth)) {
Routes.push()
}
} else if (route.child) {
initRoutes(Routes, route.child)
}
}
}
页内级权限
新增、删除等页内的权限如何实现的呢?我们把发布配置
页内的新建
权限放开,后端权限返回列表中将有: deploy.app.add
vscode 只搜索到一处匹配deploy.app.add
。
}
onClick={() => store.showForm()}>新建
]}
其中 AuthButton 和上文的 AuthDiv
一样,如果有权限则显示其中内容
// spugsrccomponentsAuthButton.js
export default function AuthButton(props) {
let disabled = props.disabled;
if (props.auth && !hasPermission(props.auth)) {
disabled = true;
}
return disabled ? null :
}
于是我们知道页内级的权限解决思路:
- 在
功能权限设置
中定义对应操作权限的值,选中后把该值传递给后端
权限的值在 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
)是作为最大的权限再做一些页面上的操作。例如这里隐藏该片段:
新建角色
myspug 中权限的实现
需求
spug 中的权限整体思路:
- 通过一个配置页面(
功能权限设置
弹框)将某些权限(页面级和页内级)赋予某角色,再让某用户属于该角色 - 用户登录成功后返回数据(
permissions
、is_supper
字段)告诉前端该用户的权限 - 前端在组装菜单和路由时只取有权限的,就这样解决了页面级的权限问题
- 再通过封装的 AuthDiv、AuthButton 等组件嵌套页内级权限,例如新建,如果没有权限则不显示
所以权限这里包含两条线:
- 一条是用户通过配置页面,将权限赋予某用户,保存在后端数据库
- 另一条是用户登录后,根据后端返回的权限显示有权限查看的页面和操作
需求
:用户没有以下三个权限(报警联系人
、报警联系组
、角色管理中的新增
)。见下图3个红框:
实现效果
最终效果如下:
代码实现
页面级权限配置
在 routes.js 中定义每个菜单(也是路由)的权限:
export default [
// 无需权限
{ icon: , title: '工作台', path: '/home', component: HomeIndex },
{
icon: , 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: , 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 :
}
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 : {props.children}
}
// myspugsrccompomentsindex.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:
// myspugsrcpagessystemroleindex.js
export default function () {
return (
)
}
- 新增按钮放入 AuthButton:
// myspugsrcpagessystemroleTable.js
} auth="system.role.add" >新增
]}
扩展
spug 中权限的缺点
spug 中权限虽然简单,其中一个缺点是每开发一个新模块,就得更新权限配置页面(即功能权限设置
)和路由(routes.js)配置:
如果将其改为可配置,将减小这部分的工作。
其他章节请看:
出处:https://www.cnblogs.com/pengjiali/p/17127181.html
本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接。
文章来源于互联网:react 高效高质量搭建后台系统 系列 —— 前端权限