从0到1深入浅出构建Nest.Js项目

Nest (NestJS) 是一个用于构建高效、可扩展的 Node.js 服务器端应用程序的开发框架。它利用JavaScript 的渐进增强的能力,使用并完全支持 TypeScript (仍然允许开发者使用纯 JavaScript 进行开发),并结合了 OOP (面向对象编程)、FP (函数式编程)和 FRP (函数响应式编程)。

在底层,Nest 构建在强大的 HTTP 服务器框架上,例如 Express (默认),并且还可以通过配置从而使用 Fastify !

Nest 在这些常见的 Node.js 框架 (Express/Fastify) 之上提高了一个抽象级别,但仍然向开发者直接暴露了底层框架的 API。这使得开发者可以自由地使用适用于底层平台的无数的第三方模块。

NestJS 是一个基于 TypeScript 的服务器端应用程序框架。 它提供了一组丰富的工具和模块来帮助开发人员构建高效、可扩展的服务器端应用程序。这些元素可以通过依赖注入系统自动注册,并可以在整个应用程序中共享

总的来说,NestJS 是一个功能丰富、易于使用的服务器端应用程序框架,可帮助开发人员快速构建高效、可扩展的服务器端应用程序。

一、NestJS优势

NestJS 的一些优势包括:

  • 构建在现代 JavaScript 栈之上,因此使用了最新的 JavaScript 技术。
  • 基于 Angular 的架构和语法,提供了强大的模块化系统和依赖注入功能。
  • 基于 TypeScript,提供了强类型和静态类型检查。
  • 提供了丰富的工具和模块,可用于构建各种类型的服务器端应用程序,包括 RESTful API、GraphQL API、WebSocket 服务器等。
  • 提供了一组可扩展的构建块,可用于快速构建应用程序。
  • 提供了与主流数据库和身份验证系统的集成。

二、IOC(控制反转 )和依赖注入DI概念

这两个概念不要搞混了,IOC其实是面向对象编程中的一种设计模式,而DI则是为了实现IOC的一种技术。

下面简单认识一下为什么需要IOC,IOC有什么好处,简单来说就是减少了固定性,通过外部传参进行控制内部本身固定的一些变量,如下例子:

在我们的代码中,经常会出现一个类依赖于另外一个类的情况,比如这样:

class Dog {}

class Person {
  private _pet

  constructor () {
    this._pet = new Dog()
  }
}

const xiaoming = new Person()

当我们遇到类与类之间存在依赖关系时,一般会直接在类的内部创建依赖对象,这样就会导致各个类之间形成耦合,并且这种关系会随着依赖关系越来越复杂从而耦合度也会越来越高,最终造成代码的难以维护。

在上述例子中:

  • Person类固定依赖于Dog类,如果后续Person想要依赖于其他宠物类,是无法轻易修改的。
  • 并且如果Dog类有所变化,比如其属性颜色染成了黑色,Person类也会直接受到影响。

IOC的思想就是将类的依赖动态注入,以解决上述两个问题:

class Dog {}

class Person {
  private _pet

  constructor (pet) {
    this._pet = pet
  }
}

const xiaohei = new Dog()
const xiaoming = new Person(xiaohei) // 将实例化的 dog 传入 person 类

这样,我们就实现了类的控制反转,同时,我们需要有一个容器来维护各个对象实例,当用户需要使用实例时,容器会自动将对象实例化给用户,这部分通常由框架处理。

这种动态注入的思想叫做依赖注入(DI, Dependency Injection),它是 IoC 的一种应用形式,把对象或依赖的实例化交给IOC容器去处理,在NestJS中这个容器就是NestJS的运行时系统。当需要一个对象实例的时候,我们不需要自己手动new xxxClass(),只需要在合适的地方对类进行注册,在需要用到的地方直接注入,容器将为我们完成new的动作。

三、NestJs中使用方式

Nest中使用依赖注入一般有以下三步:

1、声明定义

使用@Injectable装饰器来声明一个类,它表示该类可以由NestIOC容器管理

通常命名方式为XXX.service.ts

// app.service.ts
import { Injectable } from '@nestjs/common';

@Injectable()
export class AppService {
  getHello(): string {
    return 'Hello jiusi';
  }
}

2、声明在什么地方使用

这是依赖注入的地方,一般是在类的构造函数constructor中注入,只有完成注入后才可以使用

