1. 先说前端的表单验证
你有没有被表单验证折磨过?
// 前端表单验证
const form = {
username: '',
age: 18,
email: ''
};
// 手动验证
if (!form.username) {
alert('用户名不能为空');
}
if (form.age < 0 || form.age > 150) {
alert('年龄不合法');
}
if (!form.email.includes('@')) {
alert('邮箱格式不对');
}
太麻烦了!有没有更优雅的方式?
后来 Element Plus 推出了表单验证功能:
<!-- Element Plus 表单验证 -->
<el-form :model="form" :rules="rules" ref="formRef">
<el-form-item prop="username">
<el-input v-model="form.username" />
</el-form-item>
<el-form-item prop="age">
<el-input v-model.number="form.age" />
</el-form-item>
<el-form-item prop="email">
<el-input v-model="form.email" />
</el-form-item>
</el-form>
<script setup>
const form = reactive({
username: '',
age: 18,
email: ''
});
const rules = {
username: [
{ required: true, message: '用户名不能为空', trigger: 'blur' },
{ min: 3, message: '用户名至少3个字符', trigger: 'blur' }
],
age: [
{ required: true, message: '年龄不能为空', trigger: 'blur' },
{ type: 'number', min: 0, max: 150, message: '年龄范围 0-150', trigger: 'blur' }
],
email: [
{ required: true, message: '邮箱不能为空', trigger: 'blur' },
{ type: 'email', message: '邮箱格式不正确', trigger: 'blur' }
]
};
</script>
声明式验证,写一次,到处都用。
2. 后端也需要验证!
前端验证只是第一道防线,后端验证才是最后一道防线。
如果有人绕过前端直接调接口咋办?
// Post 请求接收数据
@Post('/user')
createUser(@Body() body: any) {
// 万一 age 传个 "hello" 字符串咋办?
console.log(body.age + 1); // "hello" + 1 = "hello1" ???
}
后端必须验证!
3. Nest 怎么办?用 ValidationPipe!
第 1 步:安装依赖
npm install class-validator class-transformer
第 2 步:创建 DTO
// user.dto.ts
export class CreateUserDto {
@IsString()
@MinLength(3)
username: string;
@IsInt()
@Min(0)
@Max(150)
age: number;
@IsEmail()
email: string;
}
DTO = Data Transfer Object,就是"数据传输对象",用来接收请求体的数据。
对比前端 Element Plus(rules 配置):
const rules = {
username: [
{ required: true, message: '用户名不能为空', trigger: 'blur' },
{ min: 3, message: '用户名至少3个字符', trigger: 'blur' }
],
age: [
{ required: true, message: '年龄不能为空', trigger: 'blur' },
{ type: 'number', min: 0, max: 150, message: '年龄范围 0-150', trigger: 'blur' }
],
email: [
{ required: true, message: '邮箱不能为空', trigger: 'blur' },
{ type: 'email', message: '邮箱格式不正确', trigger: 'blur' }
]
};
是不是几乎一样!
第 3 步:在 Controller 使用
@Post('/user')
createUser(
@Body(new ValidationPipe()) body: CreateUserDto
) {
return body;
}
第 4 步:验证不通过会怎样?
// 请求体
{
"username": "ab", // 长度不够!
"age": -5, // 负数!
"email": "invalid" // 邮箱格式不对!
}
返回 400 错误:
{
"statusCode": 400,
"message": [
"username must be longer than or equal to 3 characters",
"age must not be less than 0",
"email must be an email"
],
"error": "Bad Request"
}
4. 核心原理:class-validator + class-transformer
class-transformer:普通对象 → Class 实例
import { plainToInstance } from 'class-transformer';
const dto = plainToInstance(CreateUserDto, { username: 'abc' });
// 普通的 { username: 'abc' } → CreateUserDto 实例
class-validator:验证装饰器
import { IsString, IsInt, Min, Max, IsEmail } from 'class-validator';
class CreateUserDto {
@IsString() // 必须是字符串
@MinLength(3) // 最小长度 3
username: string;
@IsInt() // 必须是整数
@Min(0) // 最小值 0
@Max(150) // 最大值 150
age: number;
@IsEmail() // 必须是邮箱格式
email: string;
}
对比前端 Element Plus:
// rules 配置
const rules = {
username: [
{ required: true, message: '用户名不能为空', trigger: 'blur' },
{ min: 3, message: '用户名至少3个字符', trigger: 'blur' }
],
age: [
{ required: true, message: '年龄不能为空', trigger: 'blur' },
{ type: 'number', min: 0, max: 150, message: '年龄范围 0-150', trigger: 'blur' }
],
email: [
{ required: true, message: '邮箱不能为空', trigger: 'blur' },
{ type: 'email', message: '邮箱格式不正确', trigger: 'blur' }
]
};
5. 常用验证装饰器一览
| Nest 装饰器 | 作用 | Element Plus rules 对比 |
|---|---|---|
@IsString() | 字符串 | { type: 'string' } |
@IsInt() | 整数 | { type: 'integer' } |
@IsEmail() | 邮箱 | { type: 'email' } |
@IsUrl() | URL | { type: 'url' } |
@MinLength(n) | 最小长度 | { min: n, message: '...' } |
@MaxLength(n) | 最大长度 | { max: n, message: '...' } |
@Min(n) | 最小值 | { min: n, message: '...' } |
@Max(n) | 最大值 | { max: n, message: '...' } |
@IsArray() | 数组 | { type: 'array' } |
@IsEnum() | 枚举 | { type: 'enum' } |
@IsOptional() | 可选 | required: false |
6. 自定义错误消息
@MinLength(3, {
message: '用户名至少3个字符'
})
@Max(150, {
message(args) {
return `年龄不能超过 ${args.constraints[0]}`;
}
})
age: number;
对比前端 Element Plus:
// rules 配置
const rules = {
age: [
{ required: true, message: '年龄不能为空', trigger: 'blur' },
{ type: 'number', min: 0, message: '年龄不能为负数', trigger: 'blur' },
{ type: 'number', max: 150, message: '年龄不能超过150', trigger: 'blur' }
]
};
7. 全局验证?一键启用!
每次手动加 new ValidationPipe() 太麻烦?
// main.ts
app.useGlobalPipes(new ValidationPipe({
whitelist: true, // 自动过滤多余字段
transform: true // 自动转换类型
}));
whitelist 的作用:
// 请求体有多余字段
{
"username": "abc",
"age": 18,
"hacker": "trying to break in" // 这个会被过滤掉!
}
8. 完整流程图
Post 请求进来
│
▼
┌──────────────────────────────────────┐
│ 1. class-transformer │
│ 普通对象 → DTO Class 实例 │
└──────────────────────────────────────┘
│
▼
┌──────────────────────────────────────┐
│ 2. class-validator │
│ 基于装饰器验证每一个字段 │
└──────────────────────────────────────┘
│
▼
┌──────────────────────────────────────┐
│ 验证通过 → Controller 继续处理 │
│ 验证失败 → 返回 400 错误 │
└──────────────────────────────────────┘
9. 总结
| 概念 | 前端类比 | 作用 |
|---|---|---|
| DTO | el-form | 接收请求体数据 |
| class-validator | el-form rules | 声明式验证规则 |
| class-transformer | - | 对象类型转换 |
| ValidationPipe | 表单验证组件 | 管道验证 |
一句话总结:
ValidationPipe 就是后端的表单验证器,用声明式(装饰器)来定义验证规则,和前端的 Element Plus el-form 几乎一模一样。