概述
本节进阶内容围绕用户更新接口展开,解决三个核心问题:DTO 反序列化定义、嵌套数据(角色+权限)的类型转换与校验、以及使用事务保证关联数据更新的原子性。重点掌握 class-transformer 的 @Type() 与 @Transform() 组合处理多态数据结构。
UpdateUserDto 定义
基础结构
// update-user.dto.ts
import { PartialType } from '@nestjs/mapped-types';
import { CreateRoleDto } from './create-role.dto';
import { IsOptional, IsArray, ValidateIf } from 'class-validator';
export class UpdateUserDto extends PartialType(CreateUserDto) {
@ValidateIf((o) => !o.username)
id: number;
@ValidateIf((o) => !o.id)
username: string;
@IsOptional()
@IsArray()
roles: CreateRoleDto[];
}
typescript
核心设计点:
| 约束 | 实现方式 | 说明 |
|---|---|---|
| 二选一定位 | @ValidateIf 互相排除 | id 和 username 必须传其一 |
| 可选更新 | PartialType | 所有 CreateUserDto 字段变为可选 |
| 嵌套角色 | roles: CreateRoleDto[] | 支持同时更新角色信息 |
二选一校验逻辑
// id 和 username 至少传一个
@ValidateIf((o) => !o.username) // 只在 username 为空时校验 id
id: number;
@ValidateIf((o) => !o.id) // 只在 id 为空时校验 username
username: string;
typescript
这种写法确保三种情况都能通过:传 id、传 username、两个都传。
嵌套数据序列化:多态 Permission 处理
问题分析
前端传递的 permissions 数据可能有两种格式:
// 格式一:对象数组
[{ "name": "read", "action": "manage" }]
// 格式二:字符串数组
["read:manage", "update:delete"]
json
解决方案:Type + Transform 组合
// create-role.dto.ts
import { Type, Transform, plainToInstance } from 'class-transformer';
import { CreatePermissionDto } from './create-permission.dto';
export class CreateRoleDto {
@IsOptional()
id: number;
@IsOptional()
name: string;
@IsOptional()
@Type(() => CreatePermissionDto) // 处理对象数组
@Transform(({ value }) => // 处理字符串数组
value?.map((item) => {
if (typeof item === 'string') {
const parts = item.split(':');
return plainToInstance(CreatePermissionDto, {
name: parts[0],
action: parts[1],
});
}
return plainToInstance(CreatePermissionDto, item);
}),
)
permissions: CreatePermissionDto[];
}
typescript
执行流程:
请求数据 → @Type() 尝试转换为 CreatePermissionDto[]
→ @Transform() 对每个元素判断类型
→ string: split(':') → 构造 DTO
→ object: 直接转换
→ 输出统一的 CreatePermissionDto[]
text
@Type 与 @Transform 职责分离
| 装饰器 | 职责 | 适用场景 |
|---|---|---|
@Type() | 类型转换,将普通对象转为 class 实例 | 数据结构已确定 |
@Transform() | 自定义数据转换逻辑 | 数据结构不确定,需动态判断 |
原则:
@Type()只关注类型转换,不包含业务逻辑;复杂逻辑写在@Transform()中。
进阶:class-transformer 多类型支持
discriminator 多态方案
当某个字段需要支持多种 class 类型时,class-transformer 提供了 discriminator 机制:
// 方案:使用 discriminator 区分多个子类型
import { Type } from 'class-transformer';
abstract class BasePermission {
public type: string;
}
class StringPermission extends BasePermission {
public type = 'string';
}
class DetailPermission extends BasePermission {
public type = 'detail';
}
export class CreateRoleDto {
@Type(() => BasePermission, {
discriminator: {
property: 'type',
subTypes: [
{ value: StringPermission, name: 'string' },
{ value: DetailPermission, name: 'detail' },
],
},
})
permissions: BasePermission[];
}
typescript
discriminator 工作原理:
传入数据 { type: 'detail', name: 'read', action: 'manage' }
↓
读取 property: 'type' 的值 → 'detail'
↓
匹配 subTypes 中 name='detail' → DetailPermission
↓
使用 DetailPermission 进行转换
text
此方案为 class-transformer 内置能力,可根据项目需要选择使用
discriminator或@Transform()自定义方案。
事务更新:Prisma $transaction
用户更新涉及关联表操作,必须使用事务保证数据一致性:
// user.prisma.repository.ts
async update(username: string, updateUserDto: UpdateUserDto) {
const { id, userObj, roles, ...userData } = updateUserDto as any;
const whereCondition = id ? { id } : { username };
return this.prismaClient.$transaction(async (tx) => {
// 1. 更新用户基本信息
const updatedUser = await tx.user.update({
where: whereCondition,
data: userData,
});
// 2. 更新角色关联(如有)
if (roles?.length) {
// ...角色和权限的关联更新逻辑
}
return updatedUser;
});
}
typescript
事务执行流程:
$transaction 开始
├── 更新用户基本信息
├── 更新角色关联
└── 更新权限关联
所有操作成功 → COMMIT
任一操作失败 → ROLLBACK
text
Controller 完整接口
// user.controller.ts
@Patch(':username')
async update(
@Body() updateUserDto: UpdateUserDto,
) {
return this.userService.update(updateUserDto);
}
@Delete(':id')
async remove(@Param('id') id: string) {
return this.userService.remove(+id);
}
typescript
关键知识点总结
| 知识点 | 说明 |
|---|---|
@ValidateIf 互斥校验 | 实现字段二选一的校验逻辑 |
@Type() + @Transform() | 组合处理多态数据结构 |
plainToInstance | 手动将普通对象转为 class 实例 |
discriminator | class-transformer 内置多类型支持 |
Prisma $transaction | 事务保证关联表更新的原子性 |
↑