热搜:前端 nest neovim nvim

神光《Nest 通关秘籍》学习总结-深入理解五花八门的装饰器

lxf2023-06-16 02:59:44

最近在学习神光大神的《Nest通关秘籍》,该小册主要包含下面这些内容:

神光《Nest 通关秘籍》学习总结-深入理解五花八门的装饰器 想购买的可以点击《传送门》。

接下来的日子里,我将更新一系列的学习笔记。感兴趣的可以关注我的专栏《Nest 通关秘籍》学习总结。

特别申明:本系列文章已经经过作者本人的允许。 大家也不要想着白嫖,我的笔记只是个人边学习边记录的,不是很完整,大家想要深入学习还是要自己去购买原版小册。

本章我们来学习nest中的五花八门的装饰器。

神光《Nest 通关秘籍》学习总结-深入理解五花八门的装饰器

1.@module 声明Module

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

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

2.@Controller声明 Controller

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();
  }
}

3.@Injectable声明 Provider

import { Injectable } from '@nestjs/common';

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

4.@Inject指定注入的 token

...
@Controller()
export class AppController {
  @Inject('app_service')
  private readonly appService: AppService;

  @Get()
  getHello(): string {
    return this.appService.getHello();
  }
}
// or
...
@Controller()
export class AppController {
  constructor(@Inject('app_service') private readonly appService: AppService) {}
  @Get()
  getHello(): string {
    return this.appService.getHello();
  }
}
...

5.@Optional声明为可选的

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

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

  @Optional()
  @Inject('xiumubai')
  private readonly xiumubai: Record<string, any>;

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

6.@Global 把它声明为全局

import { Global, Module } from '@nestjs/common';
import { AaaService } from './aaa.service';
import { AaaController } from './aaa.controller';

@Global()
@Module({
  controllers: [AaaController],
  providers: [AaaService],
  exports: [AaaService],
})
export class AaaModule {}

7.@Catch 来指定处理的异常

我们在aaa.filter.ts中通过@Catch处理抛出的未捕获异常

import {
  ArgumentsHost,
  Catch,
  ExceptionFilter,
  HttpException,
} from '@nestjs/common';
import { Response } from 'express';

@Catch(HttpException)
export class AaaFilter implements ExceptionFilter {
  catch(exception: HttpException, host: ArgumentsHost) {
    const response: Response = host.switchToHttp().getResponse();
    response.status(exception.getStatus()).json({
      msg: exception.message,
    });
  }
}

然后通过 @UseFilters 应用到 handler 上:

import {
  Controller,
  Get,
  UseFilters,
  HttpStatus,
  HttpException,
} from '@nestjs/common';
import { AaaService } from './aaa.service';
import { AaaFilter } from './aaa.filter';

@Controller('aaa')
export class AaaController {
  constructor(private readonly aaaService: AaaService) {}


  @Get()
  @UseFilters(AaaFilter)
  findAll() {
    throw new HttpException('xxx', HttpStatus.BAD_REQUEST);
    return this.aaaService.findAll();
  }
}

当访问,可以看到抛出的异常:

神光《Nest 通关秘籍》学习总结-深入理解五花八门的装饰器

除了 filter 之外,interceptor、guard、pipe 也是这样用。

8.请求参数相关的装饰器

8.1 @Post

post请求可以通过@Body来获取body的部分:

@Post()
create(@Body() createAaaDto: CreateAaaDto) {
  console.log(createAaaDto);
  return this.aaaService.create(createAaaDto);
}

body参数的结构体一般使用dto来声明,nest 会实例化一个 dto 对象:

export class CreateAaaDto {
  age: number;
  name: string;
}

发送一个post请求:

神光《Nest 通关秘籍》学习总结-深入理解五花八门的装饰器

可以看到控制台接收到了一个body参数:

神光《Nest 通关秘籍》学习总结-深入理解五花八门的装饰器

除了 @Post 外,还可以用 @Get@Put@Delete@Patch@Options@Head 装饰器分别接受 getputdeletepatchoptionshead 请求。

9.@SetMetadata指定metadata

// aaa.controller.ts

import {
  Controller,
  Get,
  SetMetadata,
  UseGuards,
} from '@nestjs/common';
import { AaaService } from './aaa.service';
import { CreateAaaDto } from './dto/create-aaa.dto';
import { UpdateAaaDto } from './dto/update-aaa.dto';
import { AaaGuard } from './aaa.guid';

@Controller('aaa')
@SetMetadata('roles', ['user'])
export class AaaController {
  constructor(private readonly aaaService: AaaService) {}


  @Get()
  @UseGuards(AaaGuard)
  @SetMetadata('roles', ['admin'])
  findAll() {
    return this.aaaService.findAll();
  }

}
// aaa.guard.ts
import {
  CanActivate,
  ExecutionContext,
  Inject,
  Injectable,
} from '@nestjs/common';
import { Reflector } from '@nestjs/core';
import { Observable } from 'rxjs';

