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
+12 -46
View File
@@ -20,6 +20,8 @@ import { UpdateUserDto } from './dto/update-user.dto';
import { I18nService } from '../i18n/i18n.service';
import { UserRole } from './user-role.enum';
import { UserSettingService } from './user-setting.service';
import { Permission } from '../auth/permission/permission.decorator';
import { PermissionsGuard } from '../auth/permission/permission.guard';
@Controller('users')
@UseGuards(CombinedAuthGuard)
@@ -92,25 +94,17 @@ export class UserController {
}
@Get()
@UseGuards(PermissionsGuard)
@Permission('user:view')
async findAll(
@Request() req,
@Query('page') page?: string,
@Query('limit') limit?: string,
) {
const callerRole = req.user.role;
if (
callerRole !== UserRole.SUPER_ADMIN &&
callerRole !== UserRole.TENANT_ADMIN
) {
throw new ForbiddenException(
this.i18nService.getErrorMessage('adminOnlyViewList'),
);
}
const p = page ? parseInt(page) : undefined;
const l = limit ? parseInt(limit) : undefined;
if (callerRole === UserRole.SUPER_ADMIN) {
if (req.user.role === UserRole.SUPER_ADMIN) {
return this.userService.findAll(p, l);
} else {
return this.userService.findByTenantId(req.user.tenantId, p, l);
@@ -144,17 +138,9 @@ export class UserController {
}
@Post()
@UseGuards(PermissionsGuard)
@Permission('user:create')
async createUser(@Request() req, @Body() body: CreateUserDto) {
const callerRole = req.user.role;
if (
callerRole !== UserRole.SUPER_ADMIN &&
callerRole !== UserRole.TENANT_ADMIN
) {
throw new ForbiddenException(
this.i18nService.getErrorMessage('adminOnlyCreateUser'),
);
}
const { username, password } = body;
if (!username || !password) {
@@ -169,16 +155,9 @@ export class UserController {
);
}
// All new global users default to non-admin.
// Elevation to Super Admin status is handled separately.
// All new users default to non-admin.
let isAdmin = false;
if (callerRole === UserRole.SUPER_ADMIN) {
isAdmin = false;
} else if (callerRole === UserRole.TENANT_ADMIN) {
isAdmin = false;
}
// Pass the calculated params to the service
return this.userService.createUser(
username,
@@ -190,20 +169,14 @@ export class UserController {
}
@Put(':id')
@UseGuards(PermissionsGuard)
@Permission('user:edit')
async updateUser(
@Request() req,
@Body() body: UpdateUserDto,
@Param('id') id: string,
) {
const callerRole = req.user.role;
if (
callerRole !== UserRole.SUPER_ADMIN &&
callerRole !== UserRole.TENANT_ADMIN
) {
throw new ForbiddenException(
this.i18nService.getErrorMessage('adminOnlyUpdateUser'),
);
}
// Get user info to update
const userToUpdate = await this.userService.findOneById(id);
@@ -228,7 +201,6 @@ export class UserController {
}
// Role modification is now obsolete on global level.
// If Admin wants to elevate, they set isAdmin property directly.
if (body.isAdmin !== undefined && userToUpdate.isAdmin !== body.isAdmin) {
if (callerRole !== UserRole.SUPER_ADMIN) {
throw new ForbiddenException(
@@ -248,16 +220,10 @@ export class UserController {
}
@Delete(':id')
@UseGuards(PermissionsGuard)
@Permission('user:delete')
async deleteUser(@Request() req, @Param('id') id: string) {
const callerRole = req.user.role;
if (
callerRole !== UserRole.SUPER_ADMIN &&
callerRole !== UserRole.TENANT_ADMIN
) {
throw new ForbiddenException(
this.i18nService.getErrorMessage('adminOnlyDeleteUser'),
);
}
// Prevent admin from deleting themselves
if (req.user.id === id) {
+2
View File
@@ -8,12 +8,14 @@ import { ApiKey } from '../auth/entities/api-key.entity';
import { UserService } from './user.service';
import { UserController } from './user.controller';
import { TenantModule } from '../tenant/tenant.module';
import { PermissionModule } from '../auth/permission/permission.module';
@Global()
@Module({
imports: [
TypeOrmModule.forFeature([User, ApiKey, TenantMember, UserSetting]),
TenantModule,
PermissionModule,
],
controllers: [UserController],
providers: [UserService, UserSettingService],