【Next】5. 全局权限管理

news/2024/9/19 4:47:30/ 标签: 前端, javascript, 网络

以下笔记来源:编程导航

需求

  1. 能够灵活配置每个页面所需要的用户权限,由全局权限管理系统自动校验和拦截路由,而不需要在每个页面中编写权限校验代码,提高开发效率。(路由权限)
  2. 还要能够根据权限控制导航菜单的显隐,只有具有权限的菜单,才对用户可见。(菜单显隐)

实现思路

  1. 在路由配置文件, 定义某个路由的访问权限。由于 Next.js 项目是约定式路由,只有我们自定义的菜单配置文件,可以在菜单配置文件中定义权限。
  2. 每次访问页面时,根据用户要访问页面的路由权限信息,判断用户是否有对应的访问权限,并进行相应的拦截处理。这是一个全局逻辑,可以在项目根布局 app/layout.tsx 中添加。
  3. 导航栏展示菜单时,可以过滤掉登录用户没有权限的菜单项,从而实现根据权限控制导航菜单的显隐。

具体实现:

1. 新增 forbidden 页面

src/app/forbidden.tsx

import { Button, Result } from "antd";/*** 无权限页面* @constructor*/
const Forbidden = () => {return (<Resulttitle="403"status="403"subTitle="对不起,你无权访问该页面"extra={<Button type="primary" href={"/"}>返回首页</Button>}/>);
};export default Forbidden;

2. 定义权限常量和默认用户

src/access/accessEnum.ts

/*** 权限枚举*/
const Access_Enum = {NOT_LOGIN: "notLogin",USER: "user",ADMIN: "admin"
};
export default Access_Enum;

src/ constants/user.ts

// 默认用户
import Access_Enum from "@/access/accessEnum";const DEFAULT_USER: API.LoginUserVO = {userName: "未登录",userProfile: "暂无简介",userAvatar: "/assets/notLoginUser.png",userRole: Access_Enum.NOT_LOGIN,
};export default DEFAULT_USER;

3. 菜单配置和筛选

为需要权限的菜单增加配置项,并根据路径查找菜单。

config/menu.tsx

import { MenuDataItem } from "@ant-design/pro-layout";
import { CrownOutlined } from "@ant-design/icons";
import Access_Enum from "@/access/accessEnum";// 菜单列表
export const menus = [{path: "/",name: "主页",},{path: "/banks",name: "题库",},{path: "/questions",name: "题目",},{path: "/admin",name: "管理",icon: <CrownOutlined />,access: Access_Enum.ADMIN,children: [{path: "/admin/user",name: "用户管理",access: Access_Enum.ADMIN,},{path: "/admin/bank",name: "题库管理",access: Access_Enum.ADMIN,},{path: "/admin/question",name: "题目管理",access: Access_Enum.ADMIN,},],},
] as MenuDataItem[];/*** 根据路径查找菜单*/
export const findMenuItemByPath = (menus: MenuDataItem[],path: string,
): MenuDataItem | null => {for (let menu of menus) {if (menu.path === path) {return menu;}if (menu.children) {const matchedMenuItem = findMenuItemByPath(menu.children, path);if (matchedMenuItem) {return matchedMenuItem;}}}return null;
};/*** 根据路径查找所有菜单*/
export const findAllMenuItemByPath = (path: string): MenuDataItem | null => {return findMenuItemByPath(menus, path);
};

4. 编写通用权限校验方法

因为菜单组件中要判断权限、权限拦截也要用到权限判断功能,所以抽离成公共模块。

src/access/checkAccess.ts

import Access_Enum from "@/access/accessEnum";/*** 检查权限(判断当前登录用户是否具有某个权限)* @param loginUser 当前登录用户* @param needAccess 需要检查的权限* @return boolean 有无权限*/
const checkAccess = (loginUser: API.LoginUserVO,needAccess = Access_Enum.NOT_LOGIN,
) => {// 获取当前用户具有的权限 如果没有登录 默认没有权限const loginUserAccess = loginUser?.userRole ?? Access_Enum.NOT_LOGIN;// 如果当前不需要权限if (needAccess === Access_Enum.NOT_LOGIN) {return true;}// 如果需要权限为普通用户if (needAccess === Access_Enum.USER) {if (loginUserAccess === Access_Enum.NOT_LOGIN) {return false;}}// 如果需要权限为管理员if (needAccess === Access_Enum.ADMIN) {if (loginUserAccess !== Access_Enum.ADMIN) {return false;}}return true;
};export default checkAccess;

