likes
comments
collection
share

Nest 控制器阅读 NestJS 中文文档和神光的 Nest 通关秘籍后的学习收获。 Nest 中的控制器作用就是负责

作者站长头像
站长
· 阅读数 43
Nest 控制器阅读 NestJS 中文文档和神光的 Nest 通关秘籍后的学习收获。 Nest 中的控制器作用就是负责

Nest 中的控制器作用就是负责处理传入的请求并向客户端返回响应。。嗯,如果要说从前端的角度来讲,控制器就是路由,路由匹配与路由展示。

为什么会这么说呢?因为都是按照输入、匹配、输出的执行顺序。

细想一下前端路由,当我们输入一个 url 地址之后,就会进入代码程序中,去进行路由匹配(Switch 组件),当匹配成功之后,就进行路由展示(Route 组件)。

而对于控制器来说也是一样,当客户端请求一个 api 路径,进入服务代码,也会进行路由匹配,当路由匹配成功之后,就返回对应的结果给客户端。

前端路由在前端中非常的重要,那么控制器在 Nest 也是非常的重要。

控制器基本认识

定义一个基本的控制器,需要使用 @Controller 来进行装饰。

import { Controller } from "@nestjs/common";

@Controller()
export class XxxController {} // XxxController 就是一个控制器

在 nest 程序中控制器会存在多个,都会被路由机制所管控。路由机制控制哪个控制器接收哪些请求。

而每个控制器又存在多个路由,不同的路由可以执行不同的操作。路由匹配时,是从上到下的顺序依次匹配。

Nest 控制器阅读 NestJS 中文文档和神光的 Nest 通关秘籍后的学习收获。 Nest 中的控制器作用就是负责

上图是 XxxController 控制器下定义的三个路由,2 个 GET 请求,1 个 POST 请求。当客户端请求不同路径的时候,就会返回不同的内容。

仔细观察,上面的代码可以被优化的,/xxx 写的过于频繁,每个路由都在写。那么有什么办法可以解决呢?

其实每个路由都写上 /xxx,说明了它们是被设计者归为同一组的,而控制器就是以组来进行划分的。那么就可以借助 @Controller装饰器来解决。

@Controller("/xxx")
export class XxxController {}

当上面这样写之后,每个路由都不需要在写/xxx 了。当继续接口请求时,nest 内部会自动请求映射到该控制器并进行路由匹配,这样就减少了代码的重复编写。

这也就是中文网所说的,,路由路径包括可选的控制器路径前缀请求方法装饰器中声明的任何路径字符串

再来看一个现象(使用 Apifox 进行模拟):

Nest 控制器阅读 NestJS 中文文档和神光的 Nest 通关秘籍后的学习收获。 Nest 中的控制器作用就是负责

当使用 GET 请求 /xxx/list 时,会发现返回的数据是 动态路由。从前面看, 应该返回的字符串是 get list,为什么会造成这样的结果呢?

其实在上面就已经说了,路由匹配是从上到下的顺序,而不是精确匹配路由。

当我们动态路由获取的 id 进行返回时,就会发现:

Nest 控制器阅读 NestJS 中文文档和神光的 Nest 通关秘籍后的学习收获。 Nest 中的控制器作用就是负责

所以说,在我们在设计程序路由时,还要留一个心思来考虑路由匹配的顺序,保证程序执行是可预料的。

控制器请求响应

控制器负责处理传入的请求并向客户端返回响应。

  • 是如何接受客户端的请求?
  • 是如何响应客户端?

请求方式

nestjs/common 中暴露出了几种请求方式的装饰器

import { Get, Post, Put, Delete, Patch, Head } from "@nestjs/common";

一般来说,常用的请求方式就只有四种:get、post、put、delete,遵循 restful 接口风格规范。

而且还有很多公司服务端接口就只有两种请求方式:get 和 post。

这是装饰器都支持可选参数。可选参数作为请求路径的一部分。

@Get(':id') // 动态参数

五种数据传输

第一种:url params

http://localhost:3000/list/12321

把参数写着 url 上, 这里的 12321 就是路径中的参数(url param),获取时直接从路径上读取。