// app.controller.ts
import { Controller, Get } from '@nestjs/common';
import { AppService } from './app.service';

@Controller()
export class AppController {
  constructor(private readonly appService: AppService) {}

  @Get('/hello')
  get(): string {
    return this.appService.getHello();
  }
}

官方把appService称为tokenNestJS会根据这个token在容器中找到第1步中声明的类(这个对应关系将在第三步中进行关联注册),从而提供对应的实例,这里的实例全局唯一,只有1个!在第一次需要该实例的时候,Nestnew一个出来,而后会缓存起来,后序如果其它地方也注入了这个依赖,那Nest会从缓存中拿到之前new出来的实例供大家使用。

3、建立注入依赖与容器中类的联系

依赖注入后还需要在Module中进行关联

import { Module } from '@nestjs/common';
import { AppController } from './app.controller';
import { AppService } from './app.service';

@Module({
  controllers: [AppController],
  providers: [AppService],
})
export class AppModule {}

Nest会根据所有注入的依赖关系生成一个依赖关系图,就有点类似我们使用import引入各个模块时也会生成一个复杂的依赖关系图。这里AppController中依赖了AppService,如果AppService中还依赖其它东西也会一并放到Nest构建的依赖关系图中,Nest会从下到上按照依赖顺序构建出一整张依赖关系图保证所有的依赖关系正常运作。

四、AOP(Aspect Oriented Programming)

中文为面向切面编程。当一个请求打过来时,一般会经过 Controller(控制器)、Service(服务)、Repository(数据库访问) 的链路。当我们不使用AOP时,需要添加一些通用逻辑时(如日志记录、权限守卫、异常处理等等),就需要在每段请求逻辑中编写相关代码。

AOP就是在所有请求外面包裹一层切面,所有请求都会经过这个切面,然后我们就可以把上述的通用逻辑放在这个结构里,如下图:

在这里插入图片描述

在nestJS中实现AOP的方式有很多,比如(excepion filter过滤器、pipes管道、guards守卫、interceptors拦截器)。

五、NestJS请求流程图

  • Controllers -> 处理请求
  • Service -> 数据访问与核心逻辑
  • Modules -> 组合所有的逻辑代码
  • Pipes -> 管道–核验请求的数据
  • Filters -> 过滤器–处理请求时的错误
  • Guards -> 守卫–鉴权与认证
  • Interceptors -> 拦截器-给请求与响应加入额外的逻辑
  • Repositories -> 处理在数据库中数据

六、构建NestJs实际项目

在这里插入图片描述

1、项目创建

首先确定你已经安装了Node.js, Node.js 安装会附带npx和一个npm 包运行程序。要创建新的Nest.js 应用程序,请在终端上运行以下命令:

npm i -g @nestjs/cli  // 全局安装Nest
nest new project-name  // 创建项目

执行完创建项目, 会初始化下面这些文件, 并且询问你要是有什么方式来管理依赖包。

如果你有安装yarn,可以选择yarn,能更快一些。

注意: Nest.js 要求 Node.js(>= 10.13.0,v13 除外), 如果你的Node.js 版本不满足要求,可以通过nvm包管理工具安装符合要求的Node.js版本

2、项目结构

src
├── app.controller.spec.ts
├── app.controller.ts
├── app.module.ts
├── app.service.ts
├── main.ts
app.controller.ts单个路由的基本控制器(Controller)
app.controller.spec.ts针对控制器的单元测试
app.module.ts应用程序的根模块(Module)
app.service.ts具有单一方法的基本服务(Service)
main.ts应用程序的入口文件,它使用核心函数 NestFactory 来创建 Nest 应用程序的实例。

3、第一个接口

前面我们已经启动了服务, 那我们怎么查看呢, 首先就是找到入口文件main.ts

import { NestFactory } from '@nestjs/core';
import { AppModule } from './app.module';

async function bootstrap() {
  const app = await NestFactory.create(AppModule);
  await app.listen(3000);
}
bootstrap();

内容比较简单, 使用Nest.js的工厂函数NestFactory来创建了一个AppModule实例,启动了 HTTP 侦听器,以侦听main.ts 中所定义的端口。

监听的端口号可以自定义, 如果3000端口被其他项目使用,可以更改为其他的端口号

前边看到main.ts中也没有别的文件引入, 只有AppModule, 打开src/app.module.ts:

import { Module } from '@nestjs/common';
import { AppController } from './app.controller';
import { AppService } from './app.service';

@Module({
  imports: [],
  controllers: [AppController],
  providers: [AppService],
})
export class AppModule {}

AppModule是应用程序的根模块,根模块提供了用来启动应用的引导机制,可以包含很多功能模块。

.mudule文件需要使用一个@Module() 装饰器的类,装饰器可以理解成一个封装好的函数,其实是一个语法糖(对装饰器不了解的,可以看走近MidwayJS:初识TS装饰器与IoC机制)。@Module() 装饰器接收四个属性:providerscontrollersimportsexports

  • providers:Nest.js注入器实例化的提供者(服务提供者),处理具体的业务逻辑,各个模块之间可以共享(注入器的概念后面依赖注入部分会讲解);
  • controllers:处理http请求,包括路由控制,向客户端返回响应,将具体业务逻辑委托给providers处理;
  • imports:导入模块的列表,如果需要使用其他模块的服务,需要通过这里导入;
  • exports:导出服务的列表,供其他模块导入使用。如果希望当前模块下的服务可以被其他模块共享,需要在这里配置导出;

app.module.ts中,看到它引入了app.controller.tsapp.service.ts,分别看一下这两个文件:

// app.controller.ts
import { Controller, Get } from '@nestjs/common';
import { AppService } from './app.service';

@Controller()
export class AppController {
  constructor(private readonly appService: AppService) {}

  @Get()
  getHello(): string {
    return this.appService.getHello();
  }
}

使用@Controller装饰器来定义控制器, @Get是请求方法的装饰器,对getHello方法进行修饰, 表示这个方法会被GET请求调用。

// app.service.ts
import { Injectable } from '@nestjs/common';

@Injectable()
export class AppService { 
  getHello(): string {
    return 'Hello World!';
  }
}

从上面,我们可以看出使用@Injectable修饰后的 AppService, 在AppModule中注册之后,在app.controller.ts中使用,我们就不需要使用new AppService()去实例化,直接引入过来就可以用。

至此,对于http://localhost:9080/接口返回的Hello World逻辑就算理清楚了, 在这基础上我们再详细的学习一下Nest.js中的路由使用。

4、路由装饰器

Nest.js中没有单独配置路由的地方,而是使用装饰器。Nest.js中定义了若干的装饰器用于处理路由。

@Controller

如每一个要成为控制器的类,都需要借助@Controller装饰器的装饰,该装饰器可以传入一个路径参数,作为访问这个控制器的主路径:

app.controller.ts文件进行修改

// 主路径为 app
@Controller("app")
export class AppController {
  constructor(private readonly appService: AppService) {}

  @Get()
  getHello(): string {
    return this.appService.getHello();
  }
}

然后重新启一下服务。

5、HTTP方法处理装饰器

@Get@Post@Put等众多用于HTTP方法处理装饰器,经过它们装饰的方法,可以对相应的HTTP请求进行响应。同时它们可以接受一个字符串或一个字符串数组作为参数,这里的字符串可以是固定的路径,也可以是通配符。

继续修改app.controller.ts,看下面的例子:

// 主路径为 app
@Controller("app")
export class AppController {
  constructor(private readonly appService: AppService) {}
  
  // 1. 固定路径:
  // 可以匹配到 get请求,http://localhost:9080/app/list
  @Get("list")
  getHello(): string {...}
  
  // 可以匹配到 post请求,http://localhost:9080/app/list
  @Post("list")
  create():string{...}
  
  // 2.通配符路径(?+* 三种通配符 )
  // 可以匹配到 get请求, http://localhost:9080/app/user_xxx
  @Get("user_*")
  getUser(){return "getUser"}
  
  // 3.带参数路径
  // 可以匹配到put请求,http://localhost:9080/app/list/xxxx
  @Put("list/:id")
  update(){ return "update"}
}

由于修改了文件, 需要重启才能看到路由, 每次都重启简直就是噩梦,本来打算配置一个实时监听文件变化,发现Nest.js非常贴心的配置好了, 我们只要运行命令即可:

npm run start:dev

这样再修改什么内容, 保存后都会自动重启服务了。

6、全局路由前缀

除了上面这些装饰器可以设置路由外, 我们还可以设置全局路由前缀, 比如给所以路由都加上/api前缀。此时需要修改main.ts

