feat(api): 添加用户角色管理接口
- GET /users/:id/roles 获取用户详情(包含角色) - PATCH /users/:id/roles 分配角色给用户 - 新增 AssignRolesDto、UserRoleResponseDto、UserWithRolesResponseDto Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -1,6 +1,12 @@
|
||||
import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger';
|
||||
import type { UpdateUserDto as IUpdateUserDto, UserResponse } from '@seclusion/shared';
|
||||
import { IsString, IsOptional } from 'class-validator';
|
||||
import type {
|
||||
AssignRolesDto as IAssignRolesDto,
|
||||
UpdateUserDto as IUpdateUserDto,
|
||||
UserResponse,
|
||||
UserRoleResponse,
|
||||
UserWithRolesResponse,
|
||||
} from '@seclusion/shared';
|
||||
import { IsString, IsOptional, IsArray } from 'class-validator';
|
||||
|
||||
import { createPaginatedResponseDto } from '@/common/crud/dto/paginated-response.dto';
|
||||
|
||||
@@ -11,6 +17,30 @@ export class UpdateUserDto implements IUpdateUserDto {
|
||||
name?: string;
|
||||
}
|
||||
|
||||
/** 分配角色请求 DTO */
|
||||
export class AssignRolesDto implements IAssignRolesDto {
|
||||
@ApiProperty({
|
||||
example: ['role_id_1', 'role_id_2'],
|
||||
description: '角色 ID 列表',
|
||||
type: [String],
|
||||
})
|
||||
@IsArray()
|
||||
@IsString({ each: true })
|
||||
roleIds: string[];
|
||||
}
|
||||
|
||||
/** 用户角色响应 DTO */
|
||||
export class UserRoleResponseDto implements UserRoleResponse {
|
||||
@ApiProperty({ example: 'clxxx123', description: '角色 ID' })
|
||||
id: string;
|
||||
|
||||
@ApiProperty({ example: 'user', description: '角色编码' })
|
||||
code: string;
|
||||
|
||||
@ApiProperty({ example: '普通用户', description: '角色名称' })
|
||||
name: string;
|
||||
}
|
||||
|
||||
/** 用户响应 DTO */
|
||||
export class UserResponseDto implements UserResponse {
|
||||
@ApiProperty({ example: 'clxxx123', description: '用户 ID' })
|
||||
@@ -32,5 +62,29 @@ export class UserResponseDto implements UserResponse {
|
||||
deletedAt?: string | null;
|
||||
}
|
||||
|
||||
/** 用户详情响应(包含角色) */
|
||||
export class UserWithRolesResponseDto implements UserWithRolesResponse {
|
||||
@ApiProperty({ example: 'clxxx123', description: '用户 ID' })
|
||||
id: string;
|
||||
|
||||
@ApiProperty({ example: 'user@example.com', description: '用户邮箱' })
|
||||
email: string;
|
||||
|
||||
@ApiProperty({ example: '张三', description: '用户名称', nullable: true })
|
||||
name: string | null;
|
||||
|
||||
@ApiProperty({ example: false, description: '是否为超级管理员' })
|
||||
isSuperAdmin: boolean;
|
||||
|
||||
@ApiProperty({ type: [UserRoleResponseDto], description: '用户角色列表' })
|
||||
roles: UserRoleResponseDto[];
|
||||
|
||||
@ApiProperty({ example: '2026-01-16T10:00:00.000Z', description: '创建时间' })
|
||||
createdAt: string;
|
||||
|
||||
@ApiProperty({ example: '2026-01-16T10:00:00.000Z', description: '更新时间' })
|
||||
updatedAt: string;
|
||||
}
|
||||
|
||||
/** 分页用户响应 DTO */
|
||||
export class PaginatedUserResponseDto extends createPaginatedResponseDto(UserResponseDto) {}
|
||||
|
||||
@@ -1,7 +1,13 @@
|
||||
import { Controller, Get, Patch, Delete, Param, Body, Query, UseGuards } from '@nestjs/common';
|
||||
import { ApiTags, ApiOperation, ApiBearerAuth, ApiOkResponse } from '@nestjs/swagger';
|
||||
|
||||
import { UpdateUserDto, UserResponseDto, PaginatedUserResponseDto } from './dto/user.dto';
|
||||
import {
|
||||
AssignRolesDto,
|
||||
UpdateUserDto,
|
||||
UserResponseDto,
|
||||
UserWithRolesResponseDto,
|
||||
PaginatedUserResponseDto,
|
||||
} from './dto/user.dto';
|
||||
import { UserService } from './user.service';
|
||||
|
||||
import { JwtAuthGuard } from '@/auth/guards/jwt-auth.guard';
|
||||
@@ -28,6 +34,13 @@ export class UserController {
|
||||
return this.userService.findDeleted(query);
|
||||
}
|
||||
|
||||
@Get(':id/roles')
|
||||
@ApiOperation({ summary: '获取用户详情(包含角色)' })
|
||||
@ApiOkResponse({ type: UserWithRolesResponseDto, description: '用户详情及角色' })
|
||||
findByIdWithRoles(@Param('id') id: string) {
|
||||
return this.userService.findByIdWithRoles(id);
|
||||
}
|
||||
|
||||
@Get(':id')
|
||||
@ApiOperation({ summary: '根据 ID 获取用户' })
|
||||
@ApiOkResponse({ type: UserResponseDto, description: '用户详情' })
|
||||
@@ -42,6 +55,13 @@ export class UserController {
|
||||
return this.userService.update(id, dto);
|
||||
}
|
||||
|
||||
@Patch(':id/roles')
|
||||
@ApiOperation({ summary: '分配角色给用户' })
|
||||
@ApiOkResponse({ type: UserWithRolesResponseDto, description: '分配角色后的用户信息' })
|
||||
assignRoles(@Param('id') id: string, @Body() dto: AssignRolesDto) {
|
||||
return this.userService.assignRoles(id, dto);
|
||||
}
|
||||
|
||||
@Delete(':id')
|
||||
@ApiOperation({ summary: '删除用户' })
|
||||
@ApiOkResponse({ type: UserResponseDto, description: '被删除的用户信息' })
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { Injectable } from '@nestjs/common';
|
||||
import { Injectable, NotFoundException } from '@nestjs/common';
|
||||
import { Prisma, User } from '@prisma/client';
|
||||
import type { AssignRolesDto, UserWithRolesResponse } from '@seclusion/shared';
|
||||
|
||||
import { UpdateUserDto } from './dto/user.dto';
|
||||
|
||||
@@ -44,4 +45,77 @@ export class UserService extends CrudService<
|
||||
protected getDeletedNotFoundMessage(): string {
|
||||
return '已删除的用户不存在';
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取用户详情(包含角色信息)
|
||||
*/
|
||||
async findByIdWithRoles(id: string): Promise<UserWithRolesResponse> {
|
||||
const user = await this.prisma.user.findUnique({
|
||||
where: { id },
|
||||
include: {
|
||||
roles: {
|
||||
include: {
|
||||
role: {
|
||||
select: {
|
||||
id: true,
|
||||
code: true,
|
||||
name: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
if (!user) {
|
||||
throw new NotFoundException('用户不存在');
|
||||
}
|
||||
|
||||
return {
|
||||
id: user.id,
|
||||
email: user.email,
|
||||
name: user.name,
|
||||
isSuperAdmin: user.isSuperAdmin,
|
||||
roles: user.roles.map((ur) => ur.role),
|
||||
createdAt: user.createdAt.toISOString(),
|
||||
updatedAt: user.updatedAt.toISOString(),
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* 分配角色给用户
|
||||
* 会先删除用户现有的所有角色,再分配新角色
|
||||
*/
|
||||
async assignRoles(userId: string, dto: AssignRolesDto): Promise<UserWithRolesResponse> {
|
||||
// 检查用户是否存在
|
||||
const user = await this.prisma.user.findUnique({
|
||||
where: { id: userId },
|
||||
});
|
||||
|
||||
if (!user) {
|
||||
throw new NotFoundException('用户不存在');
|
||||
}
|
||||
|
||||
// 使用事务:先删除所有现有角色,再添加新角色
|
||||
await this.prisma.client.$transaction(async (tx) => {
|
||||
// 删除现有角色关联
|
||||
await tx.userRole.deleteMany({
|
||||
where: { userId },
|
||||
});
|
||||
|
||||
// 添加新角色关联
|
||||
if (dto.roleIds.length > 0) {
|
||||
await tx.userRole.createMany({
|
||||
data: dto.roleIds.map((roleId) => ({
|
||||
userId,
|
||||
roleId,
|
||||
})),
|
||||
skipDuplicates: true,
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// 返回更新后的用户信息
|
||||
return this.findByIdWithRoles(userId);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user