经验首页 前端设计 程序设计 Java相关 移动开发 数据库/运维 软件/图像 大数据/云计算 其他经验
当前位置:技术经验 » JS/JS库/框架 » React » 查看文章
react 高效高质量搭建后台系统 系列 —— 前端权限
来源:cnblogs  作者:彭加李  时间:2023/2/17 10:00:56  对本文有异议

其他章节请看:

react 高效高质量搭建后台系统 系列

权限

本系列已近尾声,权限是后台系统必不可少的一部分,本篇首先分析spug项目中权限的实现,最后在将权限加入到我们的项目中来。

spug 中权限的分析

权限示例

比如我要将应用发布模块的查看权限分给某用户(例如 pjl),可以这样操作:

  • 在角色管理中新建一角色(例如 demo),然后给该角色配置权限:

  • 新建用户(pjl)并赋予其 demo 权限

  • pjl 登录后就只能看到自己有权限的页面和操作:

入口

上述示例中,以 pjl 登录成功后返回如下数据:

  1. {
  2. "data": {
  3. "id": 2,
  4. "access_token": "74b0fe67d09646ee9ca44fc48c6b457a",
  5. "nickname": "pjl",
  6. "is_supper": false,
  7. "has_real_ip": true,
  8. "permissions": [
  9. "deploy.app.view",
  10. "deploy.repository.view"
  11. ]
  12. },
  13. "error": ""
  14. }

其中 permissions 表示该用户的权限。

vscode 搜索 deploy.app.view 有两处:

  • 发布配置(也是“应用管理”)页面入口 index.js。如果没有查看权限(deploy.app.view)就不展示该页面内容。
  1. // spug\src\pages\deploy\app\index.js
  2. export default observer(function () {
  3. return (
  4. <AuthDiv auth="deploy.app.view">
  5. <Breadcrumb>
  6. <Breadcrumb.Item>首页</Breadcrumb.Item>
  7. <Breadcrumb.Item>应用发布</Breadcrumb.Item>
  8. <Breadcrumb.Item>应用管理</Breadcrumb.Item>
  9. </Breadcrumb>
  10. <ComTable/>
  11. </AuthDiv>
  12. );
  13. })

Tip:AuthDiv 用于控制页内组件的权限,里面通过 hasPermission 判断是否有对应的权限,如果没有,该组件内的子元素则不会显示。

  1. export default function AuthDiv(props) {
  2. let disabled = props.disabled === undefined ? false : props.disabled;
  3. if (props.auth && !hasPermission(props.auth)) {
  4. disabled = true;
  5. }
  6. return disabled ? null : <div {...props}>{props.children}</div>
  7. }
  1. // 前端页面的权限判断(仅作为前端功能展示的控制,具体权限控制应在后端实现)
  2. export function hasPermission(strCode) {
  3. const {isSuper, permissions} = Permission;
  4. if (!strCode || isSuper) return true;
  5. for (let or_item of strCode.split('|')) {
  6. if (isSubArray(permissions, or_item.split('&'))) {
  7. return true
  8. }
  9. }
  10. return false
  11. }
  • routes.js。里面定义的就是菜单(详见左侧导航)。auth指授权,这里定义的是模块和页面的查看权限。其中deploy.app.view|deploy.repository.view|deploy.request.view表示只要有其中一个权限,“应用发布”就会展示。
  1. // spug\src\routes.js
  2. {
  3. icon: <FlagOutlined />, title: '应用发布', auth: 'deploy.app.view|deploy.repository.view|deploy.request.view', child: [
  4. { title: '应用管理', auth: 'deploy.app.view', path: '/deploy/app', component: DeployApp },
  5. { title: '构建仓库', auth: 'deploy.repository.view', path: '/deploy/repository', component: DeployRepository },
  6. { title: '发布申请', auth: 'deploy.request.view', path: '/deploy/request', component: DeployRequest },
  7. ]
  8. },

页面级的权限

