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

NestJS中POST请求DTO验证正常,PUT请求验证失效求助

NestJS Validation 异常:PUT请求未触发枚举数组验证

我在使用NestJS的Validation时遇到问题:需要验证一个对象数组类型的属性,其中对象包含枚举数组类型的字段。POST请求下验证正常——传入枚举外的无效字符串会抛出错误,但使用相同校验规则的PUT请求中,包含枚举数组capabilities的defaultCapabilities字段却不触发验证。邮箱等其他字段的验证在两种请求中都正常工作。

附上相关代码,若需更多信息请告知。

创建DTO(TenantCreateDto)

import { ApiProperty } from '@nestjs/swagger';
import { UserRole, UserCapability, CapabilitiesMap } from '../schemas/supp-tenant.schema';
import { IsEmail, ValidateNested, IsArray } from '@nestjs/class-validator';
import { Type } from '@nestjs/class-transformer';

export class TenantCreateDto {

    @ApiProperty({ example: 'client1', description: '租户名称' })
    client: string;

    @IsEmail()
    @ApiProperty({ example: 'client1@gmail.com', description: '租户邮箱' })
    email: string;

    @IsArray()
    @Type(() => CapabilitiesMap)
    @ValidateNested({ each: true })
    @ApiProperty({
        example:
            [{
                role: UserRole.USER,
                capabilities: [UserCapability.CanCreatePost]
            }],
        type: [CapabilitiesMap],
        description: '租户默认权限配置'
    })
    defaultCapabilities: CapabilitiesMap[];

}

更新DTO(TenantUpdateDto)

import { ApiProperty } from '@nestjs/swagger';
import { UserRole, UserCapability, CapabilitiesMap } from '../schemas/supp-tenant.schema';
import { IsEmail, ValidateNested, IsArray } from '@nestjs/class-validator';
import { Type } from '@nestjs/class-transformer';

export class TenantUpdateDto {

    @ApiProperty({ example: 'client1', description: '租户名称' })
    client: string;

    @IsEmail()
    @ApiProperty({ example: 'client1@gmail.com', description: '租户邮箱' })
    email: string;

    @IsArray()
    @Type(() => CapabilitiesMap)
    @ValidateNested({ each: true })
    @ApiProperty({
        example:
            [{
                role: UserRole.USER,
                capabilities: [UserCapability.CanCreatePost]
            }],
        type: [CapabilitiesMap],
        description: '租户默认权限配置'
    })
    defaultCapabilities: CapabilitiesMap[];

}

控制器(TenantsController)

import { Body, Controller, Delete, Get, Param, Post, Put } from '@nestjs/common';
import { ApiTags, ApiOperation, ApiResponse } from '@nestjs/swagger';
import { TenantsService } from '../services/tenants.service';
import { TenantCreateDto } from '../dtos/tenant-create.dto';
import { TenantUpdateDto } from '../dtos/tenant-update.dto';
import { TenantResponseDto } from '../dtos/tenant-response.dto';

@ApiTags('Tenants')
@Controller('tenants')
export class TenantsController {
    constructor(private readonly tenantsService: TenantsService) { }

    @Post()
    @ApiOperation({ summary: '创建新租户' })
    @ApiResponse({ status: 201, description: '租户创建成功', type: TenantResponseDto })
    async create(@Body() createTenantDto: TenantCreateDto): Promise<TenantResponseDto> {
        return this.tenantsService.create(createTenantDto);
    }

    @Get()
    @ApiOperation({ summary: '获取所有租户' })
    @ApiResponse({ status: 200, description: '租户列表', type: [TenantResponseDto] })
    async findAll(): Promise<TenantResponseDto[]> {
        return this.tenantsService.findAll();
    }

    @Get(':id')
    @ApiOperation({ summary: '根据ID查询租户' })
    @ApiResponse({ status: 200, description: '匹配ID的租户信息', type: TenantResponseDto })
    async findOne(@Param('id') id: string): Promise<TenantResponseDto> {
        return this.tenantsService.findOne(id);
    }

    @Put(':id')
    @ApiOperation({ summary: '根据ID更新租户' })
    @ApiResponse({ status: 200, description: '租户更新成功', type: TenantResponseDto })
    async update(@Param('id') id: string, @Body() updateTenantDto: TenantUpdateDto): Promise<TenantResponseDto> {
        return this.tenantsService.update(id, updateTenantDto);
    }

