Files
fangke/admin/src/router/core/RoutePermissionValidator.ts
T
浅念 ab4f40ba5d feat(admin): 模板精简+请求层适配 Django JWT
- 克隆 art-design-pro v3.0.2(GitHub depth1),rm .git,pnpm clean:dev 精简演示页(92 文件)
- .env.development/.env.production 均设 VITE_API_URL=/api,代理目标 127.0.0.1:8000(移除 Apifox Mock)
- http 封装信封适配:code===200 解包 data;msg/message 字段兼容(拦截器/error/类型)
- /api/admin/auth/token 登录适配(access->Bearer token, refresh->refreshToken);/api/admin/auth/me 映射 UserInfo(userName/roles/is_admin)
- 登录页演示账号替换为真实 admin;logOut 按路由名判登录页修复 redirect 嵌套
- 根 .gitignore 补 !admin/.env(模板公共 env 非密钥,需入库)
- 验证:Playwright 全链路(错误密码 401 留登页/正确登录进工作台/登出/受保护路由重定向/redirect 回跳)+ pnpm build(vue-tsc 通过,产物无 mock 域名)

Co-Authored-By: Claude Code <noreply@anthropic.com>
2026-09-15 17:17:38 +08:00

162 lines
4.0 KiB
TypeScript

/**
* 路由权限验证模块
*
* 提供路由权限验证和路径检查功能
*
* ## 主要功能
*
* - 验证路径是否在用户菜单权限中
* - 构建菜单路径集合(扁平化处理)
* - 支持动态路由参数匹配
* - 路径前缀匹配
*
* ## 使用场景
*
* - 路由守卫中验证用户权限
* - 动态路由注册后的权限检查
* - 防止用户访问无权限的页面
*
* @module router/core/RoutePermissionValidator
* @author Art Design Pro Team
*/
import type { AppRouteRecord } from '@/types/router'
/**
* 路由权限验证器
*/
export class RoutePermissionValidator {
/**
* 验证路径是否在用户菜单权限中
* @param targetPath 目标路径
* @param menuList 菜单列表
* @returns 是否有权限访问
*/
static hasPermission(targetPath: string, menuList: AppRouteRecord[]): boolean {
// 根路径始终允许访问
if (targetPath === '/') {
return true
}
return this.matchRoute(targetPath, menuList)
}
/**
* 构建菜单路径集合(扁平化处理)
* @param menuList 菜单列表
* @param pathSet 路径集合
* @returns 路径集合
*/
static buildMenuPathSet(
menuList: AppRouteRecord[],
pathSet: Set<string> = new Set()
): Set<string> {
if (!Array.isArray(menuList) || menuList.length === 0) {
return pathSet
}
for (const menuItem of menuList) {
if (!menuItem.path) {
continue
}
// 标准化路径并添加到集合
const menuPath = menuItem.path.startsWith('/') ? menuItem.path : `/${menuItem.path}`
pathSet.add(menuPath)
// 递归处理子菜单
if (menuItem.children?.length) {
this.buildMenuPathSet(menuItem.children, pathSet)
}
}
return pathSet
}
/**
* 检查目标路径是否匹配集合中的某个路径前缀
* 用于支持动态路由参数匹配,如 /user/123 匹配 /user
* @param targetPath 目标路径
* @param pathSet 路径集合
* @returns 是否匹配
*/
static checkPathPrefix(targetPath: string, pathSet: Set<string>): boolean {
// 遍历路径集合,检查是否有前缀匹配
for (const menuPath of pathSet) {
if (targetPath.startsWith(`${menuPath}/`)) {
return true
}
}
return false
}
/**
* 递归匹配路由配置,支持隐藏路由和动态参数路由
*/
static matchRoute(targetPath: string, routes: AppRouteRecord[]): boolean {
if (!Array.isArray(routes) || routes.length === 0) {
return false
}
for (const route of routes) {
if (!route.path) {
continue
}
const routePath = route.path.startsWith('/') ? route.path : `/${route.path}`
if (
routePath === targetPath ||
this.isDynamicRouteMatch(targetPath, routePath) ||
targetPath.startsWith(`${routePath}/`)
) {
return true
}
if (route.children?.length && this.matchRoute(targetPath, route.children)) {
return true
}
}
return false
}
/**
* 检查目标路径是否匹配动态参数路由,如 /demo/123 匹配 /demo/:id
*/
static isDynamicRouteMatch(targetPath: string, routePath: string): boolean {
if (!routePath.includes(':')) {
return false
}
const pattern = routePath
.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')
.replace(/:([^/]+)/g, '[^/]+')
.replace(/\\\*/g, '.*')
return new RegExp(`^${pattern}$`).test(targetPath)
}
/**
* 验证并返回有效的路径
* 如果目标路径无权限,返回首页路径
* @param targetPath 目标路径
* @param menuList 菜单列表
* @param homePath 首页路径
* @returns 验证后的路径
*/
static validatePath(
targetPath: string,
menuList: AppRouteRecord[],
homePath: string = '/'
): { path: string; hasPermission: boolean } {
const hasPermission = this.hasPermission(targetPath, menuList)
if (hasPermission) {
return { path: targetPath, hasPermission: true }
}
return { path: homePath, hasPermission: false }
}
}