第二种:url query

也是把参数写 url 上,只不过是通过 url 中 ?后面的用 & 分隔的字符串传递数据。如果是非英文的字符和一些特殊字符的情况,最好还要使用 encodeURIComponent 编码一下。

`http://localhost:3000?name=${encodeURIComponent("向")}&age=19`;

第三种:form-urlencoded

form 表单的提交方式。与 query 的方式对比:

  • 相同点:其格式都是一样,&拼接成的字符串,一些特殊的字符需要进行编码
  • 不同点:存在位置不一样,query 是存放在 url 路径上,而 form-urlencoded 是存在 body 中

也必须设置content-type类型为application/x-www-form-urlencoded

第四种:form-data

form data 不再是通过 & 分隔数据,而是使用 boundary 作为边界,进行数据分割。 boundary组成为 -----字符串分隔符

类似这种:

Nest 控制器阅读 NestJS 中文文档和神光的 Nest 通关秘籍后的学习收获。 Nest 中的控制器作用就是负责

form-data 的 Content-Type 也需要设置为 multipart/form-data

第五种:json

post 最常用的方式,把 json 对象保存在 body 中,服务端直接从 body 中获取。

Content-Type 的值为 application/json,也是默认值。

请求对象和响应对象

针对请求对象,Nest 内部提供了 @Request() 或者@Req()来直接获取 request 对象,来获取里面的 query 对象、params 对象、body 对象等等。也还提供了 @Query()@Param()@Body() 等装饰器直接获取各自的对象。

// 方式一: request
import { Request } from "@nestjs/common";

@Controller("xxx")
export class XxxController {
  @Get("query")
  getQuery(@Request() request) {
    console.log(request);
    console.log(request.query); // query 对象
    console.log(request.body); // body 对象
    console.log(request.params); // params 对象
    return request.query;
  }
}
// 方式二:直接获取各自的对象
import { Controller, Get, Param, Query, Body } from "@nestjs/common";

@Controller("xxx")
export class XxxController {
  @Get("query")
  getQuery(@Query() query, @Body() body, @Param() params) {
    console.log(query);
    console.log(body);
    console.log(params);
    return query;
  }
}

针对上面两种,还是推荐使用方式二,直接获取各自的对象。

针对响应,Nest 内部提供了两种方式: nest 标准指定库(express,fastify)

指定库, response 对象就可以通过 @Response 或者 @Res方式来进行获取,然后执行 response.send() 的形式进行响应。但是官方还是推荐使用 nest 标准,采用内置方法。

使用这个内置方法,当请求处理程序返回一个 JavaScript 对象或数组时,它将 自动地 序列化为 JSON。 然而,当它返回一个 JavaScript 原始类型(例如,string, number, boolean)时,Nest 将只发送值而不尝试序列化它。 这使得响应处理变得简单: 只需返回值,Nest 会处理剩下的事情

代码实操

针对上面的五种数据传输,采用请求方式,获取请求对象及响应处理。

  • 使用 axios 来进行网络请求的发送
  • 使用 nest 服务端来进行响应

针对这里的跨域问题,就直接服务端配置一下即可,就没采用前端的解决跨域方式。

Nest 控制器阅读 NestJS 中文文档和神光的 Nest 通关秘籍后的学习收获。 Nest 中的控制器作用就是负责

app.enableCors(); // 允许跨域

实操一:query 方法

客户端代码

const query = async function () {
  const res = await axios.get(
    "http://localhost:3000/xxx/query?name=copyer&age=18"
  );
  console.log(res.data);
};

直接在路径上拼接 name 和 age 属性。

服务端代码

import { Controller, Get, Request, Query } from "@nestjs/common";

@Controller("xxx")
export class XxxController {
  @Get("query")
  getQuery(@Query() query) {
    return query;
  }
}

也可以直接通过 @Query() 装饰器,获取 query 对象。也可以确切的传递一个属性 @Query('name'),就可以直接获取 query 对象中的 name 属性。其实装饰器底层使用的原理就是读取 request 的 query 对象。