现在我们已经知道页面级的权限解决思路:

  • 在定义菜单的地方(routes.js)通过 auth 定义查看(spug 中是 xx.xx.view)权限。如果没有 auth 则说明该页面任何人都可以访问
  • 组装菜单时仅取出有权限的菜单页面
  1. // spug\src\layout\Sider.js
  2. function makeMenu(menu) {
  3. // 仅取出有权限的菜单
  4. if (menu.auth && !hasPermission(menu.auth)) return null;
  5. if (!menu.title) return null;
  6. return menu.child ? _makeSubMenu(menu) : _makeItem(menu)
  7. }

如果直接通过 url 去访问没有权限的页面会如何:

直接 404。

为什么是 404?因为路由数组(Routes)中只取了有权限的菜单页面。

  1. // spug\src\layout\index.js
  2. <Switch>
  3. {/* 路由数组。里面每项类似这样:<Route exact key={route.path} path='/home' component={HomeComponent}/> */}
  4. {Routes}
  5. {/* 没有匹配则进入 NotFound */}
  6. <Route component={NotFound}/>
  7. </Switch>

Routes 的内容请看路由初始化方法:

  1. // 将 routes 中有权限的路由提取到 Routes 中
  2. function initRoutes(Routes, routes) {
  3. for (let route of routes) {
  4. if (route.component) {
  5. // 如果不需要权限,或有权限则放入 Routes
  6. if (!route.auth || hasPermission(route.auth)) {
  7. Routes.push(<Route exact key={route.path} path={route.path} component={route.component}/>)
  8. }
  9. } else if (route.child) {
  10. initRoutes(Routes, route.child)
  11. }
  12. }
  13. }

页内级权限

新增、删除等页内的权限如何实现的呢?我们把发布配置页内的新建权限放开,后端权限返回列表中将有: deploy.app.add

vscode 只搜索到一处匹配deploy.app.add

  1. <TableCard
  2. tKey="da"
  3. ...
  4. actions={[
  5. <AuthButton
  6. auth="deploy.app.add"
  7. type="primary"
  8. icon={<PlusOutlined/>}
  9. onClick={() => store.showForm()}>新建</AuthButton>
  10. ]}

其中 AuthButton 和上文的 AuthDiv 一样,如果有权限则显示其中内容

  1. // spug\src\components\AuthButton.js
  2. export default function AuthButton(props) {
  3. let disabled = props.disabled;
  4. if (props.auth && !hasPermission(props.auth)) {
  5. disabled = true;
  6. }
  7. return disabled ? null : <Button {...props}>{props.children}</Button>
  8. }

于是我们知道页内级的权限解决思路:

  • 功能权限设置中定义对应操作权限的值,选中后把该值传递给后端

权限的值在 codes.js 中定义如下:

  1. // codes.js
  2. {
  3. key: 'deploy',
  4. label: '应用发布',
  5. pages: [
  6. {
  7. key: 'app',
  8. label: '应用管理',
  9. perms: [
  10. { key: 'view', label: '查看应用' },
  11. { key: 'add', label: '新建应用' },
  12. { key: 'edit', label: '编辑应用' },
  13. { key: 'del', label: '删除应用' },
  14. { key: 'config', label: '查看配置' },
  15. ]
  16. },
  17. {
  18. key: 'repository',
  19. label: '构建仓库',
  20. perms: [
  21. { key: 'view', label: '查看构建' },
  22. { key: 'add', label: '新建版本' },
  23. ]
  24. },
  25. ...
  26. ]
  27. },
  • 通过 AuthDiv、AuthButton等组件的 auth 匹配,如果没有权限则不展示该组件内容。

isSuper

登录后返回权限中还有个字段 is_supper。数据格式就像这样:

  1. {
  2. "data": {
  3. "id": 1,
  4. "access_token": "6a0cef69a7d64b6f8c755ff341266e76",
  5. "nickname": "管理员",
  6. "is_supper": true,
  7. "has_real_ip": true,
  8. "permissions": [ ]
  9. },
  10. "error": ""
  11. }

