feat(rbac): add RbacController with role CRUD, permissions tree, and user management endpoints

This commit is contained in:
2026-07-02 11:42:23 +08:00
parent 9051497704
commit 2f793833eb
4 changed files with 277 additions and 1 deletions

View File

@@ -0,0 +1,68 @@
import { IsString, MinLength, IsOptional, IsArray, IsBoolean } from 'class-validator';
export class CreateRoleDto {
@IsString()
name: string;
@IsOptional()
@IsString()
description?: string;
@IsOptional()
@IsArray()
permissionIds?: number[];
}
export class UpdateRoleDto {
@IsOptional()
@IsString()
name?: string;
@IsOptional()
@IsString()
description?: string;
@IsOptional()
@IsArray()
permissionIds?: number[];
}
export class CreateUserDto {
@IsString()
username: string;
@IsString()
@MinLength(4)
password: string;
@IsString()
name: string;
@IsOptional()
@IsArray()
roleIds?: number[];
}
export class UpdateUserDto {
@IsOptional()
@IsString()
username?: string;
@IsOptional()
@IsString()
name?: string;
@IsOptional()
@IsBoolean()
isActive?: boolean;
@IsOptional()
@IsArray()
roleIds?: number[];
}
export class ResetPasswordDto {
@IsString()
@MinLength(4)
password: string;
}

View File

@@ -0,0 +1,141 @@
import {
Controller, Get, Post, Put, Delete, Body, Param, UseGuards, Request, BadRequestException,
} from '@nestjs/common';
import { RbacService } from './rbac.service';
import { CreateRoleDto, UpdateRoleDto, CreateUserDto, UpdateUserDto, ResetPasswordDto } from './dto/rbac.dto';
import { JwtAuthGuard } from '../auth/guards/jwt-auth.guard';
import { RequirePermission } from '../auth/decorators/permission.decorator';
import { OperationLogsService } from '../operation-logs/operation-logs.service';
import { extractRequestInfo } from '../common/request-utils';
@UseGuards(JwtAuthGuard)
@Controller('rbac')
export class RbacController {
constructor(
private rbacService: RbacService,
private logService: OperationLogsService,
) {}
// ==================== 角色管理 ====================
@Get('roles')
@RequirePermission('role:view')
findAllRoles() {
return this.rbacService.findAllRoles();
}
@Get('roles/:id')
@RequirePermission('role:view')
findRoleById(@Param('id') id: string) {
return this.rbacService.findRoleById(+id);
}
@Post('roles')
@RequirePermission('role:create')
async createRole(@Body() dto: CreateRoleDto, @Request() req: any) {
const { ipAddress, userAgent } = extractRequestInfo(req);
const result = await this.rbacService.createRole(dto);
await this.logService.log({ userId: req.user?.id, username: req.user?.username, module: 'RBAC', action: '创建角色', detail: `角色: ${dto.name}`, ipAddress, userAgent });
return result;
}
@Put('roles/:id')
@RequirePermission('role:edit')
async updateRole(@Param('id') id: string, @Body() dto: UpdateRoleDto, @Request() req: any) {
const { ipAddress, userAgent } = extractRequestInfo(req);
try {
const result = await this.rbacService.updateRole(+id, dto);
await this.logService.log({ userId: req.user?.id, username: req.user?.username, module: 'RBAC', action: '编辑角色', targetId: +id, targetType: 'role', detail: JSON.stringify(dto), ipAddress, userAgent });
return result;
} catch (e: any) {
throw new BadRequestException(e.message);
}
}
@Delete('roles/:id')
@RequirePermission('role:delete')
async deleteRole(@Param('id') id: string, @Request() req: any) {
const { ipAddress, userAgent } = extractRequestInfo(req);
try {
const result = await this.rbacService.deleteRole(+id);
await this.logService.log({ userId: req.user?.id, username: req.user?.username, module: 'RBAC', action: '删除角色', targetId: +id, targetType: 'role', ipAddress, userAgent });
return result;
} catch (e: any) {
throw new BadRequestException(e.message);
}
}
// ==================== 权限管理 ====================
@Get('permissions')
@RequirePermission('role:view')
findAllPermissions() {
return this.rbacService.findAllPermissions();
}
@Get('permissions/tree')
@RequirePermission('role:view')
getPermissionTree() {
return this.rbacService.getPermissionTree();
}
// ==================== 用户管理 ====================
@Get('users')
@RequirePermission('user:view')
getUsers() {
return this.rbacService.findAllUsers();
}
@Post('users')
@RequirePermission('user:create')
async createUser(@Body() dto: CreateUserDto, @Request() req: any) {
const { ipAddress, userAgent } = extractRequestInfo(req);
try {
const result = await this.rbacService.createUser(dto);
await this.logService.log({ userId: req.user?.id, username: req.user?.username, module: '账号', action: '创建账号', detail: `用户名: ${dto.username}`, ipAddress, userAgent });
return result;
} catch (e: any) {
throw new BadRequestException(e.message);
}
}
@Put('users/:id')
@RequirePermission('user:edit')
async updateUser(@Param('id') id: string, @Body() dto: UpdateUserDto, @Request() req: any) {
const { ipAddress, userAgent } = extractRequestInfo(req);
try {
const result = await this.rbacService.updateUser(+id, dto);
await this.logService.log({ userId: req.user?.id, username: req.user?.username, module: '账号', action: '更新账号', targetId: +id, targetType: 'user', detail: JSON.stringify(dto), ipAddress, userAgent });
return result;
} catch (e: any) {
throw new BadRequestException(e.message);
}
}
@Put('users/:id/password')
@RequirePermission('user:reset-password')
async resetPassword(@Param('id') id: string, @Body() dto: ResetPasswordDto, @Request() req: any) {
const { ipAddress, userAgent } = extractRequestInfo(req);
try {
const result = await this.rbacService.resetPassword(+id, dto.password);
await this.logService.log({ userId: req.user?.id, username: req.user?.username, module: '账号', action: '重置密码', targetId: +id, targetType: 'user', ipAddress, userAgent });
return result;
} catch (e: any) {
throw new BadRequestException(e.message);
}
}
@Delete('users/:id')
@RequirePermission('user:delete')
async deleteUser(@Param('id') id: string, @Request() req: any) {
const { ipAddress, userAgent } = extractRequestInfo(req);
try {
const result = await this.rbacService.deleteUser(+id);
await this.logService.log({ userId: req.user?.id, username: req.user?.username, module: '账号', action: '删除账号', targetId: +id, targetType: 'user', ipAddress, userAgent });
return result;
} catch (e: any) {
throw new BadRequestException(e.message);
}
}
}

