Nest装饰器全解析

271 阅读13分钟

装饰器在Nest中无处不在,不管处理什么内容都需要依靠装饰器来完成,如果你不了解装饰器,可以看这篇文章《ts装饰器的那点东西》来更好理解Nest中的装饰器。

@Module

用于定义一个 NestJS 模块。模块是 NestJS 应用的基本组织单元,它将相关的控制器、服务(提供者)、管道、过滤器、拦截器等组合在一起。

@Controller

用于定义一个控制器类,控制器负责处理传入的HTTP请求并返回响应。它是路由的端点,将特定的路由路径与处理函数关联起来。

@Injectable

  • 在 NestJS 中,@Injectable装饰器用于标记一个类是可被注入的提供者(provider)。提供者是 Nest 应用中的一个关键概念,它们包含了业务逻辑,可以被其他组件(如控制器)所依赖。
  • 当一个类被标记为@Injectable时,Nest 的依赖注入系统就能够识别这个类,并在需要的地方将其实例注入进去。这有助于实现代码的解耦和模块化,使得组件之间的依赖关系更加清晰和易于管理。

@Inject

  • @Inject装饰器用于手动指定要注入的提供者。在依赖注入的过程中,Nest 需要知道要注入的具体对象,通常情况下它可以根据类型自动推断,但在某些复杂场景下,可能需要通过@Inject来明确指定。
  • 通过 token 手动指定注入的 provider,里的token可以是类(class)或者字符串(string)。如果是类,Nest 会根据这个类的类型找到对应的提供者实例进行注入;如果是字符串,则可以根据自定义的字符串标识来查找提供者。

@Optional

  • 有时候,在依赖注入时,某个提供者可能是可选的,即它可能不存在但不应该导致注入失败。@Optional装饰器就是用于声明这样的情况。
  • 当一个被@Inject装饰的提供者同时被标记为@Optional时,如果对应的提供者没有找到,注入过程不会抛出错误,而是将注入的属性设置为undefined或者根据具体情况设置为默认值。
constructor(
    @Optional() private readonly providerTestService: ProviderTestService,
) {}

@Inject('aliasProvide')
@Optional() // 如果不存在的提供其可以设置可选装饰器
private readonly aliasProvide: ProviderTestService;

image.png

@Global

将模块声明为全局模块。我们在创建模块时可以指定@Global装饰器,定义为全局模块,导出的可注入类就不需要在别的模块通过imports导入再使用了。但是该模块还是需要在app.module.ts中进行导入。

  • 在 NestJS 中,模块默认是局部的,这意味着模块内部的提供者、控制器等组件只能在模块内部使用。@Global装饰器用于将一个模块声明为全局模块。
  • 全局模块中的提供者可以在整个应用程序中被注入,而不需要在每个需要使用的模块中再次导入该模块。不过,过度使用全局模块可能会导致代码的可维护性降低,所以应该谨慎使用。

@Catch

  • 在处理 HTTP 请求的过程中,可能会发生各种异常情况。@Catch装饰器用于标记一个异常过滤器(exception filter)类,以指定这个过滤器要处理的异常类型。
  • 例如,如果有一个自定义的异常类MyException,可以通过@Catch(MyException)创建一个异常过滤器,专门用于处理这种类型和其子类型的异常,从而提供自定义的异常处理逻辑,如返回特定的错误响应给客户端。

@UseFilters

  • 当需要在路由级别应用异常过滤器时,就会用到@UseFilters装饰器。它可以将一个或多个异常过滤器应用到特定的控制器类或者控制器方法上。
  • 这样,当在对应的路由处理过程中发生异常时,就会触发这些被应用的异常过滤器来处理异常,而不是使用默认的异常处理机制。

@UsePipes

  • 管道(pipe)在 NestJS 中用于对输入数据进行转换和验证等操作。@UsePipes装饰器用于在路由级别应用管道。
  • 可以将自定义的管道应用到控制器类、控制器方法或者控制器方法参数上,这样在数据进入相应的处理逻辑之前,会先经过管道的处理,例如验证请求数据是否符合特定的格式或类型要求。