5. 新增权限校验布局

src/access/AccessLayout.tsx

import React from "react";
import {useSelector} from "react-redux";
import {RootState} from "@/stores";
import {usePathname} from "next/navigation";
import {findAllMenuItemByPath} from "../../config/menu";
import Access_Enum from "@/access/accessEnum";
import checkAccess from "@/access/checkAccess";
import Forbidden from "@/app/forbidden";/*** 统一权限校验拦截器* @param children* @constructor*/
const AccessLayout: React.FC<Readonly<{ children: React.ReactNode }>> = ({children}) => {const pathname = usePathname()// 当前登录用户const loginUser = useSelector((state: RootState) => state.loginUser);// 根据路径获取当前菜单const menu = findAllMenuItemByPath(pathname) || {};// 需要的权限const needAccess = menu?.access ?? Access_Enum.NOT_LOGIN;// 判断是否拥有权限const canAccess = checkAccess(loginUser, needAccess);if (!canAccess) {return <Forbidden />}return <>{children}</>
};export default AccessLayout;

6. 包裹(增强)基础布局

src/app/layout.tsx

export default function RootLayout({children,
}: Readonly<{children: React.ReactNode;
}>) {return (<html lang="zh"><body><AntdRegistry><Provider store={store}><InitLayout><BasicLayout><AccessLayout>{children}</AccessLayout></BasicLayout></InitLayout></Provider></AntdRegistry></body></html>);
}

7. 控制菜单显隐

src/access/menuAccess.ts

import { menus } from "../../config/menu";
import checkAccess from "@/access/checkAccess";const getAccessibleMenus = (loginUser: API.LoginUserVO, menuItems = menus) => {return menuItems.filter((item) => {if (!checkAccess(loginUser, item.access)) {return false;}if (item.children) {item.children = getAccessibleMenus(loginUser, item.children);}return true;});
};export default getAccessibleMenus;

然后就可以在基础布局页面的菜单渲染使用上进行使用:

src/layouts/BasicLayout/index.tsx

// 定义菜单
menuDataRender={() => {return getAccessibleMenus(loginUser, menus);
}}

8. 404 页面

未和路由菜单匹配的路径,404 未找到。

src/app/not-found.tsx (约定式)

import {Button, Result} from "antd";/*** 未找到页面* @constructor*/
const NotFound = () => {return (<Resulttitle="404"status="404"subTitle="抱歉,你访问的页面不存在"extra={<Button type="primary" href={"/"}>返回首页</Button>}/>);
};export default NotFound;

其他(高阶组件权限校验)

还有其他实现权限校验的方法,比如使用高阶组件(HOC)在客户端进行权限校验,这种方法会更灵活。如下:

// components/withAuth.js
import { useRouter } from 'next/router';
import { useEffect } from 'react';
import { useSelector } from 'react-redux'; // 或者使用其他全局状态管理库export default function withAuth(Component) {return function AuthenticatedComponent(props) {const router = useRouter();const isAuthenticated = useSelector((state) => state.auth.isAuthenticated); // 获取用户登录状态useEffect(() => {if (!isAuthenticated) {// 如果未登录,重定向到登录页面router.push('/login');}}, [isAuthenticated]);// 如果未登录,不渲染组件if (!isAuthenticated) {return null;}// 如果已登录,渲染组件return <Component {...props} />;};
}

使用这个 HOC 包裹需要进行权限校验的页面:

// pages/protected.js
import withAuth from '@/components/withAuth';function ProtectedPage() {return <div>This is a protected page.</div>;
}export default withAuth(ProtectedPage);