is_supper 是 true,permissions 为空数组。猜测 is_supper 的作用有2个:

  • 一个用处(vscode 搜索 isSuper)直接返回有权限,无需其他判断。就像这样:
  1. // 前端页面的权限判断(仅作为前端功能展示的控制,具体权限控制应在后端实现)
  2. export function hasPermission(strCode) {
  3. const {isSuper, permissions} = Permission;
  4. if (!strCode || isSuper) return true;
  5. for (let or_item of strCode.split('|')) {
  6. if (isSubArray(permissions, or_item.split('&'))) {
  7. return true
  8. }
  9. }
  10. return false
  11. }
  • 一个用处(vscode 搜索 is_supper)是作为最大的权限再做一些页面上的操作。例如这里隐藏该片段:
  1. <Form.Item hidden={store.record.is_supper} label="角色" style={{marginBottom: 0}}>
  2. <Form.Item name="role_ids" style={{display: 'inline-block', width: '80%'}} extra="权限最大化原则,组合多个角色权限。">
  3. <Select mode="multiple" placeholder="请选择">
  4. {roleStore.records.map(item => (
  5. <Select.Option value={item.id} key={item.id}>{item.name}</Select.Option>
  6. ))}
  7. </Select>
  8. </Form.Item>
  9. <Form.Item style={{display: 'inline-block', width: '20%', textAlign: 'right'}}>
  10. <Link to="/system/role">新建角色</Link>
  11. </Form.Item>
  12. </Form.Item>

myspug 中权限的实现

需求

spug 中的权限整体思路:

  • 通过一个配置页面(功能权限设置弹框)将某些权限(页面级和页内级)赋予某角色,再让某用户属于该角色
  • 用户登录成功后返回数据(permissionsis_supper字段)告诉前端该用户的权限
  • 前端在组装菜单和路由时只取有权限的,就这样解决了页面级的权限问题
  • 再通过封装的 AuthDiv、AuthButton 等组件嵌套页内级权限,例如新建,如果没有权限则不显示

所以权限这里包含两条线:

  • 一条是用户通过配置页面,将权限赋予某用户,保存在后端数据库
  • 另一条是用户登录后,根据后端返回的权限显示有权限查看的页面和操作

需求:用户没有以下三个权限(报警联系人报警联系组、角色管理中的新增)。见下图3个红框:

实现效果

最终效果如下:

代码实现

页面级权限配置

在 routes.js 中定义每个菜单(也是路由)的权限:

  1. export default [
  2. // 无需权限
  3. { icon: <DesktopOutlined />, title: '工作台', path: '/home', component: HomeIndex },
  4. {
  5. icon: <AlertOutlined />, title: '报警中心', auth: 'alarm.alarm.view|alarm.contact.view|alarm.group.view', child: [
  6. { title: '报警历史', auth: 'alarm.alarm.view', path: '/alarm/alarm', component: AlarmCenter },
  7. { title: '报警联系人', auth: 'alarm.contact.view', path: '/alarm/contact', component: AlarmCenter },
  8. { title: '报警联系组', auth: 'alarm.group.view', path: '/alarm/group', component: AlarmCenter },
  9. ]
  10. },
  11. {
  12. icon: <AlertOutlined />, title: '系统管理', auth: "system.role.view", child: [
  13. { title: '角色管理', auth: 'system.role.view', path: '/system/role', component: SystemRole },
  14. ]
  15. },
  16. { path: '/welcome/info', component: WelcomeInfo },
  17. ]

Tip:例如角色管理页面需要system.role.view这个权限。命名任意,因为后端返回的权限是前端发送给的后端。

hasPermission

更新权限判断逻辑。之前统统返回 true:

  1. // 之前
  2. export function hasPermission(strCode) {
  3. return true
  4. }