    @Delete(':id')
    @ApiOperation({ summary: '根据ID删除租户' })
    @ApiResponse({ status: 200, description: '租户删除成功', type: TenantResponseDto })
    async remove(@Param('id') id: string): Promise<TenantResponseDto> {
        return this.tenantsService.remove(id);
    }
}

服务(TenantsService)

import { Injectable } from '@nestjs/common';
import { InjectModel } from '@nestjs/mongoose';
import { Model } from 'mongoose';
import { SuppTenant, SuppTenantDocument } from '../schemas/supp-tenant.schema';
import { TenantCreateDto } from '../dtos/tenant-create.dto';
import { TenantUpdateDto } from '../dtos/tenant-update.dto';
import { TenantResponseDto } from '../dtos/tenant-response.dto';

@Injectable()
export class TenantsService {
    constructor(@InjectModel(SuppTenant.name) private tenantModel: Model<SuppTenantDocument>) { }

    async create(createTenantDto: TenantCreateDto): Promise<TenantResponseDto> {
        const createdTenant = new this.tenantModel(createTenantDto);
        const savedTenant = await createdTenant.save();
        return new TenantResponseDto(savedTenant);
    }

    async findAll(): Promise<TenantResponseDto[]> {
        const tenants = await this.tenantModel.find().exec();
        return tenants.map(tenant => new TenantResponseDto(tenant));
    }

    async findOne(id: string): Promise<TenantResponseDto> {
        const tenant = await this.tenantModel.findById(id).exec();
        if (!tenant) {
            return null;
        }
        return new TenantResponseDto(tenant);
    }

    async update(id: string, updateTenantDto: TenantUpdateDto): Promise<TenantResponseDto> {
        const updatedTenant = await this.tenantModel.findByIdAndUpdate(id, updateTenantDto, { new: true }).exec();
        if (!updatedTenant) {
            return null;
        }
        return new TenantResponseDto(updatedTenant);
    }

    async remove(id: string): Promise<TenantResponseDto> {
        const removedTenant = await this.tenantModel.findByIdAndRemove(id).exec();
        if (!removedTenant) {
            return null;
        }
        return new TenantResponseDto(removedTenant);
    }
}

Mongo Schema(SuppTenantSchema)

我尝试在Mongo Schema中添加验证但无效:

import { IsArray, IsEnum } from '@nestjs/class-validator';
import { Prop, Schema, SchemaFactory } from '@nestjs/mongoose';
import { ApiProperty } from '@nestjs/swagger';
import { HydratedDocument } from 'mongoose';

export type SuppTenantDocument = HydratedDocument<SuppTenant>;

export enum UserCapability {
    CanCreatePost = 'canCreatePost',
    CanCreateComment = 'canCreateComment',
    CanSupport = 'canSupport',
    CanCreateReport = 'canCreateReport',
    CanCreateProject = 'canCreateProject',
}

export enum UserRole {
    ADMIN = 'admin',
    MODERATOR = 'moderator',
    EDITOR = 'editor',
    USER = 'user',
}

@Schema({ _id: false, versionKey: false })
export class CapabilitiesMap {

    @ApiProperty({
        enum: UserRole,
        default: UserRole.USER,
        example: UserRole.USER,
        description: '用户角色'
    })
    @Prop({ type: String, enum: UserRole })
    @IsEnum(UserRole)
    role: UserRole;

    @ApiProperty({
        enum: UserCapability,
        isArray: true,
        default: [],
        uniqueItems: true,
        description: '用户权限',
        example: [UserCapability.CanCreatePost]
    })
    @IsArray()
    @IsEnum(UserCapability, { each: true })
    @Prop({ type: [String], enum: UserCapability, default: [] })
    capabilities: UserCapability[];
}

const CapabilitiesMapSchema = SchemaFactory.createForClass(CapabilitiesMap);


@Schema()
export class SuppTenant {

    @Prop()
    client: string;

    @Prop({ required: true, minlength: 4, maxlength: 32, match: /.+@.+\..+/ })
    email: string;

    @Prop()
    creationDate: string;

    @Prop({ type: [CapabilitiesMapSchema], default: [] })
    defaultCapabilities: CapabilitiesMap[];

}

export const SuppTenantSchema = SchemaFactory.createForClass(SuppTenant);

期望行为:PUT请求中传入capabilities无效字符串时,能像POST请求一样触发验证错误,但目前PUT请求会直接接受无效值。

内容的提问来源于stack exchange,提问作者SteDeus

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.15 13:23:09