forked from wangziqi/gongxue-base
feat(rbac): add RbacController with role CRUD, permissions tree, and user management endpoints
This commit is contained in:
68
backend/src/rbac/dto/rbac.dto.ts
Normal file
68
backend/src/rbac/dto/rbac.dto.ts
Normal 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;
|
||||
}
|
||||
141
backend/src/rbac/rbac.controller.ts
Normal file
141
backend/src/rbac/rbac.controller.ts
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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],
|
||||
})
|
||||
|
||||
@@ -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: '用户已删除' };
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user