feat: 分层 RBAC 权限管理系统

后端:
- 新增 Role / RolePermission 实体(自动 seed 系统角色)
- PermissionService——通过 isAdmin / TenantMember 链路解析用户权限
- @Permission() 装饰器 + PermissionsGuard 守卫
- /api/permissions 和 /api/roles REST API
- UserController 内联 role 检查迁移到 @Permission()
- PermissionModule 全局注册

前端:
- usePermissions hook——获取当前用户权限集
- PermissionGate 组件级门控
- PermissionSettingsView——角色列表+权限矩阵编辑页面
- SettingsView 新增「权限管理」Tab(仅 admin 可见)
- 权限预览(26 项,7 分类)

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
Developer
2026-06-08 23:25:22 +08:00
parent c57c3028e2
commit ba33d517c1
17 changed files with 1386 additions and 87 deletions
@@ -0,0 +1,247 @@
import { Injectable, Logger, OnModuleInit } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { Repository } from 'typeorm';
import { Role } from './role.entity';
import { RolePermission } from './role-permission.entity';
import { ALL_PERMISSIONS, PERMISSION_META, PermissionKey, PermissionMeta } from './permission.constants';
import { UserRole } from '../../user/user-role.enum';
import { TenantMember } from '../../tenant/tenant-member.entity';
import { User } from '../../user/user.entity';
import { ConfigService } from '@nestjs/config';
/**
* 角色预设——系统内置角色默认挂载的权限
*/
const ROLE_DEFAULT_PERMISSIONS: Record<string, PermissionKey[]> = {
[UserRole.SUPER_ADMIN]: [...ALL_PERMISSIONS],
[UserRole.TENANT_ADMIN]: [
// 用户管理(不含删除和角色管理——安全考虑)
'user:view', 'user:create', 'user:edit', 'user:password',
// 租户管理(只能查看和编辑自己的)
'tenant:view', 'tenant:edit', 'tenant:members',
// 知识库
'kb:view', 'kb:create', 'kb:edit', 'kb:delete', 'kb:publish',
// 考核评估
'assess:view', 'assess:manage', 'assess:template', 'assess:bank',
// 模型配置
'model:view', 'model:config',
// 插件
'plugin:view', 'plugin:manage',
// 设置
'settings:view',
],
[UserRole.USER]: [
'kb:view', 'kb:create', 'kb:edit',
'assess:view',
'plugin:view',
],
};
@Injectable()
export class PermissionService implements OnModuleInit {
private readonly logger = new Logger(PermissionService.name);
constructor(
@InjectRepository(Role)
private readonly roleRepository: Repository<Role>,
@InjectRepository(RolePermission)
private readonly rolePermissionRepository: Repository<RolePermission>,
@InjectRepository(TenantMember)
private readonly tenantMemberRepository: Repository<TenantMember>,
@InjectRepository(User)
private readonly userRepository: Repository<User>,
private readonly configService: ConfigService,
) {}
/**
* 启动时自动种子化系统角色和权限
*/
async onModuleInit() {
const isTest = this.configService.get<string>('NODE_ENV') === 'test';
if (isTest) return;
try {
const existing = await this.roleRepository.count({ where: { isSystem: true } });
if (existing > 0) {
this.logger.log(`[Permission Seed] ${existing} 个系统角色已存在,跳过`);
return;
}
await this.seedSystemRoles();
this.logger.log('[Permission Seed] ✅ 系统角色和权限已初始化');
} catch (err) {
this.logger.error('[Permission Seed] ❌ 初始化失败:', err);
}
}
private async seedSystemRoles() {
const roles = [
{ name: UserRole.SUPER_ADMIN, baseRole: UserRole.SUPER_ADMIN },
{ name: UserRole.TENANT_ADMIN, baseRole: UserRole.TENANT_ADMIN },
{ name: UserRole.USER, baseRole: UserRole.USER },
];
for (const r of roles) {
const role = await this.roleRepository.save({
name: r.name,
description: this.getRoleDescription(r.name as UserRole),
isSystem: true,
baseRole: r.baseRole,
tenantId: null,
});
const perms = ROLE_DEFAULT_PERMISSIONS[r.name] || [];
if (perms.length > 0) {
await this.rolePermissionRepository.save(
perms.map(key => ({ roleId: role.id, permissionKey: key })),
);
}
this.logger.log(` - ${r.name}: ${perms.length} 项权限`);
}
}
private getRoleDescription(role: UserRole): string {
switch (role) {
case UserRole.SUPER_ADMIN: return '全局超级管理员——拥有系统全部权限';
case UserRole.TENANT_ADMIN: return '租户管理员——管理本租户内的用户和资源';
case UserRole.USER: return '普通用户——使用系统功能';
}
}
// ──────────── 角色 CRUD ────────────
async findAllRoles(tenantId?: string): Promise<Role[]> {
const where: any[] = [{ isSystem: true, tenantId: null }];
if (tenantId) {
where.push({ tenantId, isSystem: false });
}
return this.roleRepository.find({ where, order: { isSystem: 'DESC', name: 'ASC' } });
}
async findRoleById(id: string): Promise<Role | null> {
return this.roleRepository.findOne({ where: { id } });
}
async createRole(name: string, description: string, tenantId: string): Promise<Role> {
const existing = await this.roleRepository.findOne({ where: { name } });
if (existing) throw new Error(`角色名 "${name}" 已存在`);
return this.roleRepository.save({
name,
description,
isSystem: false,
baseRole: null,
tenantId,
});
}
async updateRole(id: string, data: { name?: string; description?: string }): Promise<Role> {
const role = await this.roleRepository.findOne({ where: { id } });
if (!role) throw new Error('角色不存在');
if (role.isSystem) throw new Error('系统角色不可编辑');
if (data.name) role.name = data.name;
if (data.description !== undefined) role.description = data.description;
return this.roleRepository.save(role);
}
async deleteRole(id: string): Promise<void> {
const role = await this.roleRepository.findOne({ where: { id } });
if (!role) throw new Error('角色不存在');
if (role.isSystem) throw new Error('系统角色不可删除');
await this.roleRepository.remove(role);
}
// ──────────── 权限管理 ────────────
async getRolePermissions(roleId: string): Promise<string[]> {
const rps = await this.rolePermissionRepository.find({
where: { roleId },
});
return rps.map(rp => rp.permissionKey);
}
async setRolePermissions(roleId: string, permissionKeys: string[]): Promise<void> {
const role = await this.roleRepository.findOne({ where: { id: roleId } });
if (!role) throw new Error('角色不存在');
// 验证权限键是否有效
const valid = ALL_PERMISSIONS;
const invalid = permissionKeys.filter(k => !valid.includes(k as any));
if (invalid.length > 0) throw new Error(`无效的权限: ${invalid.join(', ')}`);
// 替换角色的所有权限
await this.rolePermissionRepository.delete({ roleId });
if (permissionKeys.length > 0) {
await this.rolePermissionRepository.save(
permissionKeys.map(key => ({ roleId, permissionKey: key })),
);
}
}
// ──────────── 用户权限解析 ────────────
/**
* 获取用户在指定租户下的最终权限集
*
* 解析链路:
* 1. 如果是 global adminisAdmin=true),直接返回所有权限
* 2. 通过 TenantMember.role → 对应 role → role_permissions
*/
async getUserPermissions(userId: string, tenantId: string): Promise<Set<string>> {
// 检查全局管理员(遗留 isAdmin 字段)
const user = await this.userRepository.findOne({
where: { id: userId },
select: ['id', 'isAdmin'],
});
if (user?.isAdmin) {
const superAdminRole = await this.roleRepository.findOne({
where: { baseRole: UserRole.SUPER_ADMIN, isSystem: true },
});
if (superAdminRole) {
const perms = await this.getRolePermissions(superAdminRole.id);
return new Set(perms);
}
}
// 通过租户成员角色
const membership = await this.tenantMemberRepository.findOne({
where: { userId, tenantId },
});
if (!membership) return new Set();
const role = await this.roleRepository.findOne({
where: { baseRole: membership.role, isSystem: true },
});
if (!role) return new Set();
const perms = await this.getRolePermissions(role.id);
return new Set(perms);
}
/**
* 检查用户是否有指定权限
*/
async checkPermission(userId: string, tenantId: string, permissionKey: string): Promise<boolean> {
// 先尝试从请求级缓存获取(由 PermissionsGuard 设置)
const perms = await this.getUserPermissions(userId, tenantId);
return perms.has(permissionKey);
}
// ──────────── 元数据 ────────────
getAllPermissionMeta(): PermissionMeta[] {
return PERMISSION_META;
}
getPermissionsByCategory(): Record<string, PermissionMeta[]> {
const grouped: Record<string, PermissionMeta[]> = {};
for (const meta of PERMISSION_META) {
if (!grouped[meta.category]) grouped[meta.category] = [];
grouped[meta.category].push(meta);
}
return grouped;
}
}