async function bootstrap() {
  const app = await NestFactory.create(AppModule);
  app.setGlobalPrefix('api'); // 设置全局路由前缀
  await app.listen(3000);
}
bootstrap();

此时之前的路由,都要变更为:

http://localhost:3000/api/xxxx

到此我们认识了ControllerServiceModule、路由以及一些常用的装饰器, 那接下来就实战一下,我们以开发文章(Post)模块作为案例, 实现文章简单的CRUD,带大家熟悉一下这个过程。

七、创新新模块CURD

  • 创建服务类

nest g service posts

// src/posts/posts.service.ts
import { Injectable } from '@nestjs/common';

@Injectable()
export class PostsService {}

创建app.service.ts文件,并且在app.module.ts文件下,@Module装饰器的providers中注入注入。

其实nest-cli提供的创建命令还有很多, 比如创建过滤器、拦截器和中间件等,由于这里暂时用不到,就不过多的介绍,后面章节用到了再介绍。

八、简单CURD创建

  • nest g resource user 一键搞定整个模块

    • 执行完毕后就自动生成了一个 user 文件夹,同时在app.module.ts也进行了自动导入
    • 来到`user.controller.ts`中我们会发现它已经帮你写好了这些请求的示例
      
import {
  Controller,
  Get,
  Post,
  Body,
  Patch,
  Param,
  Delete,
} from '@nestjs/common';
import { UserService } from './user.service';
import { CreateUserDto } from './dto/create-user.dto';
import { UpdateUserDto } from './dto/update-user.dto';

@Controller('user')
export class UserController {
  constructor(private readonly userService: UserService) {}

  @Post()
  create(@Body() createUserDto: CreateUserDto) {
    return this.userService.create(createUserDto);
  }

  @Get()
  findAll() {
    return this.userService.findAll();
  }

  @Get(':id')
  findOne(@Param('id') id: string) {
    return this.userService.findOne(+id);
  }

  @Patch(':id')
  update(@Param('id') id: string, @Body() updateUserDto: UpdateUserDto) {
    return this.userService.update(+id, updateUserDto);
  }

  @Delete(':id')
  remove(@Param('id') id: string) {
    return this.userService.remove(+id);
  }
}

我们可以看到有很多装饰器,像@Controller(‘user’)定义的是请求路径user,而@Get,@Post等这些就代表请求方式的装饰器,比如你用 POSt 请求调用http://localhost:3000/user就会进入@Post()下面的 create()方法(这里你需要一个模拟请求的工具,比如 Apifox 或者 Postman 等),这里我使用 Apifox 进行模拟 post 请求,我们修改一下user.service.ts中的create函数

  create(createUserDto: CreateUserDto) {
    return {
        code:200,
        result:'请求成功'
    };
  }

如果我们想要获取前端 Post 请求传过来参数,可以直接用@Body装饰器即可,同样的 get 请求的话则使用@Query,这里以 post 请求为例,我们回到user.controller.ts中的 create 函数里

@Post()
  create(@Body() createUserDto: CreateUserDto) {
    console.log(createUserDto);
    return this.userService.create(createUserDto);
  }

前端 post 携带 body 请求

如果你想直接获得 body 中的 username,你可以直接

 create(@Body('name') name: string) {
    console.log(name);//小月
  }

看到这有小伙伴就会问了CreateUserDto干啥的,很简单,它是用来描述数据形状的,也就是说它可以定义应该接受前端传来的什么参数,参数类型等,比如在 create-user.dto.ts 中可以这样定义

export class CreateUserDto {
  namename: string;
}

很多情况下我们需要获取前端传过来的请求头,其实在 nestjs 中获取请求头也很简单,只需要一个 Headers 装饰器即可

  @Post()
  create(@Body() createUserDto: CreateUserDto, @Headers() headers) {
    console.log(headers);
    return this.userService.create(createUserDto);
  }

关于装饰器还有很多,由于篇幅有限这里就不再过多介绍了,大家可以到官网自行查看呦~

九、Mysql

安装电脑匹配的mysql和mysql workbench

由于我的mac系统版本问题,我安装的都是8.0.19版本,当然没有任何问题的

1、TypeORM连接数据库

前置知识

首先,简单说一下什么是ORM?

我们如果直接使用Node.js操作mysql提供的接口, 那么编写的代码就比较底层, 例如一个插入数据代码:

// 向数据库中插入数据 
connection.query(`INSERT INTO posts (title, content) VALUES ('${title}', '${content}')`,
    (err, data) => {
    if (err) { 
    console.error(err) 
    } else {
    console.log(data) 
    }
})

考虑到数据库表是一个二维表,包含多行多列,例如一个posts的表:

mysql> select * from posts;
+----+--------+------------+
| id | title       | content      |
+----+-------------+--------------+
|  1 | Nest.js入门 | 文章内容描述 |
+----+--------+------------+

每一行可以用一个JavaScript对象来表示, 比如第一行:

{
    id: 1,
    title:"Nest.js入门",
    content:"文章内容描述"
}

这就是传说中的ORM技术(Object-Relational Mapping),把关系数据库的表结构映射到对象上。

所以就出现了SequelizetypeORMPrisma这些ORM框架来做这个转换, (ps:Prisma呼声很高,喜欢探索的可以尝试一下)我们这里选择typeORM来操作数据库。 这样我们读写都是JavaScript对象,比如上面的插入语句就可以这样实现:

    return await this.userRepository.save(createUserDto);

接下来就是真正意义上的使用typeORM操作数据库, 首先我们要安装以下依赖包:

npm install @nestjs/typeorm typeorm mysql2 -S

连接数据库的方法:

app.mudule.ts文件中使用TypeOrmModule.forRoot

import { TypeOrmModule } from '@nestjs/typeorm';

@Module({
  imports: [
    UserModule,
    TypeOrmModule.forRoot({
      type: 'mysql',
      host: 'localhost',
      port: 3306,
      username: 'root',
      password: 'lt851328',
      database: 'nestjs',
      entities: [__dirname + '/**/*.entity{.ts,.js}'],
      synchronize: true,
    }),
  ],
  controllers: [AppController],
  providers: [AppService],
})

十、CRUD代码编写

接下来我们根据前端传来的参数对数据库进行一个简单的 crud 操作,注意这里只是演示,少了一些逻辑判断。 首先在user.module.ts中导入数据库相关东西

import { Module } from "@nestjs/common";
import { UserService } from "./user.service";
import { UserController } from "./user.controller";
import { UserEntity } from "./entities/user.entity";
import { TypeOrmModule } from "@nestjs/typeorm";
@Module({
  imports: [TypeOrmModule.forFeature([UserEntity])],
  controllers: [UserController],
  providers: [UserService],
})
export class UserModule {}

此时在create-user.dto.ts 定义一下接收前端传来的参数

export class CreateUserDto {
  username: string;
  password: string;
}

然后在user.service.ts进行数据库数据的操作,代码如下:

import { Injectable } from '@nestjs/common';
import { CreateUserDto } from './dto/create-user.dto';
import { UpdateUserDto } from './dto/update-user.dto';
import { UserEntity } from './entities/user.entity';
import { Repository } from 'typeorm';
import { InjectRepository } from '@nestjs/typeorm';

@Injectable()
export class UserService {
  constructor(
    @InjectRepository(UserEntity)
    private readonly userRepository: Repository<UserEntity>,
  ) {}
  create(createUserDto: CreateUserDto) {
    console.log(createUserDto);
    return this.userRepository.save(createUserDto);
  }

  async findAll() {
    return await this.userRepository.find();
  }

  findOne(id: number) {
    return `This action returns a #${id} user`;
  }
  
  async update(id: number, updateUserDto: UpdateUserDto) {
    const db = this.userRepository.createQueryBuilder();
    return await db.update(updateUserDto).where({ id }).execute();
  }

  async remove(id: number) {
    const db = this.userRepository.createQueryBuilder();
    return await db.delete().where({ id }).execute();
  }
}

十一、统一接口规范

通过上面的一通操作,细心的小伙汁肯定发现了上面接口返回的数据是个什么玩意.要状态没状态,要描述没描述的,这样拿给前端前端不得揍死你。所以为了把最好的留给前端,我们还需要对接口返回进行一个统一数据封装。
在这里插入图片描述

一般接口返回的数据大致格式可能如下

{
    data:业务参数,
    code:状态码,
    describe:状态描述
    ...
}

1、filter过滤器

首先我们使用命令新建一个过滤器,用来抛出我们需要返回给前端的错误码以告知前端传来的是错误请求