View File

@@ -2,6 +2,7 @@ import { Module, OnModuleInit, forwardRef } from '@nestjs/common';
import { TypeOrmModule } from '@nestjs/typeorm';
import { Permission, Role, User } from '../entities';
import { RbacService } from './rbac.service';
import { RbacController } from './rbac.controller';
import { AuthModule } from '../auth/auth.module';
@Module({
@@ -9,7 +10,7 @@ import { AuthModule } from '../auth/auth.module';
TypeOrmModule.forFeature([Permission, Role, User]),
forwardRef(() => AuthModule),
],
controllers: [],
controllers: [RbacController],
providers: [RbacService],
exports: [RbacService],
})

View File

@@ -262,4 +262,70 @@ export class RbacService {
}
return Array.from(codes);
}
// ---- 用户管理 ----
async findAllUsers() {
const users = await this.userRepo.find({
relations: ['roles'],
order: { createdAt: 'DESC' },
});
return users.map(u => ({
id: u.id,
username: u.username,
name: u.name,
isActive: u.isActive,
lastLoginAt: u.lastLoginAt,
createdAt: u.createdAt,
updatedAt: u.updatedAt,
roles: u.roles?.map(r => ({ id: r.id, name: r.name })) || [],
}));
}
async createUser(dto: { username: string; password: string; name: string; roleIds?: number[] }) {
const exists = await this.userRepo.findOne({ where: { username: dto.username } });
if (exists) throw new Error('用户名已存在');
const hash = await bcrypt.hash(dto.password, 10);
const user = this.userRepo.create({ username: dto.username, passwordHash: hash, name: dto.name });
if (dto.roleIds && dto.roleIds.length > 0) {
user.roles = await this.roleRepo.findByIds(dto.roleIds);
}
await this.userRepo.save(user);
return { message: '用户创建成功' };
}
async updateUser(id: number, dto: { username?: string; name?: string; isActive?: boolean; roleIds?: number[] }) {
const user = await this.userRepo.findOne({ where: { id }, relations: ['roles'] });
if (!user) throw new Error('用户不存在');
if (dto.username !== undefined && dto.username !== user.username) {
const exists = await this.userRepo.findOne({ where: { username: dto.username } });
if (exists) throw new Error('用户名已存在');
user.username = dto.username;
}
if (dto.name !== undefined) user.name = dto.name;
if (dto.isActive !== undefined) user.isActive = dto.isActive;
if (dto.roleIds !== undefined) {
user.roles = dto.roleIds.length > 0
? await this.roleRepo.findByIds(dto.roleIds)
: [];
}
await this.userRepo.save(user);
return { message: '更新成功' };
}
async resetPassword(id: number, newPassword: string) {
const user = await this.userRepo.findOne({ where: { id } });
if (!user) throw new Error('用户不存在');
user.passwordHash = await bcrypt.hash(newPassword, 10);
await this.userRepo.save(user);
return { message: '密码已重置' };
}
async deleteUser(id: number) {
const user = await this.userRepo.findOne({ where: { id } });
if (!user) throw new Error('用户不存在');
if (user.username === 'admin') throw new Error('不能删除默认管理员');
await this.userRepo.remove(user);
return { message: '用户已删除' };
}
}