@UseInterceptors

  • 拦截器(interceptor)是 NestJS 中用于在请求处理的不同阶段进行操作的一种机制,例如在请求处理前、后添加日志记录,或者对响应进行修改等。
  • @UseInterceptors装饰器用于在路由级别使用拦截器,通过将拦截器应用到控制器类或者控制器方法上,可以在特定的路由处理过程中启用拦截器提供的功能。

@SetMetadata

  • 在 NestJS 中,元数据(metadata)可以用于存储关于类或者处理函数(handler)的额外信息@SetMetadata装饰器用于在类或者处理函数上添加元数据。
  • 这些元数据可以在后续的中间件、鉴权、管道、拦截器等组件中被获取和使用,例如用于权限验证,根据元数据中的权限信息判断是否允许访问某个路由。
 @Get('meta')
@SetMetadata('roles', ['admin']) // 添加角色信息,在
getMeta() {
    return '设置元数据';
}

// 鉴权Guard
import { CanActivate, ExecutionContext, Injectable } from '@nestjs/common';
import { Reflector } from '@nestjs/core';
import { Observable } from 'rxjs';

@Injectable()
export class LoginGuard implements CanActivate {
  constructor(private reflector: Reflector) {}
  canActivate(
    context: ExecutionContext,
  ): boolean | Promise<boolean> | Observable<boolean> {
    const roles = this.reflector.get<string[]>('roles', context.getHandler());
    console.log('鉴权----获取到设置的元素据', roles); // 获取到设置的元素据
    return true;
  }
}

image.png

@Get、@Post、@Put、@Delete、@Patch、@Options、@Head

  • 这些装饰器分别对应 HTTP 协议中的不同请求方法。
  • @Get用于标记一个控制器方法处理 HTTP GET 请求,通常用于获取资源。@Post用于处理 HTTP POST 请求,常用于创建新资源。@Put用于更新整个资源(一般需要提供完整的更新后的数据),@Delete用于删除资源,@Patch用于部分更新资源,@Options用于处理 HTTP OPTIONS 请求(通常与跨域资源共享等相关),@Head用于处理 HTTP HEAD 请求(与 GET 类似,但只返回头部信息,不返回主体内容)。

@Param

  • 在定义路由时,可能会包含动态参数,例如/aaa/:id中的:id就是一个动态参数。@Param装饰器用于在控制器方法中取出这些 URL 中的参数。
  • 可以通过@Param('id')这样的方式在控制器方法中获取对应的参数值,并在业务逻辑中使用,比如根据这个id查询数据库中的特定记录。

@Query

  • 查询参数(query parameters)是在 URL 中以?开头的部分,例如/aaa?name=xx中的name就是一个查询参数。@Query装饰器用于取出这些查询部分的参数。
  • 可以通过@Query('name')在控制器方法中获取查询参数的值,以便根据这些参数进行查询过滤、分页等操作。

@Body

  • 在处理 HTTP 请求时,POST、PUT、PATCH 等请求通常会在请求体(request body)中包含数据。@Body装饰器用于取出请求体中的数据。
  • 为了更好地对请求体数据进行类型检查和约束,可以通过数据传输对象(DTO - Data Transfer Object)类来接收请求体数据,Nest 会根据 DTO 类的定义对请求体数据进行验证和转换。

@RawBody

  • 在 NestJS 中,@RawBody装饰器可用于获取 HTTP 请求的原始请求体(raw body)内容。与@Body装饰器不同,@Body装饰器通常会对请求体内容进行一定的解析(例如,根据请求的Content-Type将其解析为对象或其他合适的数据结构),而@RawBody直接获取未经解析的原始字节流形式的请求体内容。
  • 这在某些特定场景下非常有用,例如,当需要对请求体进行自定义的加密验证、签名验证或者处理一些特殊格式(如非标准的 JSON 或表单数据格式)的请求体时,需要获取原始的请求体内容来进行操作。

@UploadedFile, @UploadedFiles

  • @UploadedFile

    • 用于在处理单个文件上传的控制器方法中获取上传的文件对象。例如,当用户上传一个头像图片时,可以使用这个装饰器在控制器方法中获取到该图片文件的相关信息。
  • @UploadedFiles

    • 用于获取多个上传的文件对象,适用于处理如批量上传文件的场景,例如一次上传多个文档或者图片等情况。