http://www.ppmy.cn/news/1520924.html

相关文章

生活因科技而美好:一键解锁PDF处理的无限可能

前言 “科技&#xff0c;是时代的诗篇&#xff0c;书写着人类不断超越自我、追求卓越的壮丽篇章。”这一理念深刻地影响着每一位开发者&#xff0c;他们不断探索、创新&#xff0c;旨在为用户带来更加便捷、高效的生活体验。正是在这样的背景下&#xff0c;一款旨在提升PDF处理…

MySQL case when【用法详解】

MySQL case when【用法详解】 语法1. 简单CASE表达式2. 搜索CASE表达式 示例示例1&#xff1a;使用简单CASE表达式示例2&#xff1a;使用搜索CASE表达式示例3&#xff1a;在UPDATE语句中使用CASE示例4&#xff1a;在DELETE语句中使用CASE注意事项 总结 在MySQL中&#xff0c;CA…

【技术解析】工厂内部导航系统:高精度定位与智能路径规划的技术实现

一、工厂内部导航系统概述 工厂内部导航系统集成了最新的GPS室内定位技术、蓝牙定位技术&#xff0c;实现了对工厂内部环境的无缝覆盖与高精度定位。无论是繁忙的生产线、错综复杂的仓库还是广阔的厂区&#xff0c;都能轻松应对。 二、工厂内部导航系统核心功能 实时定位&…

深度学习的发展历程

深度学习的起源 在机器学习中&#xff0c;我们经常使用两种方式来表示特征&#xff1a;局部表示&#xff08;Local Representation&#xff09;和分布式表示&#xff08;Distributed Representation&#xff09;。以颜色表示为例&#xff0c;见下图&#xff1a; 要学习到一种好…

电信500M宽带+AX210无线网卡测速

500M电信宽带&#xff0c;PC的Wifi模块是AX210 一、PC测速 2.4G Wifi 5G Wifi 有线网口 二、 手机端&#xff0c;小翼管家App测速 2.4G Wifi 5G Wifi 结论&#xff1a; 手机上网要快的话&#xff0c;还是要选择5G wifi

使用实例:xxl-job应用在spring cloud微服务下

1、首先下载&#xff0c;从github上下载&#xff0c;选择zip然后直接解压 https://github.com/xuxueli/xxl-job/releases 2、解压完后用idea启动。 启动这个启动类&#xff0c;然后按照路径访问 http://localhost:8080/xxl-job-admin/ 3、在你的项目里编写一个单独的微服务&a…

目标跟踪算法——ByteTrack算法原理解析

文章目录 ByteTrack1. ByteTrack算法步骤&#xff1a;2. 算法解释2.1 模型初始化2.2 模型更新算法流程2.2.1 检测结果划分&#xff0c;划分为高分和较低分段2.2.2 高分段处理手段2.2.3 最优匹配与未匹配划分2.2.4 低分框再匹配2.2.5 未确认轨迹处理2.2.6 更新状态 2.3 匈牙利匹…

ffplay源码分析(五)包缓存队列和帧缓存队列

在音视频处理流程中&#xff0c;ffplay的有两种队列&#xff0c;包缓存队列&#xff08;Packet Buffer Queue&#xff09;和帧缓存队列&#xff08;Frame Buffer Queue&#xff09;。这两个队列的存在&#xff0c;是为了适应音视频数据处理过程中的多线程架构——包括收包线程、…

图像白平衡

目录 效果 背景 什么是白平衡&#xff1f; 实现原理 将指定图色调调整为参考图色调主要流程 示例代码 效果 将图一效果转换为图二效果色调&#xff1a; 调整后&#xff0c;可实现色调对换 背景 现有两张图像&#xff0c;色调不一致&#xff0c;对于模型重建会有影响。因…

RabbitMQ 02 操作,配置信息,用户权限

01.介绍启动&#xff0c;关闭 02.环境 2.1 MQ是用Erlang语言写的 2.2 一个RabbitMQ 节点 一个 Erlang节点一个Erlang 程序 &#xff08;RabbitMQ程序&#xff09; 2.3 Erlang节点&#xff1a; 这个是Erlang节点集群状态下&#xff1a; 2.4 启动节点 2.5 查看日志信息 …

