从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 _petconstructor () {this._pet = new Dog()}
}const xiaoming = new Person()

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

在上述例子中:

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

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

class Dog {}class Person {private _petconstructor (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());

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

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

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Java的学习(语法相关)

字符串存储的问题 char 和字符串都是字符的集合&#xff0c;它们之间的确有相似性&#xff0c;但在 Java 中它们有着不同的存储机制和处理方式。让我从 char 和 String 的本质区别入手来解释。 1. char 和 String 的区别 char 是基本类型&#xff1a;char 是 Java 中的基本数据…

Java-数据结构-Map和Set(三)-习题 o(´^`)o

目录 ❄️一、习题一(只出现一次的数字)&#xff1a; ❄️二、习题二(随机链表的复制)&#xff1a; ❄️三、习题三(宝石与石头)&#xff1a; ❄️四、习题四(旧键盘)&#xff1a; ❄️五、习题五(前k个高频单词)&#xff1a; ❄️总结&#xff1a; ❄️一、习题一(只出现一…

Python(三)——列表

文章目录 创建列表访问下标遍历列表元素新增元素查找元素删除元素连接列表切片操作 创建列表 创建列表主要有两种方式 [ ]表示一个空的列表 a [] print(type(a)) # <class list> print(a) # []通过list()的方式来创建一个空列表 a list() print(type(a)) # …

Java对象头

一、对象在堆内存中的布局 1.定义 在HotSpot虚拟机中&#xff0c;对象在堆内存的存储布局可以划分为三个部分&#xff1a;对象头&#xff08;Header&#xff09;、实例数据&#xff08;Instance Data&#xff09;、和对齐填充&#xff08;Paddin&#xff09;。 二、对象在堆内…

Rstudio:强大的R语言集成开发环境(IDE)

Rstudio 应该是 R 语言使用的标配&#xff0c;尽管 Rstudio 的母公司 Posit 推出了新一代的集成开发环境 Positron&#xff0c;但其还处于开发阶段。作为用户不妨让其成熟后再使用&#xff0c;现阶段还是 Rstudio 更稳定。 如果你在生物信息学或统计学领域工作&#xff0c;R语言…

【springboot】整合沙箱支付

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

动态内存分配

1. 基本使用 在内存空间中&#xff0c;我们如何做到想用多少内存空间就申请多少内存空间&#xff1f; 使用以下函数可以实现&#xff1a; 如何利用malloc申请一片连续的内存空间&#xff1a; int* p malloc(100 * sizef(int)); 该代码实现了&#xff0c;申请一片空间&#…

VS开发 - 静态编译和动态编译的基础实践与混用

目录 1. 基础概念 2. 直观感受一下静态编译和动态编译的体积与依赖项目 3. VS运行时库包含哪些主要文件&#xff08;从VS2015起&#xff09; 4. 动态库和静态库混用的情况 5. 感谢清单 1. 基础概念 所谓的运行时库&#xff08;Runtime Library&#xff09;就是WINDOWS系统…

828华为云征文|WordPress部署

目录 前言 一、环境准备 二、远程连接 三、WordPress简介 四、WordPress安装 1. 基础环境安装 ​编辑 2. WordPress下载与解压 3. 创建站点 4. 数据库配置 总结 前言 WordPress 是一个非常流行的开源内容管理系统&#xff08;Content Management System, CMS&#xf…

进度条(倒计时)Linux

\r回车(回到当前行开头) \n换行 行缓冲区概念 什么现象&#xff1f; 什么现象&#xff1f;&#xff1f; 什么现象&#xff1f;&#xff1f;&#xff1f; 自己总结&#xff1a; #pragma once 防止头文件被重复包含 倒计时 在main.c中&#xff0c;windows.h是不可以用的&…

CleanMyMac X v4.12.1 中文破解版 Mac优化清理工具

在数字时代&#xff0c;我们的Mac设备承载着越来越多的重要信息和日常任务。然而&#xff0c;随着时间的推移&#xff0c;这些设备可能会变得缓慢、混乱&#xff0c;甚至充满不必要的文件。这就是CleanMyMac X发挥作用的地方。 CleanMyMac X是一款功能强大的Mac优化工具&#…

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

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

CSP-J Day 3 模拟赛补题报告

姓名&#xff1a;王胤皓&#xff0c;校区&#xff1a;和谐校区&#xff0c;考试时间&#xff1a; 2024 2024 2024 年 10 10 10 月 3 3 3 日 9 : 00 : 00 9:00:00 9:00:00~ 12 : 30 : 00 12:30:00 12:30:00&#xff0c;学号&#xff1a; S 07738 S07738 S07738 请关注作者的…

[20241003] 狂飙500天,国产大模型如何突破商业化之困?

大模型加速狂飙&#xff0c;AI商业化却面临巨大鸿沟。 一方面&#xff0c;传统企业不知道怎么将AI融入原始业务&#xff0c;另一方面&#xff0c;AI企业难以找到合适的变现方式。AI企业究竟该如何突破商业化之困&#xff1f;B端和C端&#xff0c;呈现出两种不同的路径。 纵…

Pikachu-暴力破解-验证码绕过(on client)

访问页面&#xff0c; 从burpsuite 上看到返回的源代码&#xff1b; 验证码生成时通过 createCode 方法生成&#xff0c;在前端页面生成&#xff1b; 同时也是在前端做的校验&#xff1b; 直接验证&#xff1b;F12 -- 网络&#xff0c;随便输入个账号、密码、验证码&#xff0…

OceanBase—02(入门篇——对于单副本单节点,由1个observer扩容为3个observer集群)——之前的记录,当初有的问题未解决,目前新版未尝试

OceanBase—02&#xff08;入门篇——对于单副本单节点&#xff0c;由1个observer扩容为3个observer集群&#xff09;——之前的记录&#xff0c;有的问题未解决&#xff0c;新版未尝试 1、前言—安装单副本单节点集群1.1 docker安装OB 2、查看现有集群情况2.1 进入容器&#x…

计算机网络的整体认识---网络协议,网络传输过程

计算机网络背景 网络发展 独立模式: 计算机之间相互独立; 网络互联: 多台计算机连接在一起, 完成数据共享; 局域网LAN: 计算机数量更多了, 通过交换机和路由器连接在一起; 广域网WAN: 将远隔千里的计算机都连在一起;所谓 "局域网" 和 "广域网" 只是一个相…

【EXCEL数据处理】000011 案列 EXCEL带有三角形图标的单元格转换,和文本日期格式转换。

前言&#xff1a;哈喽&#xff0c;大家好&#xff0c;今天给大家分享一篇文章&#xff01;创作不易&#xff0c;如果能帮助到大家或者给大家一些灵感和启发&#xff0c;欢迎收藏关注哦 &#x1f495; 目录 【EXCEL数据处理】000011 案列 EXCEL带有三角形图标的单元格转换。使用…

基于SpringBoot+Vue+MySQL的民宿预订平台

系统展示 用户前台界面 管理员后台界面 商家后台界面 系统背景 随着旅游业的蓬勃发展&#xff0c;民宿作为一种独特的住宿方式&#xff0c;受到了越来越多游客的青睐。然而&#xff0c;传统的民宿预定方式往往存在信息不对称、效率低下等问题&#xff0c;难以满足游客的个性化需…

npm切换到淘宝镜像

1、输入以下命令后回车&#xff0c;npm切换至淘宝镜像 npm config set registry https://registry.npmmirror.com 2、输入以下命令后回车&#xff0c;检查是否切换成功 npm config get registry 若返回此信息&#xff0c;表示切换成功 3、切换后就可使用淘宝镜像加快npm包的…