@Headers

  • 请求头(request headers)包含了关于请求的各种元信息,如Content-TypeAuthorization等。@Headers装饰器用于取出某个或全部请求头信息
  • 可以通过@Headers('Authorization')这样的方式获取特定的请求头值,或者不传入参数获取所有请求头的对象,以便在业务逻辑中进行身份验证、内容协商等操作。

@Header

  • @Header装饰器用于修改响应头(response headers)。可以在控制器方法中使用这个装饰器来设置特定的响应头信息,如@Header('Content-Type', 'application/json')用于设置响应的内容类型为JSON格式。

@Session

  • 在使用基于会话(session)的状态管理时(需要启用express-session中间件),@Session装饰器用于取出会话对象。
  • 会话对象可以用于存储用户相关的状态信息,如用户登录后的身份标识、购物车信息等,在不同的请求之间保持用户状态的连贯性。
  // main.ts
  app.use(
    session({
      secret: 'secretKey',
      cookie: { maxAge: 60000 },
    }),
  );

  // controller.ts
  @Get('session')
  setSession(@Session() session) {
    session.loggedIn = true;
    session.user = {
      name: 'zh',
    };
    return 'login后设置session';
  }
  @Get('/getSession')
  async getSession(@Session() session) {
    console.log('获取session', session);
    if (session.loggedIn) {
      return session.user;
    } else {
      return '暂未登录';
    }
  }

image.png

@HostParm

  • 在某些情况下,可能需要从请求的主机(host)部分获取参数。@HostParm装饰器用于取出主机里的参数。
  • 例如,如果主机名包含特定的标识或信息,可以通过这个装饰器获取并在业务逻辑中加以利用,不过这种情况相对较少见。
@Controller({
  path: 'api-test',
  host: ':x.0.0.1', //指定访问的域名规则。只有.0.0.1结尾的ip才可以访问
})

// 可以通过`@HhostParam`获取域名中x属性。
getUrlParam(
@HostParam() param: any,
@Ip() ipParams: string,
) {
console.log('获取get请求url param参数', param, ipParams);
return `获取get请求url param参数: ${id}`;
}

image.png

其实这个装饰器的作用可以使用@Req, @Request来代替,可以在请求对象中进行获取请求host。

@Req、@Request

  • 这两个装饰器都用于注入请求对象(request object)。请求对象包含了关于 HTTP 请求的所有信息,如请求方法、请求头、请求参数、请求体等。
  • 在控制器方法中注入请求对象后,可以根据具体需求获取和操作这些信息,例如获取请求的原始 IP 地址、检查请求的来源等。