2021年大厂Java面试题(基础+框架+系统架构+分布式+实战)

Java线程的状态 进程和线程的区别&#xff0c;进程间如何通讯&#xff0c;线程间如何通讯 HashMap的数据结构是什么&#xff1f;如何实现的。和HashTable&#xff0c;ConcurrentHashMap的区别 Cookie和Session的区别 索引有什么用&#xff1f;如何建索引&#xff1f; Arra…

Elasticsearch 中,term 查询和 match 查询的区别

文章目录 前言Elasticsearch 中&#xff0c;term 查询和 match 查询的区别1. Term 查询2. Match 查询3. 总结 前言 如果您觉得有用的话&#xff0c;记得给博主点个赞&#xff0c;评论&#xff0c;收藏一键三连啊&#xff0c;写作不易啊^ _ ^。   而且听说点赞的人每天的运气都…

各种各样的正则表达式

一、校验数字的表达式 数字:^[0-9]*$ n位的数字:^\d{n}$ 至少n位的数字:^\d{n,}$ m-n位的数字:^\d{m,n}$ 零和非零开头的数字:^(0|[1-9][0-9]*)$ 非零开头的最多带两位小数的数字:^([1-9][0-9]*)+(.[0-9]{1,2})?$ 带1-2位小数的正数或负数:^(\-)?\d+(\.\d{1,2})?$ 正…

【flask】python框架flask的hello world

创建一个py文件&#xff0c;写如下内容 # save this as app.py from flask import Flaskapp Flask(__name__)app.route("/") def hello():return "Hello, World!"如下图 在此py文件路径下启动cmd&#xff0c;输入 flask run结果如下图 在浏览器中访问…

【科普】数字化和数字化转型:是什么,为什么,怎么做?

​一、什么是数字化转型&#xff1f; 近年来 “数字化”、“数字化转型”概念已经渗透到各个行业&#xff0c;成为业界的热点议题。对于什么是“数字化转型”&#xff0c;众说纷纭。 有人说“数字化转型不过就是给传统的信息化穿上皇帝的新衣”&#xff0c;也有人说“数字化转…

策略优化:提升MySQL数据备份效率的实用指南

在当今数据驱动的商业环境中&#xff0c;数据备份策略的优化对于确保数据安全和业务连续性至关重要。MySQL作为广泛使用的数据库系统&#xff0c;其数据备份策略的优化不仅可以提高数据恢复的效率&#xff0c;还能降低存储成本和提高系统性能。本文将深入探讨如何在MySQL中实现…

用户管理和授权

授权 mysql> show databases; -------------------- | Database | -------------------- | information_schema | | day01db | | employees | | mysql | | mysql01 | | mysql02 | | performance_schema …

深入理解Java虚拟机的类加载机制

深入理解Java虚拟机的类加载机制 目录 深入理解Java虚拟机的类加载机制 一、类加载概念与过程 1. 类加载定义与作用 2. 类加载过程详解 二、类加载器 1. 系统提供的类加载器 2. 自定义类加载器 三、双亲委派模型 1. 双亲委派模型的概念 2. 工作过程 四、类的卸载与重…

【whisper】使用whisper实现语音转文字

whisper需要ffmpeg支持 官网下载ffmpeg https://www.gyan.dev/ffmpeg/builds/下载完毕后解压放到合适的位置 添加环境变量 在cmd中输入以下 ffmpeg -version出现下面结果代表成功 安装whisper pip install openai-whisper在vscode中运行 测试代码 import whisperif __n…

ROS2 2D相机基于AprilTag实现3D空间定位最简流程

文章目录 前言驱动安装下载安装方式一&#xff1a;方式二&#xff1a; 相机检测配置config文件编译、运行程序注意 内参标定标定板运行程序 apriltag空间定位标签打印下载安装可视化结果 前言 AprilTag是一种高性能的视觉标记系统&#xff0c;广泛应用于机器人导航、增强现实和…