来自小满zs的教程 附链接
在底层,Nest使用强大的 HTTP Server 框架,如 Express和 Fastify。Nest 在这些框架之上提供了一定程度的抽象,同时也将其 API 直接暴露给开发人员。这样可以轻松使用每个平台的无数第三方模块。
依赖注入
TypeScript装饰器
装饰器是一种特殊类型的声明,它能够被附加到类声明,属性, 访问符,方法或方法参数上。 装饰器使用 @expression这种形式,expression求值后必须为一个函数,它会在运行时被调用,被装饰的声明信息做为参数传入
类装饰器
自动把class的构造函数传入到装饰器的第一个参数target,然后通过prototype可以自定义添加属性和方法
属性装饰器(PropertyDecorator)
同样使用@符号给属性添加装饰器,他会返回两个参数
- 原型对象
- 属性的名称
参数装饰器(ParameterDecorator)
同样使用@符号给属性添加装饰器,他会返回三个参数
- 原型对象
- 方法的名称
- 参数的位置(从0开始)
方法装饰器(MethodDecorator)
同样使用@符号给属性添加装饰器,他会返回三个参数
- 原形对象
- 方法的名称
- 属性描述符 可写对应writable,可枚举对应enumerable,可配置对应configurable
装饰器工厂 (nestjs Get装饰器的实现)
Nest.js 学习
安装
$ npm i -g @nestjs/cli
$ nest new project-name
基本文件介绍
app.module.ts: 根模块用于处理其他类的引用与共享app.controller.ts: 常用功能是用来处理http请求以及调用service层的处理方法app.service.ts: 封装通用的业务逻辑、与数据层的交互、其他额外的一些第三方请求
nest 命令
nest --help: 获取nest命令的详细介绍nest g mo user: 在user文件夹下生成 user.module.ts 文件nest g co user: 在user文件夹下生成 user.controller.ts 文件nest g s user: 在user文件夹下生成 user.service.ts 文件nest g resource user: 生成如下目录结构nest g mi logger: 生成中间件 logger.middleware.ts 文件
RESTful版本控制
- 第一步,在main.ts中打开配置
app.enableVersioning(VersioningType.URI), 控制版本的类型可以选择,包括- URI
- 请求参数
- 自定义Header
- 媒体类型
- 第二步,在
user.controller.ts中配置
使用以下设置,http://localhost:3000/v1/user 可以匹配,但 http://localhost:3000/user 不能 配@Controller({ path:"user", version:'1' } - 单个请求配置
@Get() @Version('1')
控制器
注意:@Post请求的请求体内容默认为json,如果需要其他格式请设置请求头
路由参数
使用:id占位,使用进行读取@Params 读取
- 方法一:
@Get(':id')
findOne(@Param() params): string {
console.log(params.id);
return `This action returns a #${params.id} cat`;
}
- 方法二:
@Get(':id')
findOne(@Param('id') id): string {
return `This action returns a #${id} cat`;
}
状态码装饰器
状态码默认返回200
通过@HttpCode可以修改返回的状态码 (status)
@Post()
@HttpCode(204)
create() {
return 'This action adds a new cat';
}
Session 插件
- 安装
npm i express-session --save
npm i @types/express-session -D
- 引入
import * as session from 'express-session'
app.use(session())
- 参数
注意
- 如果后端返回的Cookie前端要接收到,那么请求头需要携带Cookie
- axios携带Cookie的方式
axios.defaults.withCredentials=true - 直接访问地址默认携带Cookie
提供者
基本使用,注册位置标注
import { Module } from '@nestjs/common';
import { UserService } from './user.service';
import { UserService2 } from './user.service2';
import { UserService3 } from './user.service3';
import { UserController } from './user.controller';
@Module({
controllers: [UserController],
// 提供者注册
providers: [
// 语法糖
UserService2,
]
})
export class UserModule { }
类提供者
useClass
{
provide: "Xiaoman",
useClass: UserService
},
值提供者
useValue
{
provide: "JD",
useValue: ['TB', 'PDD', 'JD']
}
工厂提供者
useFactory
{
provide: "Test",
// inject属性接受一个提供者数组,在实例化过程中,Nest 将解析该数组并将其作为参数传递给工厂函数
inject: [UserService2],
useFactory(UserService2: UserService2) {
return new UserService3(UserService2)
}
}
支持异步
{
provide: "sync",
async useFactory() {
return await new Promise((r) => {
setTimeout(() => {
r('sync')
}, 3000)
})
}
}
别名提供者
useExisting 语法允许您为现有的提供程序创建别名。这将创建两种访问同一提供者的方法
{
provide: 'AliasedLoggerService',
useExisting: LoggerService,
},
导出自定义提供者
与任何提供程序一样,自定义提供程序的作用域仅限于其声明模块。要使它对其他模块可见,必须导出它。要导出自定义提供程序,我们可以使用其令牌或完整的提供程序对象
const connectionFactory = {
// 令牌
provide: 'CONNECTION',
useFactory: (optionsProvider: OptionsProvider) => {
const options = optionsProvider.get();
return new DatabaseConnection(options);
},
inject: [OptionsProvider],
};
@Module({
providers: [connectionFactory],
// 从这里导出
exports: ['CONNECTION'],
})
export class AppModule {}
模块
共享模块
- 第一步,编写共享组件,使用
exports
@Module({
controllers: [ListController],
providers: [ListService],
exports: [ListService]
})
export class ListModule {}
- 第二步,注册共享组件,在需要共享的组件中使用
imports
@Module({
imports: [ListModule],
controllers: [UserController],
providers: [UserService],
})
export class UserModule { }
注意
在UserModule中引入了ListModule后,不能再用同样的方式在ListModule中引入UserModule
全局模块
使用@Global() 修饰,exports导出,可以不用局部注册
- 第一步,使用
@Global声明为全局模块 - 第二步,使用
exports导出模块 - 第三步,在根模块
AppModule中声明
import { Global, Module } from '@nestjs/common';
import { UserService } from './user.service';
import { UserController } from './user.controller';
@Global()
@Module({
controllers: [UserController],
providers: [UserService],
exports: [UserService]
})
export class UserModule { }
动态模块
动态模块主要就是为了给模块传递参数 可以给该模块添加一个静态方法 用来接受参数
- 第一步,定义动态模块
interface Options {
path: string
}
// 动态模块
@Module({
controllers: [ConfigController],
providers: [ConfigService],
})
export class ConfigModule {
static forRoot(option: Options): DynamicModule {
return {
// 如果需要注册全局,设置global
global: true,
module: ConfigModule,
providers: [{
provide: 'config',
useValue: option?.path
}],
exports: [{
provide: 'config',
useValue: option?.path
}]
}
}
}
- 第二步,注册使用动态模块
imports: [UserModule, ConfigModule.forRoot({path: '/dt'}), ListModule]
- 第三步,使用动态模块的
// 使用值提供器,声明方式
@Inject("config") private readonly config: any
中间件
Nest中间件实际上等价于express中间件
nest g mi logger生成中间件文件
- 第一步定义中间件,使用
@Injectable()装饰器
logger.middleware.ts
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();
}
}
- 第二步应用中间件,在module中 实现
NestModule,再在configure(consumer: MiddlewareConsumer)方法中注册中间件
import { Module, NestModule, RequestMethod, MiddlewareConsumer } from '@nestjs/common';
import { LoggerMiddleware } from './common/middleware/logger.middleware';
import { CatsModule } from './cats/cats.module';
@Module({
imports: [CatsModule],
})
export class AppModule implements NestModule {
configure(consumer: MiddlewareConsumer) {
consumer
.apply(LoggerMiddleware)
.exclude(
{ path: 'cats', method: RequestMethod.GET },
{ path: 'cats', method: RequestMethod.POST },
'cats/(.*)',
)
.forRoutes({ path: 'cats', method: RequestMethod.GET });
}
}
forRoutes({path: 'ab*cd',method:RequestMethod.All}),可以使用路由通配符,*被称为通配符forRoutes()可接受一个字符串、多个字符串、对象、一个控制器类甚至多个控制器类- 可以使用
exclude()排除某些路由。此方法可以采用一个字符串,多个字符串或一个 RouteInfo 对象来标识要排除的路由 - 我们可以把中间件定义成一个简单的函数,叫做函数式中间件
多个中间件
为了绑定顺序执行的多个中间件 使用
apply()方法进行绑定
consumer.apply(cors(), helmet(), logger).forRoutes(CatsController);
全局中间件
一次性将中间件绑定到每个注册路由 使用
use()方法绑定全局
const app = await NestFactory.create(AppModule);
app.use(logger);
await app.listen(3000);
响应拦截器
拦截器是使用 @Injectable() 装饰器注解的类。拦截器应该实现 NestInterceptor 接口
- 第一步,定义一个拦截器,用来格式化返回的数据
import { Injectable, NestInterceptor, CallHandler } from '@nestjs/common'
import { map } from 'rxjs/operators'
import { Observable } from 'rxjs'
interface data<T> {
data: T
}
@Injectable()
export class Response<T = any> implements NestInterceptor {
intercept(context, next: CallHandler): Observable<data<T>> {
// 由于 handle() 返回一个RxJS Observable,我们有很多种操作符可以用来操作流
return next
.handle()
.pipe(map(data => {
return {
data,
code: 200,
message: "牛逼"
}
}))
}
}
- 第二步,注册,可以局部注册也可以全局注册
局部注册 请注意,我们传递的是 LoggingInterceptor 类型而不是实例,让框架承担实例化责任并启用依赖注入。另一种可用的方法是传递立即创建的实例:
@UseInterceptors(LoggingInterceptor)
export class CatsController {}
立即创建实例
@UseInterceptors(new LoggingInterceptor())
export class CatsController {}
全局注册
app.useGlobalInterceptors(new Response())
异常过滤器
内置的异常层负责处理整个应用程序中的所有抛出的异常。当捕获到未处理的异常时,最终用户将收到友好的响应。
- 第一步,定义一个过滤器
import { ExceptionFilter, Catch, ArgumentsHost, HttpException } from '@nestjs/common'
import { Request, Response } from 'express'
// @Catch() 可以传递多个参数,所以你可以通过逗号分隔来为多个类型的异常设置过滤器。
@Catch(HttpException)
export class HttpFilter implements ExceptionFilter {
catch(exception: HttpException, host: ArgumentsHost) {
const ctx = host.switchToHttp()
const request = ctx.getRequest<Request>()
const response = ctx.getResponse<Response>()
const status = exception.getStatus()
console.log(exception);
response
.status(status)
.json({
data: exception,
time: new Date().getTime(),
success: false,
path: request.url,
code: status
})
}
}
- 第二步,注册,可以局部绑定也可以全局绑定
局部绑定
@Post()
@UseFilters(new HttpExceptionFilter())
async create(@Body() createCatDto: CreateCatDto) {
throw new ForbiddenException();
}
全局绑定
app.useGlobalFilters(new HttpFilter())
管道 | 管道验证
内置管道
Nest 自带九个开箱即用的管道,即
- ValidationPipe
- ParseIntPipe
- ParseFloatPipe
- ParseBoolPipe
- ParseArrayPipe
- ParseUUIDPipe
- ParseEnumPipe
- DefaultValuePipe
- ParseFilePipe
他们从 @nestjs/common 包中导出,使用举例
@Get(':id')
async findOne(@Param('id', ParseIntPipe) id: number) {
return this.catsService.findOne(id);
}
自定义管道
对象结构验证
依赖包
npm install --save joi
npm install --save-dev @types/joi
类结构验证
依赖包
$ npm i --save class-validator class-transformer
- 第一步,定义
import { ArgumentMetadata, HttpException, HttpStatus, Injectable, PipeTransform } from '@nestjs/common';
import { plainToInstance } from 'class-transformer';
import { validate } from 'class-validator';
@Injectable()
export class LoginPipe implements PipeTransform {
async transform(value: any, metadata: ArgumentMetadata) {
const dto = plainToInstance(metadata.metatype, value)
// 验证value是否满足验证条件,满足返回空数组
const error = await validate(dto)
if (error.length) {
throw new HttpException(error, HttpStatus.BAD_REQUEST)
}
return value;
}
}
- 第二步,局部绑定
@Post()
create(@Body(LoginPipe) createLoginDto: CreateLoginDto) {
return this.loginService.create(createLoginDto);
}
全局管道
全局绑定
app.useGlobalPipes(new ValidationPipe());
默认值设置
DefaultValuePipe
@Get()
async findAll(
@Query('activeOnly', new DefaultValuePipe(false), ParseBoolPipe) activeOnly: boolean,
@Query('page', new DefaultValuePipe(0), ParseIntPipe) page: number,
) {
return this.catsService.findAll({ activeOnly, page });
}
守卫
守卫是一个使用 @Injectable() 装饰器的类。 守卫应该实现 CanActivate 接口。
权限验证
- 第一步,编写角色装饰器
nest g d roles
import { SetMetadata } from '@nestjs/common';
export const Roles = (...args: string[]) => SetMetadata('roles', args);
- 第二步,编写守卫
nest g gu roles
import { CanActivate, ExecutionContext, Injectable } from '@nestjs/common';
import { Observable } from 'rxjs';
import { Reflector } from '@nestjs/core';
import type { Request } from 'express';
@Injectable()
export class RolesGuard implements CanActivate {
constructor(private Reflector: Reflector) { }
canActivate(
context: ExecutionContext,
): boolean | Promise<boolean> | Observable<boolean> {
// 获取设置的属性
const admin = this.Reflector.get<string[]>('roles', context.getHandler())
// 获取query数据
const req = context.switchToHttp().getRequest<Request>()
if (admin.includes(req.query.roles as string)){
return true
}else{
return false
}
}
}
- 第三步,绑定角色,绑定守卫
@Post()
@Roles("admin")
@UseGuards(RolesGuard)
create(@Body(LoginPipe) createLoginDto: CreateLoginDto) {
return this.loginService.create(createLoginDto);
}
自定义装饰器
参数装饰器
createParamDecorator
import { createParamDecorator, ExecutionContext } from '@nestjs/common';
export const User = createParamDecorator((data: unknown, ctx: ExecutionContext) => {
const request = ctx.switchToHttp().getRequest();
return request.user;
});
- data:可以接收传递的参数
- ctx:上下文
聚合装饰器
applyDecorators
export function Auth(...roles: Role[]) {
return applyDecorators(
SetMetadata('roles', roles),
UseGuards(AuthGuard, RolesGuard),
ApiBearerAuth(),
ApiUnauthorizedResponse({ description: 'Unauthorized"' })
);
}