周六日
跟着官方文档跑一遍,把之前忽略的信息整合一遍,
1.路由映射
2.路由通配符
3.状态码
在swagger上可以看见响应的状态码为204,更改成功。
4.依赖注入
constructor(private readonly catsService: CatsService) {}
我们需要在 Nest 中注册该服务,以便它可以执行注入。 为此,我们可以编辑模块文件(app.module.ts),然后将服务添加到@Module()装饰器的 providers 数组中。
5.共享模块
在 Nest 中,默认情况下,模块是单例,因此您可以轻松地在多个模块之间共享同一个提供者实例。
6.全局模块
@Global 装饰器使模块成为全局作用域。 全局模块应该只注册一次,最好由根或核心模块注册。 在上面的例子中,CatsService 组件将无处不在,而想要使用 CatsService 的模块则不需要在 imports 数组中导入 CatsModule。
7.中间件
中间件是在路由处理程序 之前 调用的函数。 中间件函数可以访问请求和响应对象,以及应用程序请求响应周期中的 next() 中间件函数。 next() 中间件函数通常由名为 next 的变量表示。
间件函数可以执行以下任务:
执行任何代码。
对请求和响应对象进行更改。
结束请求-响应周期。
调用堆栈中的下一个中间件函数。
如果当前的中间件函数没有结束请求-响应周期, 它必须调用 next() 将控制传递给下一个中间件函数。否则, 请求将被挂起。
import { Injectable, NestMiddleware } from '@nestjs/common';
import { Request, Response, NextFunction } from 'express';
@Injectable()
export class LoggerMiddleware implements NestMiddleware {
use(req: Request, res: Response, next: NextFunction) {
console.log('Request...');
next();
}
}
Nest中间件完全支持依赖注入。 就像提供者和控制器一样,它们能够注入属于同一模块的依赖项(通过 constructor )。
中间件不能在 @Module() 装饰器中列出。我们必须使用模块类的 configure() 方法来设置它们。包含中间件的模块必须实现 NestModule 接口。我们将 LoggerMiddleware 设置在 ApplicationModule 层上。
我们还可以在配置中间件时将包含路由路径的对象和请求方法传递给forRoutes()方法,如下:
8.路由通配符
路由同样支持模式匹配。例如,星号被用作通配符,将匹配任何字符组合。
forRoutes({ path: 'ab*cd', method: RequestMethod.ALL });
9. 函数式中间件
把类转换成函数
//logger.middleware.ts
export function logger(req, res, next) {
console.log(`Request...`);
next();
};
//多个中间件
consumer.apply(cors(), helmet(), logger).forRoutes(CatsController);
//全局中间件
const app = await NestFactory.create(AppModule);
app.use(logger);
await app.listen(3000);
周一
异常过滤器
内置的异常层负责处理整个应用程序中的所有抛出的异常。当捕获到未处理的异常时,最终用户将收到友好的响应。
客户端调用这个端点时,响应如下所示:
自定义异常
在 findAll()方法中使用它
除此以外,还有许多内置的过滤器:
异常过滤器
基本(内置)异常过滤器可以为您自动处理许多情况,但有时您可能希望对异常层拥有完全控制权,例如,您可能希望基于某些动态因素添加日志记录或使用不同的 JSON 模式。 异常过滤器正是为此目的而设计的。 它们使您可以控制精确的控制流以及将响应的内容发送回客户端。
@Catch() 可以传递多个参数,所以你可以通过逗号分隔来为多个类型的异常设置过滤器。
绑定过滤器
将 HttpExceptionFilter 绑定到 CatsController 的 create() 方法上。
注意:
尽可能使用类而不是实例。由于 Nest 可以轻松地在整个模块中重复使用同一类的实例,因此可以减少内存使用。
将过滤器设置为控制器作用域,执行以下操作:
需要创建一个全局范围的过滤器
注册一个全局范围的过滤器直接为任何模块设置过滤器:
除此以外,为了捕获每一个未处理的异常(不管异常类型如何),将 @Catch() 装饰器的参数列表设为空,例如 @Catch()。
import {
ExceptionFilter,
Catch,
ArgumentsHost,
HttpException,
HttpStatus,
} from '@nestjs/common';
@Catch()
export class AllExceptionsFilter implements ExceptionFilter {
catch(exception: unknown, host: ArgumentsHost) {
const ctx = host.switchToHttp();
const response = ctx.getResponse();
const request = ctx.getRequest();
const status =
exception instanceof HttpException
? exception.getStatus()
: HttpStatus.INTERNAL_SERVER_ERROR;
response.status(status).json({
statusCode: status,
timestamp: new Date().toISOString(),
path: request.url,
});
}
}
在上面的示例中,过滤器将捕获抛出的每个异常,而不管其类型(类)如何。
将异常处理委托给基础过滤器,需要继承 BaseExceptionFilter 并调用继承的 catch() 方法。
继承自基础类的过滤器必须由框架本身实例化(不要使用 new 关键字手动创建实例)
通过注入 HttpServer 来使用继承自基础类的全局过滤器。
async function bootstrap() {
const app = await NestFactory.create(AppModule);
const { httpAdapter } = app.get(HttpAdapterHost);
app.useGlobalFilters(new AllExceptionsFilter(httpAdapter));
await app.listen(3000);
}
bootstrap();
周二
继续学习官方文档,
管道
1.概念
管道和拦截器有点像,都是在数据传输过程中的“关卡”,只不过各司其职,具有 @Injectable() 装饰器的类。管道应实现 PipeTransform 接口。
管道有两个类型:
转换:管道将输入数据转换为所需的数据输出
验证:对输入数据进行验证,如果验证成功继续传递; 验证失败则抛出异常;
2.创建
开始实际开发:先创建pipe文件
nest g pipe validation pipe
接着安装依赖包:
npm i --save class-validator class-transformer
然后在 validation.pipe.ts 中编写验证逻辑
import { PipeTransform, Injectable, ArgumentMetadata, BadRequestException } from '@nestjs/common';
import { validate } from 'class-validator';
import { plainToClass } from 'class-transformer';
@Injectable()
export class ValidationPipe implements PipeTransform<any> {
async transform(value: any, { metatype }: ArgumentMetadata) {
if (!metatype || !this.toValidate(metatype)) {
return value;
// 如果没有传入验证规则,则不验证,直接返回数据
}
// 将对象转换为 Class 来验证
const object = plainToClass(metatype, value);
const errors = await validate(object);
if (errors.length > 0) {
throw new BadRequestException('Validation failed');
}
return value;
}
private toValidate(metatype: Function): boolean {
const types: Function[] = [String, Boolean, Number, Array, Object];
return !types.includes(metatype);
}
}
3.绑定
直接传入类(而不是实例),让框架承担实例化责任,启用依赖注入。
ValidationPipe 被创建为尽可能通用,所以我们将把它设置为一个全局作用域的管道,用于整个应用程序中的每个路由处理器。
从任何模块外部注册的全局管道(如上例所示)无法注入依赖,因为它们不属于任何模块。为了解决这个问题,可以使用以下构造直接为任何模块设置管道:
4.转换管道
有用的例子是按 ID 从数据库中选择一个现有的用户实体。
@Get(':id')
findOne(@Param('id', UserByIdPipe) userEntity: UserEntity) {
return userEntity;
}
周三
守卫
守卫是一个使用 @Injectable() 装饰器的类。 守卫应该实现 CanActivate 接口。设计与异常过滤器、管道和拦截器非常相似,目的是让您在请求/响应周期的正确位置插入处理逻辑,并以声明的方式进行插入。这有助于保持代码的简洁和声明性。
授权守卫
当调用者(通常是经过身份验证的特定用户)具有足够的权限时,特定的路由才可用。我们现在要构建的 AuthGuard 假设用户是经过身份验证的(因此,请求头附加了一个token)。它将提取和验证token,并使用提取的信息来确定请求是否可以继续。
基于角色认证
这个守卫只允许具有特定角色的用户访问。下面的例子是一个基本模板
目前,它允许所有请求继续:
import { Injectable, CanActivate, ExecutionContext } from '@nestjs/common';
import { Observable } from 'rxjs';
@Injectable()
export class RolesGuard implements CanActivate {
canActivate(
context: ExecutionContext,
): boolean | Promise<boolean> | Observable<boolean> {
return true;
}
}
绑定守卫
让框架进行实例化,并启用了依赖项注入。与管道和异常过滤器一样,我们也可以传递一个实例:
设置一个全局守卫,使用Nest应用程序实例的useGlobalGuards() 方法:
const app = await NestFactory.create(AppModule);
app.useGlobalGuards(new RolesGuard());
周四
拦截器
每个拦截器都有 intercept() 方法,它接收2个参数。
第一个是 ExecutionContext 实例(与守卫完全相同的对象):提供有关当前执行过程的更多详细信息。这些详细信息有助于构建可以在广泛的控制器,方法和执行上下文中使用的更通用的拦截器。
第二个参数是 CallHandler。如果不手动调用 handle() 方法,则主处理程序根本不会进行求值。这是什么意思?基本上,CallHandler是一个包装执行流的对象,因此推迟了最终的处理程序执行。
1.截取切面
使用拦截器在函数执行之前或之后添加额外的逻辑。当我们要记录与应用程序的交互时,它很有用,例如 存储用户调用,异步调度事件或计算时间戳
import { Injectable, NestInterceptor, ExecutionContext, CallHandler } from '@nestjs/common';
import { Observable } from 'rxjs';
import { tap } from 'rxjs/operators';
@Injectable()
export class LoggingInterceptor implements NestInterceptor {
intercept(context: ExecutionContext, next: CallHandler): Observable<any> {
console.log('Before...');
const now = Date.now();
return next
.handle()
.pipe(
tap(() => console.log(`After... ${Date.now() - now}ms`)),
);
}
}
2.绑定拦截器
3.绑定全局拦截器
useGlobalInterceptors() 方法:
const app = await NestFactory.create(ApplicationModule);
app.useGlobalInterceptors(new LoggingInterceptor());
响应映射
创建一个 TransformInterceptor, 它将打包响应并将其分配给 data 属性。
import { Injectable, NestInterceptor, ExecutionContext, CallHandler } from '@nestjs/common';
import { Observable } from 'rxjs';
import { map } from 'rxjs/operators';
export interface Response<T> {
data: T;
}
@Injectable()
export class TransformInterceptor<T> implements NestInterceptor<T, Response<T>> {
intercept(context: ExecutionContext, next: CallHandler): Observable<Response<T>> {
return next.handle().pipe(map(data => ({ data })));
}
}
之后,当有人调用GET /cats端点时,请求将如下所示(我们假设路由处理程序返回一个空 arry []):
{
“data”: []
}
本周总结与下周计划:
主要学习Nestjs官方文档,巩固了之前学的知识。
遇到的问题:
因为是cli导入模块的,所以哪怕不同文件下的文件如果名字一致的话,也会报错。
解决方法:重命名
下周计划:
和前端同学一起讨论数据格式。