如何为不同用户角色复用同一个NestJS DTO
复用单个DTO与服务的实现方案
1. 给DTO添加角色动态验证
没必要写两个DTO,咱们可以通过自定义验证器结合请求上下文,让同一个UpdateAccountDto根据当前用户角色自动判断字段是否可修改:
先写一个自定义验证器,用来校验当前用户有没有权限修改某个字段:
import { ValidatorConstraint, ValidatorConstraintInterface, ValidationArguments } from 'class-validator'; import { ExecutionContext, Injectable } from '@nestjs/common'; import { Request } from 'express'; @ValidatorConstraint({ async: true }) @Injectable() export class IsAllowedFieldForRole implements ValidatorConstraintInterface { constructor(private readonly executionContext: ExecutionContext) {} async validate(_: any, args: ValidationArguments) { const request = this.executionContext.switchToHttp().getRequest<Request>(); const user = request.user; // 假设请求中已挂载当前用户信息,包含role字段 const fieldName = args.property; // 普通用户仅能修改locale字段 if (user.role === 'user' && fieldName !== 'locale') { return false; } // 管理员无权限限制 return true; } defaultMessage(args: ValidationArguments) { return `普通用户无权修改${args.property}字段`; } }
然后修改原DTO,给需要权限控制的字段添加上这个验证器:
import { ApiPropertyOptional } from '@nestjs/swagger'; import { IsOptional, IsBoolean, IsString, Validate } from 'class-validator'; import { IsAllowedFieldForRole } from './is-allowed-field-for-role.validator'; export class UpdateAccountDto { @ApiPropertyOptional() @IsOptional() @Validate(IsAllowedFieldForRole) roles: string[]; @ApiPropertyOptional() @IsOptional() @Validate(IsAllowedFieldForRole) managers: string[]; @ApiPropertyOptional() @IsOptional() @IsBoolean() @Validate(IsAllowedFieldForRole) status: boolean; @ApiPropertyOptional() @IsString() @IsOptional() locale: string; }
2. 服务层做字段过滤
就算验证通过,服务层也得再加一层字段过滤,防止有人绕过验证提交非法字段:
@Injectable() export class AccountService { async updateAccount(userId: string, updateDto: UpdateAccountDto, currentUser: any) { // 根据角色筛选允许更新的字段 const allowedFields = currentUser.role === 'admin' ? updateDto : { locale: updateDto.locale }; // 执行数据库更新操作,例如使用TypeORM的update方法 return this.accountRepository.update(userId, allowedFields); } }
3. 控制器传递用户信息
在控制器里从请求中获取当前用户,传给服务即可:
@Controller('accounts') export class AccountController { constructor(private readonly accountService: AccountService) {} @Patch(':id') async update( @Param('id') id: string, @Body() updateAccountDto: UpdateAccountDto, @Request() req: any, // 假设req.user已通过AuthGuard挂载用户信息 ) { return this.accountService.updateAccount(id, updateAccountDto, req.user); } }
额外优化:用守卫提前清理非法字段
要是想在验证之前就把普通用户提交的非法字段删掉,还能写个自定义守卫:
@Injectable() export class UpdateAccountGuard implements CanActivate { canActivate(context: ExecutionContext): boolean { const request = context.switchToHttp().getRequest<Request>(); const user = request.user; const body = request.body; if (user.role === 'user') { // 仅保留locale字段,其余字段直接删除 Object.keys(body).forEach(key => { if (key !== 'locale') delete body[key]; }); } return true; } }
然后在控制器方法上挂载这个守卫:
@Patch(':id') @UseGuards(UpdateAccountGuard) async update(...) { ... }
这样一套实现下来,只用单个DTO和服务就能完美区分管理员与普通用户的更新权限。
内容的提问来源于stack exchange,提问作者Catopus
相关产品推荐
相关产品推荐