@Injectable()
export class AaaGuard implements CanActivate {
  @Inject(Reflector)
  private readonly reflector: Reflector;

  canActivate(
    context: ExecutionContext,
  ): boolean | Promise<boolean> | Observable<boolean> {
    const classMetaDatas = this.reflector.get('roles', context.getClass());
    const methodMetaDatas = this.reflector.get('roles', context.getHandler());

    console.log(classMetaDatas, methodMetaDatas);
    return true;
  }
}

神光《Nest 通关秘籍》学习总结-深入理解五花八门的装饰器

10.@Headers获取请求头

@Get('/header')
header(
  @Headers('Accept') accept: string,
  @Headers() headers: Record<string, any>,
) {
  console.log('accept', accept);
  console.log('headers', headers);
}

神光《Nest 通关秘籍》学习总结-深入理解五花八门的装饰器

11.@Session拿到session对象

要使用 session 需要安装一个 express 中间件:

pnpm install express-session

在 main.ts 里引入并启用:

import { NestFactory } from '@nestjs/core';
import { AppModule } from './app.module';
import * as session from 'express-session';

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

  app.use(
    session({
      secret: 'xiumu',
      cookie: { maxAge: 1000 },
    }),
  );
  await app.listen(3000);
}
bootstrap();

发送一个请求

神光《Nest 通关秘籍》学习总结-深入理解五花八门的装饰器

会返回 Set-Cookie 的响应头,设置了 cookie,包含 sid 也就是 sesssionid。

12.@HostParam获取域名部分的参数

import {
  Controller,
  Get,
  HostParam,
} from '@nestjs/common';
import { BbbService } from './bbb.service';

@Controller({ host: ':host.0.0.1', path: 'bbb' })
export class BbbController {
  constructor(
    private readonly bbbService: BbbService,
  ) {}

  @Get('host')
  host(@HostParam('host') host) {
    return host;
  }
}

这样只有通过xxx.0.0.1访问的才能通过。

13.@Req获取请求参数

@Get('req')
  getBbbb(@Req() req: Request) {
    console.log('req', req.hostname, req.url);
  }

神光《Nest 通关秘籍》学习总结-深入理解五花八门的装饰器

14.自定义方法装饰器

之前我们用@SetMetadata设置了一些角色数据,然后通过Guard再判断逻辑。现在我们把@SetMetaData来自定义一下:

// bbb.decorator.ts
import { SetMetadata } from '@nestjs/common';

export const Bbb = (...args: string[]) => SetMetadata('aaa', args);

然后在Controller中使用:

import {
  Controller,
  Get,
  HostParam,
  UseGuards,
} from '@nestjs/common';
import { Request } from 'express';
import { BbbService } from './bbb.service';
import { Bbb } from './bbb.decorator';
import { BbbGuard } from './bbb.guard';

@Controller({ host: ':host.0.0.1', path: 'bbb' })
export class BbbController {
  constructor(
    private readonly bbbService: BbbService,
  ) {}

  @Get()
  @UseGuards(BbbGuard)
  @Bbb('admin')
  findAll() {
    return this.aaaService.findAll();
  }
}

这样我们可以使用上面的方式来使用这个装饰器。测试一下:

神光《Nest 通关秘籍》学习总结-深入理解五花八门的装饰器

现在我们这里的装饰器有点多:

神光《Nest 通关秘籍》学习总结-深入理解五花八门的装饰器

我们可以把这三个装饰合并成一个使用:

// merge.decorator.ts
import { applyDecorators, Get, UseGuards } from '@nestjs/common';
import { Bbb } from './bbb.decorator';
import { BbbGuard } from './bbb.guard';

export function Merge(path, role) {
  return applyDecorators(Get(path), Bbb(role), UseGuards(BbbGuard));
}

使用的时候可以这样使用:

import {
  Controller,
  Get,
  HostParam,
  Req,
  UseGuards,
} from '@nestjs/common';
import { Request } from 'express';
import { BbbService } from './bbb.service';
import { AaaService } from 'src/aaa/aaa.service';
import { Bbb } from './bbb.decorator';
import { BbbGuard } from './bbb.guard';
import { Merge } from './merge.decorator';
@Controller({ host: ':host.0.0.1', path: 'bbb' })
export class BbbController {
  constructor(
    private readonly bbbService: BbbService,
    private readonly aaaService: AaaService,
  ) {}

  @Get()
  @UseGuards(BbbGuard)
  @Bbb('admin')
  findAll() {
    return this.aaaService.findAll();
  }

  @Merge('hello2', 'admin')
  getHello3(): string {
    return 'this is merge decorator';
  }
}

访问http://127.0.0.1:3000/bbb/hello2:

神光《Nest 通关秘籍》学习总结-深入理解五花八门的装饰器

神光《Nest 通关秘籍》学习总结-深入理解五花八门的装饰器

效果和之前一样。

15.自定义参数装饰器

先来写一个参数装饰器:

// ccc.decorator.ts
import { createParamDecorator, ExecutionContext } from '@nestjs/common';

export const Ccc = createParamDecorator(
  (data: string, ctx: ExecutionContext) => {
    return 'ccc';
  },
);

这样使用:

import { Controller, Get } from '@nestjs/common';
import { CccService } from './ccc.service';
import { Ccc } from './ccc.decorator';
@Controller('ccc')
export class CccController {
  constructor(private readonly cccService: CccService) {}

  @Get('arg')
  getArg(@Ccc() c) {
    return c;
  }
}

神光《Nest 通关秘籍》学习总结-深入理解五花八门的装饰器

这里拿到的c就是参数装饰器的返回值。

在我们自定义的装饰器中,data 很明显就是传入的参数,而 ExecutionContext 前面用过,可以取出 request、response 对象。

这样,那些内置的 @Param、@Query、@Ip、@Headers 等装饰器,我们也可以自己实现了。

下面我们实现一个Header装饰器:

import { createParamDecorator, ExecutionContext } from '@nestjs/common';
import { Request } from 'express';

export const MyHeaders = createParamDecorator(
  (key: string, ctx: ExecutionContext) => {
    const request: Request = ctx.switchToHttp().getRequest();
    return key ? request.headers[key] : request.headers;
  },
);
@Get('my')
getHeader(@Headers('Accept') headers1, @MyHeaders('accept') headers2) {
  console.log('headers1', headers1);
  console.log('headers2', headers2);
}

这里我们的@@MyHeaders@Headers具有同样的效果,都能拿到Accept的值。

注意:@MyHeaders()接受的参数是accept,小写开头。

神光《Nest 通关秘籍》学习总结-深入理解五花八门的装饰器

下面我们再实现一个query装饰器。

export const MyQuery = createParamDecorator(
  (key: string, ctx: ExecutionContext) => {
    const request: Request = ctx.switchToHttp().getRequest();
    return request.query[key];
  },
);
 @Get('query')
getHello6(@Query('aaa') aaa, @MyQuery('bbb') bbb) {
  console.log('aaa', aaa);
  console.log('bbb', bbb);
}

我们通过断点调试的方式,来看看:

神光《Nest 通关秘籍》学习总结-深入理解五花八门的装饰器

可以看到在request中,可以拿到query返回的参数,我们根据key值,取到返回了。

16.自定义class装饰器

同样的,class的装饰器也可以自定义:

// myHeaders.decorator.ts
export const MyClass = () => Controller('class');
import { Controller, Get, Headers, Query } from '@nestjs/common';
import { CccService } from './ccc.service';
import { Ccc } from './ccc.decorator';
import { MyHeaders, MyQuery, MyClass } from './myHeaders.decorator';
// @Controller('ccc')
@MyClass()
export class CccController {
  constructor(private readonly cccService: CccService) {}

  @Get('arg')
  getArg(@Ccc() c) {
    return c;
  }

  @Get('my')
  getHeader(@Headers('Accept') headers1, @MyHeaders('Accept') headers2) {
    console.log('headers1', headers1);
    console.log('headers2', headers2);
  }

  @Get('query')
  getHello6(@Query('aaa') aaa, @MyQuery('bbb') bbb) {
    console.log('aaa', aaa);
    console.log('bbb', bbb);
  }
}

我们把@Controller('ccc')替换成了@MyClass(),然后访问一下http://127.0.0.1:3000/class/query?aaa=aaa&bbb=bbb,同样也是没问题的。

总结:

内置装饰器不够用的时候,或者想把多个装饰器合并成一个的时候,都可以自定义装饰器。

方法的装饰器就是传入参数,调用下别的装饰器就好了,比如对 @SetMetadata 的封装。

如果组合多个方法装饰器,可以使用 applyDecorators api。

还可以通过 createParamDecorator 来创建参数装饰器,它能拿到 ExecutionContext,进而拿到 reqeust、response,可以实现很多内置装饰器的功能,比如 @Query、@Headers 等装饰器。

本网站是一个以CSS、JavaScript、Vue、HTML为核心的前端开发技术网站。我们致力于为广大前端开发者提供专业、全面、实用的前端开发知识和技术支持。 在本网站中,您可以学习到最新的前端开发技术,了解前端开发的最新趋势和最佳实践。我们提供丰富的教程和案例,让您可以快速掌握前端开发的核心技术和流程。 本网站还提供一系列实用的工具和插件,帮助您更加高效地进行前端开发工作。我们提供的工具和插件都经过精心设计和优化,可以帮助您节省时间和精力,提升开发效率。 除此之外,本网站还拥有一个活跃的社区,您可以在社区中与其他前端开发者交流技术、分享经验、解决问题。我们相信,社区的力量可以帮助您更好地成长和进步。 在本网站中,您可以找到您需要的一切前端开发资源,让您成为一名更加优秀的前端开发者。欢迎您加入我们的大家庭,一起探索前端开发的无限可能!