1-3 回顾 管道概念:三大类型管道及校验类管道
本节系统介绍 NestJS 管道(Pipe)的核心概念,包括管道的两大应用场景、三种作用范围,以及 class-validator + class-transformer 配合 ValidationPipe 的工作原理,为后续实现声明式参数校验奠定基础。
为什么需要管道?
前端参数校验有很多成熟方案(如 Zod、Joi 等),为什么要使用 NestJS 内置的管道机制?
| 特性 | 通用校验库(Zod / Joi) | NestJS 管道 |
|---|---|---|
| 集成方式 | 需手动在每处调用 | 自动在请求处理前执行 |
| 错误处理 | 需手动包装响应 | 自动抛出标准异常 |
| 框架整合 | 独立运行 | 与 IoC 容器、装饰器深度集成 |
| 性能 | 校验失败后仍可能进入处理逻辑 | 校验失败直接拦截,不占用数据库资源 |
管道的核心优势在于:校验失败时请求根本不会到达 Controller 和 Service,避免了不必要的数据库查询和资源消耗。Node.js 的异步非阻塞 I/O 模型在这种轻量级拦截场景下性能表现优异。
管道的两大应用场景
客户端请求 ──▶ 管道(Pipe)──▶ Controller ──▶ Service ──▶ Repository ──▶ 数据库
│
├── 转换(Transformation):将输入数据转换为目标类型
│ 例:字符串 "123" → 整数 123
│
└── 验证(Validation):校验输入数据的合法性
例:用户名长度 ≥ 6,密码格式正确
校验成功 → 继续传递数据
校验失败 → 直接抛出异常,返回错误响应
text
TypeScript 的局限性:TypeScript 的类型系统仅在编译时生效,运行时无法校验请求参数的实际值(如字符串长度、格式等)。管道在运行时层面弥补了这一缺陷。
管道接口定义
所有管道都必须实现 PipeTransform 接口:
import { PipeTransform, ArgumentMetadata } from '@nestjs/common';
interface PipeTransform<T, R> {
transform(value: T, metadata: ArgumentMetadata): R;
}
// ArgumentMetadata 结构
interface ArgumentMetadata {
type: 'body' | 'query' | 'param' | 'custom';
metatype: Type<unknown>;
data: string;
}
typescript
value— 待处理的输入数据(请求体、查询参数、路由参数等)metadata— 参数的元数据信息,包含参数类型、目标类型等- 返回值 — 转换或验证后的数据,传递给后续处理逻辑
管道的三种作用范围
NestJS 管道按作用范围分为三类:
1. 全局管道(Global Pipe)
应用级别,所有 Controller 的所有路由参数都会经过该管道。通常用于 ValidationPipe。
// main.ts
import { NestFactory } from '@nestjs/core';
import { AppModule } from './app.module';
import { ValidationPipe } from '@nestjs/common';
async function bootstrap() {
const app = await NestFactory.create(AppModule);
app.useGlobalPipes(
new ValidationPipe({
whitelist: true, // 自动剥离未在 DTO 中定义的属性
forbidNonWhitelisted: true, // 存在未定义属性时抛出异常
transform: true, // 自动将普通对象转换为 DTO 类实例
transformOptions: {
enableImplicitConversion: true,
},
}),
);
await app.listen(3000);
}
bootstrap();
typescript
2. Controller 级别管道
作用于单个 Controller 中的所有路由处理方法:
import { Controller, UsePipes, ValidationPipe } from '@nestjs/common';
@Controller('auth')
@UsePipes(new ValidationPipe({ transform: true }))
export class AuthController {
// 该 Controller 中所有方法的参数都会经过管道处理
}
typescript
3. 参数级别管道(Parameter-level Pipe)
作用于单个参数,主要用于数据转换:
import { Controller, Get, Param, ParseIntPipe } from '@nestjs/common';
@Controller('users')
export class UsersController {
@Get(':id')
findOne(@Param('id', new ParseIntPipe()) id: number) {
// id 已被自动转换为 number 类型
return { id };
}
}
typescript
三种管道范围对比:
| 范围 | 作用域 | 典型用途 | 注册方式 |
|---|---|---|---|
| 全局管道 | 所有 Controller、所有方法 | 全局参数校验(ValidationPipe) | app.useGlobalPipes() |
| Controller 级别 | 单个 Controller 的所有方法 | 特定模块的统一校验/转换 | @UsePipes() 装饰器 |
| 参数级别 | 单个参数 | 数据类型转换(如 ParseIntPipe) | 参数装饰器内传入 |
ValidationPipe 的工作原理
ValidationPipe 是 NestJS 内置的校验管道,它依赖两个第三方库:
请求数据(普通 JS 对象)
│
▼
┌─────────────────────┐
│ class-transformer │ ← 将普通对象转换为 DTO class 实例
│ plainToInstance() │
└──────────┬──────────┘
│ DTO 类实例
▼
┌─────────────────────┐
│ class-validator │ ← 根据装饰器规则校验 DTO 属性
│ validate() │
└──────────┬──────────┘
│
┌─────┴──────┐
│ │
校验通过 校验失败
│ │
▼ ▼
Controller 抛出异常
正常处理 返回 400 错误
text
两个核心库的职责:
| 库 | 作用 | 安装 |
|---|---|---|
class-transformer | 将普通 JS 对象转换为 class 实例 | npm i class-transformer |
class-validator | 基于装饰器对 class 属性进行校验 | npm i class-validator |
DTO 与 Entity 的区别
理解管道校验流程时,需要区分两个重要概念:
| 概念 | 用途 | 数据完整性 |
|---|---|---|
| DTO(Data Transfer Object) | 数据传输对象,定义接口入参/出参的结构 | 仅包含接口所需字段(如注册时只需 username + password) |
| Entity(实体) | 数据库模型,映射数据表结构 | 包含所有数据库字段(如 id、createdAt、updatedAt 等) |
// DTO — 注册请求的数据结构
export class SignUpDto {
username: string;
password: string;
// 没有 id、createdAt 等字段
}
// Entity — 数据库用户表的完整结构
export class User {
id: number;
username: string;
password: string;
email: string;
createdAt: Date;
updatedAt: Date;
}
typescript
DTO 和 Entity 都可以使用
class-validator装饰器进行校验,因为它们本质上都是 class。但推荐在 DTO 上定义校验规则,保持 Entity 纯粹反映数据库结构。
实现管道校验的完整步骤
后续章节将按照以下流程实现声明式参数校验:
1. 在 main.ts 中全局注册 ValidationPipe
└── app.useGlobalPipes(new ValidationPipe({ ... }))
2. 创建 DTO 类,使用 class-validator 装饰器定义校验规则
└── @IsString(), @MinLength(6), @MaxLength(16) 等
3. 在 Controller 方法参数中使用 DTO 类
└── @Body() signUpDto: SignUpDto
4. 请求到达时,ValidationPipe 自动执行转换和校验
└── 校验失败直接返回 400 错误,校验成功进入 Controller
text
NestJS 内置管道一览
| 管道 | 用途 | 示例 |
|---|---|---|
ValidationPipe | 基于 DTO class 校验请求参数 | 全局注册,配合 class-validator |
ParseIntPipe | 将字符串参数转换为整数 | @Param('id', ParseIntPipe) |
ParseBoolPipe | 将字符串参数转换为布尔值 | @Query('active', ParseBoolPipe) |
ParseArrayPipe | 将字符串参数解析为数组 | @Query('ids', new ParseArrayPipe()) |
ParseUUIDPipe | 校验参数是否为合法 UUID | @Param('id', new ParseUUIDPipe()) |
ParseEnumPipe | 校验参数是否为枚举值 | @Query('status', new ParseEnumPipe(Status)) |
DefaultValuePipe | 为参数设置默认值 | @Query('page', new DefaultValuePipe(1)) |
本节要点
- 管道两大场景:转换(数据类型变更)和验证(数据合法性检查),运行在 Controller 处理之前
- 三种作用范围:全局管道、Controller 级别、参数级别,各有适用场景
- 核心工具链:
class-transformer(对象转 class)+class-validator(装饰器校验)+ValidationPipe(管道执行) - DTO vs Entity:DTO 是接口数据传输的结构定义,Entity 是数据库模型的完整映射,两者职责不同
- 校验提前拦截:管道校验失败时请求不会到达数据库层,有效节省服务器资源
↑