对输入做验证是一个 web 应用的基本功能,不止前端要做、后端也要做:
前端做表单的验证基本不用自己写,有很多 validation 的库,大家写的也比较多了。后端的验证大家可能写的相对较少,今天我们就来学下后端框架 Nest.js 如何做参数的验证吧。
本文会学到这些内容:
Nest.js 是基于 IOC 和 MVC 的思想的后端框架:
此外,Nest.js 还支持 Module,可以把 Controller、Service、Repository 封装成一个 Module,易于代码的组织。
整体架构如图:
整个 IOC 容器内有多个 Controller、Service、Respository 等实例,分散在不同的 Module 中。有一个 AppModule 作为根来引入其他 Module。
请求是在 Controller 里处理的,调用 Service 来完成业务逻辑,其中对数据库的 CRUD 由 Repository 完成。
那么对参数的 validate 应该放在哪呢?
对参数做验证,在 Controller 里就可以,但是这种验证逻辑是通用的,每个 Controller 里都做一遍也太麻烦了,能不能在 Controller 之前就做好了呢?
可能大家没什么思路,那我们再了解一个 Nest.js 的功能:管道(Pipe)。
Nest.js 支持管道(Pipe),它会在请求到达 Controller 之前被调用,可以对参数做验证和转换,如果抛出了异常,则不会再传递给 Controller。
这种管道的特性适合用来做一些跨 Controller 的通用逻辑,比如 string 到 int 的转换,参数验证等等。
Nest.js 内置了 8 个管道:
可以分为 3 类:
parseXxx,把参数转为某种类型;defaultValue,设置参数默认值;validation,做参数的验证。
这些都是很通用的功能。
很明显,validation 就可以用那个 ValidationPipe 来做。
但是我们先不着急用 Nest.js 提供的 Pipe,先自己实现下试试。
Pipe 的形式是实现 PipeTransform 接口的类,实现它的 transform 方法,在里面对 value 做各种转换或者验证,如果验证失败就抛一个异常。
import { PipeTransform, Injectable, ArgumentMetadata, BadRequestException } from '@nestjs/common';
@Injectable()
export class MyValidationPipe implements PipeTransform<any> {
async transform(value: any, metadata: ArgumentMetadata) {
if (value.age > 20) {
throw new BadRequestException('年龄超过限制');
} else {
value.age += 10;
}
return value;
}
}
之后我们在 IOC 容器启动的时候调用 useGlobalPipes 方法注册一下这个 Pipe:
import { NestFactory } from '@nestjs/core';
import { AppModule } from './app.module';
import { MyValidationPipe } from './pipes/MyValidationPipe';
async function bootstrap() {
const app = await NestFactory.create(AppModule);
app.useGlobalPipes(new MyValidationPipe());
await app.listen(3000);
}
bootstrap();
我们来测试下:
当参数的 age 大于 20,就会抛异常返回对应的 response。
当参数小于 20,参数会被修改之后传递到 Controller:
image.png
可以看到,参数被传递到了 Controller 并且做了修改。
这就是 Pipe 的作用。
所以,我们在 pipe 中对参数做 validate 就行了。可以用 class-validation 这个包,它支持装饰器的方式来配置验证规则:
类似这样:
import { IsEmail, IsNotEmpty, IsPhoneNumber, IsString } from "class-validator";
export class CreatePersonDto {
@IsNotEmpty({
message: 'name 不能为空'
})
@IsString()
name: string;
@IsPhoneNumber("CN", {
message: 'phone 不是一个电话号码'
})
phone: string;
@IsEmail({}, {
message: 'email 不是一个合法邮箱'
})
email: string;
}
然后在 pipe 中调用 validate 的方法,如果有错误就抛异常:
import { PipeTransform, Injectable, ArgumentMetadata, BadRequestException } from '@nestjs/common';
import { validate } from 'class-validator';
import { plainToClass } from 'class-transformer';
@Injectable()
export class MyValidationPipe implements PipeTransform<any> {
async transform(value: any, { metatype }: ArgumentMetadata) {
if (!metatype) {
return value;
}
const object = plainToClass(metatype, value);
const errors = await validate(object);
if (errors.length > 0) {
throw new BadRequestException('Validation failed');
}
return value;
}
}
因为我们是用装饰器做的配置,那就要通过对象拿到它对应的类的装饰器,所以在 validate 之前要调用 class-transformer 包的 plainToClass 方法来把普通的参数对象转换为该类的实例。
这样就实现了参数校验的功能:
这就是 Nest.js 的 ValidationPipe 的实现原理。
当然,我们没有做错误的格式化,不如内置 Pipe 做的漂亮,我们来看下内置 Pipe 的效果:
启用内置的 ValidationPipe:
import { ValidationPipe } from '@nestjs/common';
import { NestFactory } from '@nestjs/core';
import { AppModule } from './app.module';
async function bootstrap() {
const app = await NestFactory.create(AppModule);
app.useGlobalPipes(new ValidationPipe());
await app.listen(3000);
}
bootstrap();
然后测试下:
人家这个返回的格式好多了。
还有,大家有没有注意到,我们只是返回了一个 BadRequestException 的 error,但是服务器就返回了 400 的相应,这个是什么原因呢?
这就涉及到了 Nest.js 的另一个机制:异常过滤器(Exception Filter)
。
Nest.js 支持异常过滤器(ExceptionFilter),可以声明对什么错误做什么响应,这样应用想返回什么响应只需要抛相应的异常。
异常过滤器的形式是一个实现 ExceptionFilter 接口的类,通过 Catch 装饰器声明对什么异常做处理。实现它的 catch 方法,在方法内拿到 response 对象返回相应的响应。
定义异常:
export class ForbiddenException extends HttpException {
constructor() {
super('Forbidden', HttpStatus.FORBIDDEN);
}
}
定义异常过滤器:
import { ExceptionFilter, Catch, ArgumentsHost, HttpException } from '@nestjs/common';
import { Request, Response } from 'express';
@Catch(HttpException)
export class HttpExceptionFilter implements ExceptionFilter {
catch(exception: HttpException, host: ArgumentsHost) {
const ctx = host.switchToHttp();
const response = ctx.getResponse<Response>();
const request = ctx.getRequest<Request>();
const status = exception.getStatus();
response
.status(status)
.json({
statusCode: status,
timestamp: new Date().toISOString(),
path: request.url,
});
}
}
很明显,之所以我们在 ValidationPipe 里只是抛了一个 BadRequestException 的错误,就返回了 400 的响应就是因为有内置的 ExceptionFilter。
Nest.js 内置了很多 ExceptionFilter,比如:
想返回什么响应就抛什么 exception 就行,不够的话还可以自定义 ExceptionFilter。
至此,我们实现了参数的 validate,通过 Pipe + ExceptionFilter。
对输入的验证是一个基本功能,前后端都要做。
我们先过了一下 Nest.js 的基础:Nest.js 是 MVC + IOC 的架构,并且支持 Module 来组织代码。
然后探究了 Nest.js 的 validate 的实现思路:验证可以放在 Controller 之前,通过 Pipe 对参数做验证和转换,如果有错误就抛异常,异常会触发 ExceptionFilter,从而返回不同的错误响应。
Pipe 在 Controller 之前被调用,如果抛出异常,请求就不会继续传递到 Controller。
ExceptionFilter 可以监听不同类型的 exception,做不同的响应。
内置有很多 Pipe 和 ExceptionFilter 可以直接用,不够的时候还可以自己定义。
当然,如果只是实现验证,不用这么麻烦,直接用 ValidationPipe 就行。
Validation 是一个基础功能,但我们通过它学会了 Pipe 和 ExceptionFilter,还是很有意义的。
本文由哈喽比特于3年以前收录,如有侵权请联系我们。
文章来源:https://mp.weixin.qq.com/s/MvYESCdTGnfegUn9WuSowA
京东创始人刘强东和其妻子章泽天最近成为了互联网舆论关注的焦点。有关他们“移民美国”和在美国购买豪宅的传言在互联网上广泛传播。然而,京东官方通过微博发言人发布的消息澄清了这些传言,称这些言论纯属虚假信息和蓄意捏造。
日前,据博主“@超能数码君老周”爆料,国内三大运营商中国移动、中国电信和中国联通预计将集体采购百万台规模的华为Mate60系列手机。
据报道,荷兰半导体设备公司ASML正看到美国对华遏制政策的负面影响。阿斯麦(ASML)CEO彼得·温宁克在一档电视节目中分享了他对中国大陆问题以及该公司面临的出口管制和保护主义的看法。彼得曾在多个场合表达了他对出口管制以及中荷经济关系的担忧。
今年早些时候,抖音悄然上线了一款名为“青桃”的 App,Slogan 为“看见你的热爱”,根据应用介绍可知,“青桃”是一个属于年轻人的兴趣知识视频平台,由抖音官方出品的中长视频关联版本,整体风格有些类似B站。
日前,威马汽车首席数据官梅松林转发了一份“世界各国地区拥车率排行榜”,同时,他发文表示:中国汽车普及率低于非洲国家尼日利亚,每百户家庭仅17户有车。意大利世界排名第一,每十户中九户有车。
近日,一项新的研究发现,维生素 C 和 E 等抗氧化剂会激活一种机制,刺激癌症肿瘤中新血管的生长,帮助它们生长和扩散。
据媒体援引消息人士报道,苹果公司正在测试使用3D打印技术来生产其智能手表的钢质底盘。消息传出后,3D系统一度大涨超10%,不过截至周三收盘,该股涨幅回落至2%以内。
9月2日,坐拥千万粉丝的网红主播“秀才”账号被封禁,在社交媒体平台上引发热议。平台相关负责人表示,“秀才”账号违反平台相关规定,已封禁。据知情人士透露,秀才近期被举报存在违法行为,这可能是他被封禁的部分原因。据悉,“秀才”年龄39岁,是安徽省亳州市蒙城县人,抖音网红,粉丝数量超1200万。他曾被称为“中老年...
9月3日消息,亚马逊的一些股东,包括持有该公司股票的一家养老基金,日前对亚马逊、其创始人贝索斯和其董事会提起诉讼,指控他们在为 Project Kuiper 卫星星座项目购买发射服务时“违反了信义义务”。
据消息,为推广自家应用,苹果现推出了一个名为“Apps by Apple”的网站,展示了苹果为旗下产品(如 iPhone、iPad、Apple Watch、Mac 和 Apple TV)开发的各种应用程序。
特斯拉本周在美国大幅下调Model S和X售价,引发了该公司一些最坚定支持者的不满。知名特斯拉多头、未来基金(Future Fund)管理合伙人加里·布莱克发帖称,降价是一种“短期麻醉剂”,会让潜在客户等待进一步降价。
据外媒9月2日报道,荷兰半导体设备制造商阿斯麦称,尽管荷兰政府颁布的半导体设备出口管制新规9月正式生效,但该公司已获得在2023年底以前向中国运送受限制芯片制造机器的许可。
近日,根据美国证券交易委员会的文件显示,苹果卫星服务提供商 Globalstar 近期向马斯克旗下的 SpaceX 支付 6400 万美元(约 4.65 亿元人民币)。用于在 2023-2025 年期间,发射卫星,进一步扩展苹果 iPhone 系列的 SOS 卫星服务。
据报道,马斯克旗下社交平台𝕏(推特)日前调整了隐私政策,允许 𝕏 使用用户发布的信息来训练其人工智能(AI)模型。新的隐私政策将于 9 月 29 日生效。新政策规定,𝕏可能会使用所收集到的平台信息和公开可用的信息,来帮助训练 𝕏 的机器学习或人工智能模型。
9月2日,荣耀CEO赵明在采访中谈及华为手机回归时表示,替老同事们高兴,觉得手机行业,由于华为的回归,让竞争充满了更多的可能性和更多的魅力,对行业来说也是件好事。
《自然》30日发表的一篇论文报道了一个名为Swift的人工智能(AI)系统,该系统驾驶无人机的能力可在真实世界中一对一冠军赛里战胜人类对手。
近日,非营利组织纽约真菌学会(NYMS)发出警告,表示亚马逊为代表的电商平台上,充斥着各种AI生成的蘑菇觅食科普书籍,其中存在诸多错误。
社交媒体平台𝕏(原推特)新隐私政策提到:“在您同意的情况下,我们可能出于安全、安保和身份识别目的收集和使用您的生物识别信息。”
2023年德国柏林消费电子展上,各大企业都带来了最新的理念和产品,而高端化、本土化的中国产品正在不断吸引欧洲等国际市场的目光。
罗永浩日前在直播中吐槽苹果即将推出的 iPhone 新品,具体内容为:“以我对我‘子公司’的了解,我认为 iPhone 15 跟 iPhone 14 不会有什么区别的,除了序(列)号变了,这个‘不要脸’的东西,这个‘臭厨子’。