nest g filter common/filter/http-exception

然后修改一下http-exception.filter.ts

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({
      code: status,
      timestamp: new Date().toISOString(),
      path: request.url,
    });
  }
}
// This is a custom exception filter that will catch all HttpExceptions and return a JSON response with the status code, timestamp, and path of the request.
// The catch method takes two arguments: exception and host. exception is the HttpException that was thrown, and host is an ArgumentsHost object that provides access to the request and response objects.
// Inside the catch method, we switch to the HTTP context using the switchToHttp method and get the response and request objects using the getResponse and getRequest methods.

在 main.ts 中注册

import { NestFactory } from '@nestjs/core';
import { AppModule } from './app.module';
import { HttpExceptionFilter } from './common/filter/http-exception/http-exception.filter';
async function bootstrap() {
  const app = await NestFactory.create(AppModule);
  app.useGlobalFilters(new HttpExceptionFilter());
  await app.listen(3000);
}
bootstrap();

这时候我们随便找个接口抛出一个错误码试一下,就你了findAll方法

  async findAll() {
    throw new HttpException('错误的请求', 401);
    return await this.userRepository.find();
  }

然后我们再创建一个拦截器对请求成功的数据进行格式化

2、interceptor拦截器

然后我们再创建一个拦截器对请求成功的数据进行格式化

同样的使用

nest g interceptor common/interceptor/transform

创建一个拦截器,直接把官网示例抄过来transform.interceptor.ts

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) => ({ code: 200, data, describe: '请求成功' })));
  }
}

以下是对这段代码的详细解释:

一、导入模块

import {
  Injectable,
  NestInterceptor,
  ExecutionContext,
  CallHandler,
} from '@nestjs/common';
import { Observable } from 'rxjs';
import { map } from 'rxjs/operators';
  • Injectable:来自@nestjs/common,用于将一个类标记为可注入的服务。在这里,它用于标记TransformInterceptor类可以被 NestJS 的依赖注入系统管理。
  • NestInterceptorExecutionContextCallHandler也来自@nestjs/commonNestInterceptor是一个接口,用于定义拦截器。ExecutionContext提供了关于当前正在处理的请求的上下文信息,包括请求和响应对象等。CallHandler表示对路由处理函数的调用,可以用来控制请求的执行流程。
  • Observable来自rxjs,用于处理异步操作。在 NestJS 中,许多异步操作都是通过 Observable 来实现的。
  • map来自rxjs/operators,用于对 Observable 发出的值进行转换。

二、定义接口

export interface Response<T> {
  data: T;
}

这里定义了一个泛型接口Response,它表示一个响应对象,包含一个泛型类型的数据字段data

三、定义拦截器类

@Injectable()
export class TransformInterceptor<T>
  implements NestInterceptor<T, Response<T>>
{
  intercept(
    context: ExecutionContext,
    next: CallHandler,
  ): Observable<Response<T>> {
    return next
     .handle()
     .pipe(map((data) => ({ code: 200, data, describe: '请求成功' })));
  }
}
  • @Injectable()装饰器将TransformInterceptor类标记为可注入的服务,以便在 NestJS 的依赖注入系统中使用。
  • implements NestInterceptor<T, Response<T>>表示这个类实现了NestInterceptor接口,泛型参数T表示请求处理函数返回的数据类型,而Response<T>是拦截器处理后返回的响应类型。
  • intercept方法是拦截器的核心方法,它接收两个参数:ExecutionContext类型的contextCallHandler类型的next
    • context提供了关于当前请求的上下文信息,可以从中获取请求和响应对象等。
    • next是一个CallHandler,调用next.handle()会触发后续的请求处理流程,即执行路由处理函数。
  • return next.handle().pipe(map((data) => ({ code: 200, data, describe: '请求成功' })));这行代码首先调用next.handle()来触发后续的请求处理流程,然后使用pipemap操作符对处理结果进行转换。map操作符接收一个函数,这个函数将原始的处理结果data转换为一个包含状态码code、数据data和描述describe的对象,这里将状态码设置为 200,并添加了描述“请求成功”。最终返回一个新的Observable,其发出的值是经过转换后的响应对象。

这个拦截器的作用是在请求处理完成后,对响应数据进行统一的格式转换,添加状态码和描述信息,以便在整个应用中提供一致的响应格式。

最后