现在看后端是否返回该权限:

  1. // 前端页面的权限判断(仅作为前端功能展示的控制,具体权限控制应在后端实现)
  2. export function hasPermission(strCode) {
  3. const { isSuper, permissions } = Permission;
  4. if (!strCode || isSuper) return true;
  5. for (let or_item of strCode.split('|')) {
  6. if (isSubArray(permissions, or_item.split('&'))) {
  7. return true
  8. }
  9. }
  10. return false
  11. }
  12. // 数组包含关系判断
  13. export function isSubArray(parent, child) {
  14. for (let item of child) {
  15. if (!parent.includes(item.trim())) {
  16. return false
  17. }
  18. }
  19. return true
  20. }
页内级权限组件

定义两个组件(AuthButton、AuthDiv),并导出:

  1. import React from 'react';
  2. import { Button } from 'antd';
  3. import { hasPermission } from '@/libs';
  4. export default function AuthButton(props) {
  5. let disabled = props.disabled;
  6. if (props.auth && !hasPermission(props.auth)) {
  7. disabled = true;
  8. }
  9. return disabled ? null : <Button {...props}>{props.children}</Button>
  10. }
  1. import React from 'react';
  2. import { hasPermission } from '@/libs';
  3. export default function AuthDiv(props) {
  4. let disabled = props.disabled === undefined ? false : props.disabled;
  5. if (props.auth && !hasPermission(props.auth)) {
  6. disabled = true;
  7. }
  8. return disabled ? null : <div {...props}>{props.children}</div>
  9. }
  1. // myspug\src\compoments\index.js
  2. import NotFound from './NotFound';
  3. import TableCard from './TableCard';
  4. import AuthButton from './AuthButton'
  5. import AuthDiv from './AuthDiv'
  6. export {
  7. NotFound,
  8. TableCard,
  9. AuthButton,
  10. AuthDiv,
  11. }

例如新建按钮就放入 AuthButton 组件中,而整个角色管理的入口模块就放入 AuthDiv 中。

组装
  • mock登录后的数据:
  1. // mock/index.js
  2. {
  3. "data": {
  4. "id": 2,
  5. "access_token": "74b0fe67d09646ee9ca44fc48c6b457a",
  6. "nickname": "pjl",
  7. "is_supper": false,
  8. "has_real_ip": true,
  9. "permissions": ["system.role.view", "alarm.alarm.view"]
  10. },
  11. "error": ""
  12. }
  • 角色管理入口页放入 AuthDiv:
  1. // myspug\src\pages\system\role\index.js
  2. export default function () {
  3. return (
  4. <AuthDiv auth="system.role.view">
  5. <ComTable />
  6. </AuthDiv>
  7. )
  8. }
  • 新增按钮放入 AuthButton:
  1. // myspug\src\pages\system\role\Table.js
  2. <TableCard
  3. rowKey="id"
  4. title="角色列表"
  5. actions={[
  6. <AuthButton type="primary" icon={<PlusOutlined/>} auth="system.role.add" >新增</AuthButton>
  7. ]}

扩展

spug 中权限的缺点

spug 中权限虽然简单,其中一个缺点是每开发一个新模块,就得更新权限配置页面(即功能权限设置)和路由(routes.js)配置:

如果将其改为可配置,将减小这部分的工作。

其他章节请看:

react 高效高质量搭建后台系统 系列

原文链接:https://www.cnblogs.com/pengjiali/p/17127181.html

 友情链接:直通硅谷  点职佳  北美留学生论坛

本站QQ群:前端 618073944 | Java 606181507 | Python 626812652 | C/C++ 612253063 | 微信 634508462 | 苹果 692586424 | C#/.net 182808419 | PHP 305140648 | 运维 608723728

W3xue 的所有内容仅供测试,对任何法律问题及风险不承担任何责任。通过使用本站内容随之而来的风险与本站无关。
关于我们  |  意见建议  |  捐助我们  |  报错有奖  |  广告合作、友情链接(目前9元/月)请联系QQ:27243702 沸活量
皖ICP备17017327号-2 皖公网安备34020702000426号