forked from hangshuo652/aurak
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:
@@ -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 admin(isAdmin=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;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user