Nest 控制器阅读 NestJS 中文文档和神光的 Nest 通关秘籍后的学习收获。 Nest 中的控制器作用就是负责

实操二:param 方法

客户端代码

const params = async function () {
  const res = await axios.get("http://localhost:3000/xxx/params/111");
  console.log(res.data);
};

111 就是路径上的 params 拼接

服务端代码

import { Controller, Get, Param } from "@nestjs/common";

@Controller("xxx")
export class XxxController {
  @Get("params/:id")
  getParam(@Param("id") id) {
    return id; // 没有进过序列化
  }
}

Nest 控制器阅读 NestJS 中文文档和神光的 Nest 通关秘籍后的学习收获。 Nest 中的控制器作用就是负责

实操三:form-urlencoded

客户端代码

const formUrlencoded = async function () {
  const res = await axios.post(
    "http://localhost:3000/xxx/formurlencoded",
    `name=${encodeURIComponent("向")}&age=18`,
    {
      headers: { "content-type": "application/x-www-form-urlencoded" },
    }
  );
  console.log(res.data);
};
  • 汉字需要编码
  • 设置 Content-Type 为 application/x-www-form-urlencoded

服务端代码

import { Controller, Post, Body, HttpCode } from "@nestjs/common";

@Controller("xxx")
export class XxxController {
  @HttpCode(200)
  @Post("formurlencoded")
  getFormurlencoded(@Body() body) {
    // 这里 body 类型定义应该使用 DTO
    return body;
  }
}
  • @Body() 来获取 body 的参数。
  • post 请求默认返回的状态码为 201,可以通过 @HttpCode(200) 可以设置状态码为 200。

Nest 控制器阅读 NestJS 中文文档和神光的 Nest 通关秘籍后的学习收获。 Nest 中的控制器作用就是负责

方式四:json

客户端代码

const json1 = async function () {
  const res = await axios.post("http://localhost:3000/xxx/json1", {
    name: "向",
    age: 12,
  });
  console.log(res.data);
};

服务端代码

import { Controller, Post, Body, HttpCode } from "@nestjs/common";

@Controller("xxx")
export class XxxController {
  @HttpCode(200)
  @Post("json1")
  getJson(@Body() body) {
    // 这里 body 类型定义应该使用 DTO
    return body;
  }
}

方式五:form-data

客服端代码

<body>
  <input type="file" id="fileInput" multiple />
  <script>
    async function formData() {
      const data = new FormData();
      data.set("name", "向");
      data.set("age", 18);
      data.set("file1", fileInput.files[0]);

      const res = await axios.post("http://localhost:3000/xxx/files", data, {
        headers: { "content-type": "multipart/form-data" },
      });
      console.log(res);
    }

    fileInput.onchange = formData;
  </script>
</body>
  • 传递 form-data 对象
  • 设置 Content-Type 为 multipart/form-data

服务端代码

import {
  Controller,
  Post,
  Body,
  HttpCode,
  UseInterceptors,
  UploadedFiles,
} from "@nestjs/common";

@Controller("xxx")
export class XxxController {
  @HttpCode(200)
  @Post("files")
  @UseInterceptors(
    AnyFilesInterceptor({
      // 文件上传到 uploads 文件夹下面
      dest: "uploads/",
    })
  )
  file(
    @Body() body: FileDto,
    @UploadedFiles() files: Array<Express.Multer.File>
  ) {
    console.log(body, files);
    return body;
  }
}
  • 对于文件上传,使用拦截器,把文件上传到静态资源文件下(uploads)
  • @UploadedFiles() 是 files 对象的装饰器
  • Multer类型定义需要安装 pnpm add -D @types/multer
  • 就可以拿去 body 对象和 files 对象。

Nest 控制器阅读 NestJS 中文文档和神光的 Nest 通关秘籍后的学习收获。 Nest 中的控制器作用就是负责

控制器其他知识

在控制器的请求和响应的过程中,还会穿插一些额外小知识。

请求方式

在 Nest 内部中,还提供了一个装饰器 @All(),可以匹配所有的请求方式。

Nest 控制器阅读 NestJS 中文文档和神光的 Nest 通关秘籍后的学习收获。 Nest 中的控制器作用就是负责