和过滤器一样在 main.ts 中注册

app.useGlobalInterceptors(new TransformInterceptor());

然后试一下查询接口,到这里我们就完成了返回结果的一个简单封装

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/886416.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

Spring Boot技术栈:打造高效在线商城

2 相关技术 2.1 Springboot框架介绍 Spring Boot是由Pivotal团队提供的全新框架&#xff0c;其设计目的是用来简化新Spring应用的初始搭建以及开发过程。该框架使用了特定的方式来进行配置&#xff0c;从而使开发人员不再需要定义样板化的配置。通过这种方式&#xff0c;Spring…

sentinel原理源码分析系列(三)-启动和初始化

本文是sentinel原理源码分析系列第三篇&#xff0c;分析sentinel启动和初始化 启动/初始化 sentinel初始化分两块&#xff0c;静态初始和适配器(包括aop) 静态初始 1. Root EntranceNode 如果我们用一栋楼类比资源调用&#xff0c;root EntranceNode好比一栋楼的大门&…

vulnhub-unknowndevice64 2靶机

vulnhub&#xff1a;https://www.vulnhub.com/entry/unknowndevice64-2,297/ 导入靶机&#xff0c;放在kali同网段&#xff0c;扫描 靶机在192.168.81.9&#xff0c;扫描端口 啥啊这都是&#xff0c;详细扫描一下 5555是adb&#xff0c;6465是ssh&#xff0c;12345看样子应该是…

简单的springboot 编写Socket服务接口

简单的springboot 编写Socket服务接口 1.需求 我们项目中有部分老接口为票据接口&#xff0c;其中实现为java socket形式进行实现&#xff0c;但是其中大部分信息都是原始公司封装的包进行实现的&#xff0c;想要修改非常费劲&#xff0c;所以此处简单了解了一下socket&#…

洛谷 P11045 [蓝桥杯 2024 省 Java B] 最优分组

[Problem Discription] \color{blue}{\texttt{[Problem Discription]}} [Problem Discription] [Analysis] \color{blue}{\texttt{[Analysis]}} [Analysis] 首先得注意这么一点&#xff1a; k k k 必须得是 n n n 的因数&#xff08;这里的 n , k n,k n,k 对应于题目的 N ,…

量化交易backtrader实践(三)_指标与策略篇(2)_内置指标A开头

在第1节中&#xff0c;我们学习了移动平均线的原理&#xff0c;中位数以及正态分布的概念&#xff0c;并通过python手工做了一个双均线的策略回测。了解了怎么用pandas计算移动平均线&#xff08;rollingmean)&#xff0c;怎么得到某一列上1个的值&#xff08;shift)&#xff0…

【算法与图】通向高效解决方案的钥匙

文章目录 遍历算法BFS&#xff08;广度优先遍历&#xff09;1. 什么是 BFS&#xff1f;2. 特点和应用3. BFS 示例 DFS&#xff08;深度优先搜索&#xff09;1. 什么是 DFS&#xff1f;2. DFS 的基本步骤3. 特点4. DFS 的应用5. DFS 示例 最小生成树问题1. 什么是最小生成树&…

【mmengine】优化器封装(OptimWrapper)(入门)优化器封装 vs 优化器

MMEngine 实现了优化器封装&#xff0c;为用户提供了统一的优化器访问接口。优化器封装支持不同的训练策略&#xff0c;包括混合精度训练、梯度累加和梯度截断。用户可以根据需求选择合适的训练策略。优化器封装还定义了一套标准的参数更新流程&#xff0c;用户可以基于这一套流…

虚拟机三种网络模式详解

在电脑里开一台虚拟机&#xff0c;是再常见不过的操作了。无论是用虚拟机玩只有旧版本系统能运行的游戏&#xff0c;还是用来学习Linux、跑跑应用程序都是很好的。而这其中&#xff0c;虚拟机网络是绝对绕不过去的。本篇文章通俗易懂的介绍了常见的虚拟网络提供的三种网络链接模…

数据结构-3.5.队列的顺序实现

一.队列的顺序实现&#xff0c;初始化操作以及判断队列是否为空&#xff1a; 1.图解&#xff1a; 2.代码&#xff1a; #include<stdio.h> #define MaxSize 10 //定义一个队列最多存储的元素个数 ​ typedef struct {int data[MaxSize]; //用静态数组存放队列元素int f…