@Res、@Response

  • 这两个装饰器用于注入响应对象(response object)。一旦注入了这个响应对象,Nest 就不会把控制器方法的返回值作为响应了(除非指定passthroughtrue
  • 注入响应对象后,可以直接操作响应,例如设置响应头、状态码、发送自定义的响应内容等,给予开发者更大的控制权,但也需要更多的手动操作来确保正确的响应处理。
@Get('getreqres')
getReqRes(@Req() req, @Res({passthrough: true}) res) {
    console.log('req, res', req, res);
    return '返回无效'; // 可以指定passthrough属性让返回值有效
}

@Next

  • 在中间件或者路由处理函数的链式调用中,@Next装饰器用于注入调用下一个处理函数(handler)的next方法。所以这个也会阻止路由处理方法的return返回响应。
  • 在中间件中,如果没有调用next方法,请求将不会继续向下传递,可能会导致后续的中间件或者路由处理函数无法执行;通过调用next,可以确保请求按照预定的顺序在中间件和路由处理函数之间流动。

@HttpCode

  • 在 NestJS 中,默认情况下,成功的请求会返回200状态码,失败的请求会根据具体情况返回不同的状态码。@HttpCode装饰器用于修改响应的状态码。不能设置到控制器上。
  • 例如,可以通过@HttpCode(201)在创建资源成功时返回201 Created状态码,以符合 HTTP 协议的语义。
@Get('getreqres')
@HttpCode(400)
getReqRes(@Req() req, @Res({ passthrough: true }) res) {
    console.log('req, res', req, res);
    return '返回有效,因为设置了passthrough';
}

image.png

@Redirect

  • 在某些情况下,需要将请求重定向到另一个 URL。@Redirect装饰器用于指定重定向的 URL。
  • 可以根据业务逻辑,例如用户登录成功后重定向到用户个人页面,通过这个装饰器实现重定向操作,同时还可以设置一些重定向相关的参数,如重定向的状态码等。
// 将当前路由重定向到掘金
@Redirect('http://juejin.cn', 302) // 可以指定重定向url和状态码
async getRedirect() {
}

@Render

  • 在 NestJS 中,@Render装饰器主要用于视图渲染相关的操作。当构建一个需要返回视图(例如 HTML 视图)而不是单纯的 JSON 数据或其他数据格式的应用时,@Render装饰器就会发挥作用。
  • 它通常与模板引擎(如 Hbs、Pug、EJS 等)结合使用,使得在控制器方法中可以方便地指定要渲染的视图模板,并传递相关的数据到视图中进行展示。
  // main.ts 这里我们使用hbs模板语法
  app.useStaticAssets(join(__dirname, '..', 'public'));
  app.setBaseViewsDir(join(__dirname, '..', 'views'));
  app.setViewEngine('hbs');
  
  // controller.ts
  @Get('/index')
  @Render('index') // 设置模板路径
  async getIndex() {
    console.log('设置后端渲染');
    return {
      data: '为模板提供的数据', // 为模板提供数据
    };
  }
  
  // views/index.hbs
  <h1>{{data.data}}</h1>

image.png

@Ip

  • 在 NestJS 中,@Ip装饰器用于在控制器方法中注入客户端的 IP 地址。这在需要根据客户端 IP 进行访问控制、日志记录、地理定位(结合外部 IP - to - 地理位置的服务)等操作时非常有用。
@Get()
getClientIp(@Ip() clientIp: string) {
    return `The client IP is: ${clientIp}`;
}

image.png

@Version

  • 在 NestJS 中,@Version装饰器用于版本控制,特别是在构建 API 时。它允许你为控制器或控制器方法指定版本信息,这样就可以轻松地管理和维护不同版本的 API 端点。
  • 随着应用的发展,API 可能会发生变化,通过版本控制可以确保旧版本的客户端仍然能够正常使用,同时支持新版本的功能开发。

如果我们想要让整个项目设置统一的版本我们可以在main.ts中统一设置。

  // 接口版本化管理
  app.enableVersioning({
    type: VersioningType.URI,
    defaultVersion: '1', // 设置默认版本,如果不在路由处理方法或者控制器中设置@Version,默认接口路径自带v[defaultVersion]
  });

image.png 如果想要单独设置某个控制器或者路由处理方法版本,我们可以使用@Version@Controller来覆盖提供的默认值。

// 路由处理方法
@Version('2')
getVersion2() {
    return 'version 2';
}

// 控制器
@Controller({
  path: 'provider-test',
  version: '1',
})

image.png 我们还可以设置指定一个版本请求不携带版本号。但是一般加以区分,还是会特别加上对应的版本请求的。

// 接口版本化管理
  app.enableVersioning({
    type: VersioningType.URI,
    // defaultVersion: '1', // 设置默认版本,如果不在路由处理方法或者控制器中设置version,默认接口路径自带v[defaultVersion]
    defaultVersion: [VERSION_NEUTRAL, '1', '2'], // // VERSION_NEUTRAL自然选择,如果路由没有设置版本,那么就匹配第一个符合的路由,如果设置版本,那就匹配version和路由
  });
  
  // 使用
  @Controller({
    path: 'provider-test',
    version: [VERSION_NEUTRAL, '1'],
  })
  @Version([VERSION_NEUTRAL, '1'])

image.png

@Bind

  • @Bind装饰器允许你在方法执行前对参数进行预处理或者将参数绑定到特定的上下文。它可以用于动态修改方法的参数值,或者在方法调用时注入额外的信息。
  • 例如,你可以使用 @Bind装饰器来将方法的参数绑定到请求对象的特定属性上,或者在方法执行前对参数进行一些转换或验证操作。

未详细讲解的装饰器我们在前面文章都介绍过,可以去看看Nest相关文章。

往期年度总结

往期文章

专栏文章

🔥如果此文对你有帮助的话,欢迎💗关注、👍点赞、⭐收藏✍️评论,    支持一下博主~

公众号:全栈追逐者,不定期的更新内容,关注不错过哦!