路由时从上到下匹配的,/xxx/query 就会匹配上 GET 的请求方式。但是如果 GET 方式没有匹配上,就会进入 All 路由里面去。

路由通配符

所谓的通配符都是正则中的 *

模式匹配的路由也是支持的。例如,星号(asterisk)用作通配符,将匹配任何字符组合。

@Get('ab*cd')
findAll() {
  return 'This route uses a wildcard';
}

由路径'ab*cd'将匹配abcdab_cdabecd等。字符?+*()可以在路由路径中使用,并且是其正则表达式对应项的子集。连字符(-)和句点(.)在基于字符串的路径中会被直接解释。

头部信息

使用@Header()装饰器,来自定义头部信息。

@Post()
@Header('Cache-Control', 'none') // 设置没有缓存
create() {
  return 'This action adds a new cat';
}

重定向

可以使用@Redirect()装饰器,将响应重定向到特定的 URL;也可以使用特定于库的响应对象(并直接调用res.redirect())。

@Redirect()接受两个参数,urlstatusCode,两者都是可选的。如果省略statusCode,其默认值为302Found)。

import { Redirect } from '@nestjs/common'

@Get('query')
@Redirect('http://localhost:4000', 302) // 当请求该地址时,会转向 4000 的服务器
getQuery(@Query() query) {
  console.log('get');
  return query;
}

存在重定向的地址是动态的,那么 只需要返回如下格式, Nest 内部会帮你进行处理(前提该路由上使用 @Redirect()

{
  url: 'xxx',
  statusCode: 302
}

例如:

@Get('query')
@Redirect('http://localhost:4000', 302)
getQuery(@Query('version') version) {
  if (version && version === '5') {
    return { url: 'http://localhost:5000' };
  }
}

异步性

在 Nest 路由中支持返回一个 promise,返回一个延迟值。nest 内部会自动去解析。

Nest 控制器阅读 NestJS 中文文档和神光的 Nest 通关秘籍后的学习收获。 Nest 中的控制器作用就是负责

export class XxxController {
  @Get("query")
  async getQuery() {
    return new Promise((resolve) => {
      setTimeout(() => {
        resolve("copyer");
      }, 1000);
    });
  }
}

效果:

Nest 控制器阅读 NestJS 中文文档和神光的 Nest 通关秘籍后的学习收获。 Nest 中的控制器作用就是负责

请求负载

通过 DTO(数据传输对象)来定义了数据将如何通过网络发送的对象。推荐使用来定义 DTO。(ES6 class 会保留实体,而 interface 在编译过程中,会被删除,nest 在运行时无法使用。在某些情景下,运行时会存现错误的可能,保留实体比较重要)。

在前面也说过,POST 请求中 body 的类型会通过 DTO 来进行定义

class FormUrlEncodedDto {
  name: string;
  age: number;
}

Nest 控制器阅读 NestJS 中文文档和神光的 Nest 通关秘籍后的学习收获。 Nest 中的控制器作用就是负责

总结

  1. 控制器负责处理传入的请求并向客户端返回响应。
  2. 定义控制器使用 @Controller() 。每个控制器下面又会存在多个路由,路由的配置顺序从上到下的匹配。
  3. 请求方式常用的有 get、post、put、delete,分别对应着 @Get()@Post()@Put()@Delete() 来定义。
  4. 在前后端对接中,数据传输一般有五种方式:param、query、json、form-urlencoded、form-data。传递的方式?保存的位置?以及 content-type 值的设置
  5. 请求对象可以通过 @Request()@Query()@Param()@Body() 来分别获取对应的对象。
  6. 响应对象有两种方式,nest 标准(推荐方式)和 库指定。
  7. 针对文件上传,需要使用到 AnyFilesInterceptor 拦截器,把文件保存在服务端中。也可以通过 @UploadedFiles() 获取 file 对象。
  8. 还有一些额外的特定知识,请求方式通匹配,路径通匹配,设置头部信息,重定向,函数的异步性,以及请求负载(DTO)。
转载自:https://juejin.cn/post/7370170468781277199
评论
请登录