【springboot】整合沙箱支付

目录 1. 配置沙箱应用环境2. 配置springboot项目1. 引入依赖2. 配置文件注册下载ngrok 3. 创建支付宝支付服务类4. 支付界面模板5. 控制类实现支付6. 测试 1. 配置沙箱应用环境 使用支付宝账号登录到开放平台控制台。 使用支付宝登录后&#xff0c;看到以下页面&#xff0c;下…

MFC工控项目实例二十二主界面计数背景颜色改变

承接专栏《MFC工控项目实例二十一型号选择界面删除参数按钮禁用切换》 1、在SEAL_PRESSUREDlg.h文件中添加代码 class CSEAL_PRESSUREDlg : public CDialog { public: CBrush m_brush1;CBrush m_brush2;CBrush m_brush3;... } 2、在SEAL_PRESSUREDlg.cpp文件中添加代码 BO…

在2核2G服务器安装部署MySQL数据库可以稳定运行吗?

阿里云2核2G服务器可以安装MySQL数据库吗&#xff1f;当然可以&#xff0c;并且可以稳定运行MySQL数据库&#xff0c;目前阿里云服务器网aliyunfuwuqi.com使用的就是阿里云2核2G服务器&#xff0c;在云服务器上安装MySQL数据库&#xff0c;可以稳定运行。 目前阿腾云用于运行M…

查看 git log的过程中看到 :说明日志输出可能超出屏幕大小,系统进入了分页模式

在命令行提示符中&#xff0c;通常 : 表示系统等待进一步的输入。如果你在查看 git log 的过程中看到 :&#xff0c;说明日志输出可能超出屏幕大小&#xff0c;系统进入了分页模式&#xff0c;默认使用 less 命令查看内容。 此时你可以&#xff1a; 按 q 退出日志查看。按 En…

算法笔记(五)——分治

文章目录 算法笔记&#xff08;五&#xff09;——分治快排颜色分类排序数组数组中的第K个最大元素库存管理 III 归并排序数组交易逆序对的总数计算右侧小于当前元素的个数翻转对 算法笔记&#xff08;五&#xff09;——分治 分治算法字面上的解释是“分而治之”&#xff0c;就…

Python 从入门到实战32(数据库MySQL)

我们的目标是&#xff1a;通过这一套资料学习下来&#xff0c;通过熟练掌握python基础&#xff0c;然后结合经典实例、实践相结合&#xff0c;使我们完全掌握python&#xff0c;并做到独立完成项目开发的能力。 上篇文章我们讨论了数据库编程接口操作的相关知识。今天我们将学习…

【框架篇】过滤器和拦截器的区别以及使用场景

在项目开发中&#xff0c;常常会同时配置拦截器&#xff08;Interceptor&#xff09;和过滤器&#xff08;Filter&#xff09;&#xff0c;以下就是它们两个主要的区别&#xff1a; 过滤器&#xff08;Filter&#xff09; 配置和实现 Filter的实现还是很简单的&#xff0c;可…

【微服务】组件、基础工程构建(day2)

组件 服务注册和发现 微服务模块中&#xff0c;一般是以集群的方式进行部署的&#xff0c;如果我们调用的时候以硬编码的方式&#xff0c;那么当服务出现问题、服务扩缩容等就需要对代码进行修改&#xff0c;这是非常不好的。所以微服务模块中就出现了服务注册和发现组件&…

计算机毕业设计 基于Python的广东旅游数据分析系统的设计与实现 Python+Django+Vue Python爬虫 附源码 讲解 文档

&#x1f34a;作者&#xff1a;计算机编程-吉哥 &#x1f34a;简介&#xff1a;专业从事JavaWeb程序开发&#xff0c;微信小程序开发&#xff0c;定制化项目、 源码、代码讲解、文档撰写、ppt制作。做自己喜欢的事&#xff0c;生活就是快乐的。 &#x1f34a;心愿&#xff1a;点…

华为云+WordPress+Puock主题搭建个人博客

网站访问地址&#xff1a;qingxuly.cn 搭建网站 购买华为云服务器&#xff0c;购买域名&#xff0c;进行备案&#xff0c;配置域名解析等操作&#xff0c;请参考华为云文档。 安装Ubuntu系统 华为云控制台中给云服务器安装Ubuntu2204。 配置服务器安全组 华为云安全组中创建安…