Files
seclusion/docs/workflow/250117-权限管理体系实施.md
charilezhou 2a9e46a3f2 docs: 更新项目文档
- 更新 CLAUDE.md 中 HTTP 客户端说明(apiFetch -> http)
- 更新 docs/web/DESIGN.md 技术栈说明
- 添加任务工作流文档

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-17 14:07:35 +08:00

15 KiB
Raw Blame History

菜单及路由管理体系实施文档

需求确认

项目 选择
菜单来源 混合模式(核心菜单静态 + 扩展菜单数据库)
权限粒度 按钮级别
权限模型 RBAC角色-权限模型)
超级管理员 需要内置
权限编码 资源:操作 格式(如 user:create
前端权限 Hook + 组件 + 指令
路由守卫 自动拦截(跳转 403

一、数据库模型设计

新增模型

// 角色表
model Role {
  id          String       @id @default(cuid(2))
  code        String       @unique          // 角色编码: admin, user
  name        String                        // 角色名称
  description String?
  isSystem    Boolean      @default(false)  // 系统内置角色不可删
  isEnabled   Boolean      @default(true)
  sort        Int          @default(0)
  createdAt   DateTime     @default(now())
  updatedAt   DateTime     @updatedAt
  deletedAt   DateTime?

  users       UserRole[]
  permissions RolePermission[]
  menus       RoleMenu[]
}

// 权限表
model Permission {
  id          String       @id @default(cuid(2))
  code        String       @unique          // 权限编码: user:create
  name        String
  description String?
  resource    String                        // 资源: user
  action      String                        // 操作: create
  isEnabled   Boolean      @default(true)
  createdAt   DateTime     @default(now())
  updatedAt   DateTime     @updatedAt
  deletedAt   DateTime?

  roles       RolePermission[]
}

// 菜单表
model Menu {
  id         String    @id @default(cuid(2))
  parentId   String?
  code       String    @unique
  name       String
  type       String    @default("menu") // dir / menu / button
  path       String?
  icon       String?                    // Lucide 图标名
  component  String?
  permission String?                    // 关联权限编码
  isExternal Boolean   @default(false)
  isHidden   Boolean   @default(false)
  isEnabled  Boolean   @default(true)
  isStatic   Boolean   @default(true)   // 静态/动态菜单
  sort       Int       @default(0)
  meta       Json?
  createdAt  DateTime  @default(now())
  updatedAt  DateTime  @updatedAt
  deletedAt  DateTime?

  parent   Menu?      @relation("MenuTree", fields: [parentId], references: [id])
  children Menu[]     @relation("MenuTree")
  roles    RoleMenu[]
}

// 关联表
model UserRole { ... }
model RolePermission { ... }
model RoleMenu { ... }

// User 模型扩展
model User {
  // ... 现有字段
  isSuperAdmin Boolean    @default(false)  // 超级管理员标记
  roles        UserRole[]
}

超级管理员设计

  • User.isSuperAdmin 字段标记
  • 超管拥有所有权限,跳过权限检查
  • 超管标记仅允许数据库直接修改

二、共享类型定义

文件: packages/shared/src/types/permission.ts

枚举常量

export const MenuType = { DIR: 'dir', MENU: 'menu', BUTTON: 'button' } as const;
export const SystemRoleCode = { SUPER_ADMIN: 'super_admin', ADMIN: 'admin', USER: 'user' } as const;
export const PermissionAction = { CREATE: 'create', READ: 'read', UPDATE: 'update', DELETE: 'delete' } as const;

接口类型

  • RoleResponse / RoleDetailResponse - 角色响应
  • PermissionResponse - 权限响应
  • MenuResponse / MenuTreeNode - 菜单响应
  • AuthUserWithPermissions - 带权限的认证用户
  • UserMenusAndPermissionsResponse - 用户菜单和权限响应

三、后端模块设计

目录结构

apps/api/src/permission/
├── decorators/
│   ├── index.ts
│   └── require-permission.decorator.ts
├── guards/
│   ├── index.ts
│   └── permission.guard.ts
├── dto/
│   ├── index.ts
│   ├── permission.dto.ts
│   ├── role.dto.ts
│   └── menu.dto.ts
├── services/
│   ├── index.ts
│   ├── permission.service.ts
│   ├── role.service.ts
│   └── menu.service.ts
├── controllers/
│   ├── index.ts
│   ├── permission.controller.ts
│   ├── role.controller.ts
│   └── menu.controller.ts
├── index.ts
└── permission.module.ts

核心实现

权限装饰器 @RequirePermission(...permissions)

export const RequirePermission = (...permissions: string[]) =>
  SetMetadata(PERMISSION_KEY, permissions);

权限守卫 PermissionGuard

canActivate(context) {
  const permissions = reflector.get(PERMISSION_KEY, handler);
  if (!permissions) return true;

  const user = request.user;
  if (user.isSuperAdmin) return true;  // 超管直接通过

  return permissions.some(p => user.permissions.includes(p));
}

JWT 策略更新 - 返回用户权限信息

validate(payload) {
  const user = await prisma.user.findUnique({
    include: { roles: { include: { role: { include: { permissions } } } } }
  });
  return { ...user, roles: [...], roleIds: [...], permissions: [...] };
}

API 端点

模块 端点 权限
角色 GET/POST /roles role:read/create
角色 GET/PATCH/DELETE /roles/:id role:read/update/delete
权限 GET /permissions permission:read
菜单 GET/POST /menus menu:read/create
菜单 PATCH/DELETE /menus/:id menu:update/delete
当前用户 GET /auth/menus 登录即可

四、前端权限系统设计

目录结构

apps/web/src/
├── stores/permissionStore.ts      # 权限状态
├── hooks/usePermission.ts         # 权限检查 Hook
├── components/permission/
│   ├── index.ts
│   ├── PermissionGuard.tsx        # 权限守卫组件
│   └── WithPermission.tsx         # 权限 HOC
├── services/permission.service.ts # 权限服务
├── config/route-permissions.ts    # 路由权限映射
├── lib/icons.ts                   # 图标映射
└── app/403/page.tsx               # 无权限页面

权限 Store

interface PermissionState {
  menus: MenuTreeNode[];
  permissions: string[];
  isSuperAdmin: boolean;
  isLoaded: boolean;
}

interface PermissionActions {
  setPermissionData: (data) => void;
  clearPermissionData: () => void;
  hasPermission: (p: string | string[]) => boolean;
  hasAllPermissions: (permissions: string[]) => boolean;
}

usePermission Hook

function usePermission() {
  const { permissions, isSuperAdmin } = usePermissionStore();

  const hasPermission = (permission: string | string[]) => {
    if (isSuperAdmin) return true;
    if (Array.isArray(permission)) return permission.some(p => permissions.includes(p));
    return permissions.includes(permission);
  };

  return { hasPermission, hasAnyPermission, hasAllPermissions };
}

PermissionGuard 组件

<PermissionGuard permission="user:create">
  <CreateUserButton />
</PermissionGuard>

<PermissionGuard permission={['user:create', 'user:update']} fallback={<span>无权限</span>}>
  <UserForm />
</PermissionGuard>

<PermissionGuard permission={['user:create', 'user:update']} mode="all">
  <UserForm />
</PermissionGuard>

路由守卫

在 Dashboard Layout 中实现:

  1. 登录后获取用户菜单和权限
  2. 存入 permissionStore
  3. 路由变化时检查权限
  4. 无权限跳转 403 页面

五、文件清单

后端文件

优先级 文件路径 说明
P0 apps/api/prisma/schema.prisma 数据库模型
P0 apps/api/prisma/seed.ts 种子数据
P0 apps/api/src/permission/guards/permission.guard.ts 权限守卫
P0 apps/api/src/permission/decorators/require-permission.decorator.ts 权限装饰器
P0 apps/api/src/auth/strategies/jwt.strategy.ts JWT 策略(已更新)
P0 apps/api/src/auth/auth.controller.ts 认证控制器(已更新)
P0 apps/api/src/auth/auth.module.ts 认证模块(已更新)
P1 apps/api/src/permission/services/*.ts 服务层
P1 apps/api/src/permission/controllers/*.ts 控制器层
P1 apps/api/src/permission/dto/*.ts DTO 定义
P1 apps/api/src/permission/permission.module.ts 权限模块
P1 apps/api/src/app.module.ts 应用模块(已更新)
P1 apps/api/src/prisma/prisma.service.ts Prisma 服务(已更新)

共享包文件

文件路径 说明
packages/shared/src/types/permission.ts 权限相关类型定义
packages/shared/src/types/index.ts 类型导出(已更新)

前端文件

优先级 文件路径 说明
P0 apps/web/src/stores/permissionStore.ts 权限状态管理
P0 apps/web/src/app/(dashboard)/layout.tsx Dashboard 布局(已更新)
P1 apps/web/src/hooks/usePermission.ts 权限检查 Hook
P1 apps/web/src/components/permission/PermissionGuard.tsx 权限守卫组件
P1 apps/web/src/components/permission/WithPermission.tsx 权限 HOC
P1 apps/web/src/components/layout/Sidebar.tsx 侧边栏(已更新)
P1 apps/web/src/lib/icons.ts 图标映射工具
P1 apps/web/src/config/route-permissions.ts 路由权限配置
P1 apps/web/src/config/constants.ts 常量配置(已更新)
P1 apps/web/src/services/permission.service.ts 权限服务
P1 apps/web/src/app/403/page.tsx 403 页面
P2 apps/web/src/components/ui/collapsible.tsx 折叠组件

六、使用指南

后端使用

1. 保护接口

@Controller('users')
@UseGuards(JwtAuthGuard, PermissionGuard)
@ApiBearerAuth()
export class UserController {
  @Get()
  @RequirePermission('user:read')
  findAll() { ... }

  @Post()
  @RequirePermission('user:create')
  create() { ... }

  @Delete(':id')
  @RequirePermission('user:delete')
  delete() { ... }
}

2. 获取当前用户权限

@Get('profile')
getProfile(@CurrentUser() user: AuthUserWithPermissions) {
  // user.isSuperAdmin - 是否超管
  // user.roles - 角色编码列表
  // user.permissions - 权限编码列表
}

前端使用

1. 权限守卫组件

import { PermissionGuard } from '@/components/permission';

// 单个权限
<PermissionGuard permission="user:create">
  <Button>创建用户</Button>
</PermissionGuard>

// 多个权限OR 关系)
<PermissionGuard permission={['user:create', 'user:update']}>
  <UserForm />
</PermissionGuard>

// 多个权限AND 关系)
<PermissionGuard permission={['user:create', 'user:update']} mode="all">
  <UserForm />
</PermissionGuard>

// 带 fallback
<PermissionGuard permission="user:delete" fallback={<span>无权限</span>}>
  <DeleteButton />
</PermissionGuard>

2. usePermission Hook

import { usePermission } from '@/hooks';

function MyComponent() {
  const { hasPermission, isSuperAdmin } = usePermission();

  return (
    <div>
      {hasPermission('user:create') && <CreateButton />}
      {hasPermission(['user:update', 'user:delete']) && <ActionMenu />}
    </div>
  );
}

3. 路由权限配置

// config/route-permissions.ts
export const routePermissions: Record<string, string | string[]> = {
  '/users': 'user:read',
  '/users/create': 'user:create',
  '/roles': 'role:read',
};

export const publicRoutes = [
  '/dashboard',
  '/profile',
  '/settings',
];

七、部署步骤

1. 数据库迁移

# 启动数据库
docker compose -f deploy/docker-compose.yml up -d

# 执行数据库迁移
cd apps/api && pnpm db:migrate

# 初始化种子数据
cd apps/api && pnpm db:seed

2. 验证

  1. 使用超级管理员账号登录

    • 邮箱: admin@seclusion.dev
    • 密码: admin123
  2. 访问 Swagger 文档查看新增的权限管理 API

    • http://localhost:4000/api/docs
  3. 验证菜单根据权限动态显示

  4. 验证无权限路由跳转到 403 页面


八、预置数据

角色

编码 名称 说明
super_admin 超级管理员 系统内置,拥有所有权限
admin 管理员 系统内置,拥有大部分权限
user 普通用户 系统内置,基础权限

权限

编码 名称 资源 操作
user:create 创建用户 user create
user:read 查看用户 user read
user:update 更新用户 user update
user:delete 删除用户 user delete
role:create 创建角色 role create
role:read 查看角色 role read
role:update 更新角色 role update
role:delete 删除角色 role delete
permission:read 查看权限 permission read
menu:create 创建菜单 menu create
menu:read 查看菜单 menu read
menu:update 更新菜单 menu update
menu:delete 删除菜单 menu delete

菜单

编码 名称 类型 路径 图标 权限
dashboard 仪表盘 menu /dashboard LayoutDashboard -
user-management 用户管理 menu /users Users user:read
system 系统管理 dir - Settings -
role-management 角色管理 menu /roles Shield role:read
menu-management 菜单管理 menu /menus Menu menu:read
profile 个人中心 menu /profile User -
settings 系统设置 menu /settings Settings -

九、阶段四:管理界面(已完成)

角色管理

文件路径 说明
apps/web/src/app/(dashboard)/roles/page.tsx 角色管理页面
apps/web/src/components/roles/RolesTable.tsx 角色列表表格组件
apps/web/src/components/roles/RoleEditDialog.tsx 角色编辑弹窗组件
apps/web/src/hooks/useRoles.ts 角色相关 Hooks
apps/web/src/services/role.service.ts 角色服务层

菜单管理

文件路径 说明
apps/web/src/app/(dashboard)/menus/page.tsx 菜单管理页面
apps/web/src/components/menus/MenusTable.tsx 菜单列表表格组件
apps/web/src/components/menus/MenuEditDialog.tsx 菜单编辑弹窗组件
apps/web/src/hooks/useMenus.ts 菜单相关 Hooks
apps/web/src/services/menu.service.ts 菜单服务层

功能说明

  1. 角色管理

    • 角色列表展示(支持分页、搜索、排序)
    • 新建/编辑角色
    • 为角色分配权限
    • 删除角色(系统内置角色不可删除)
  2. 菜单管理

    • 菜单列表展示(支持分页、搜索、排序)
    • 新建/编辑菜单
    • 支持目录、菜单、按钮三种类型
    • 删除菜单(静态菜单不可删除)

十、后续扩展建议

  1. 数据权限 - 支持数据行级权限控制
  2. 权限缓存 - Redis 缓存用户权限减少数据库查询
  3. 权限继承 - 支持角色继承
  4. 操作日志 - 记录权限变更日志
  5. 批量导入导出 - 支持权限配置的批量操作