You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何为不同用户角色复用同一个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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.25 11:48:34