神光《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();
}
}
当访问,可以看到抛出的异常:
除了 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请求:
可以看到控制台接收到了一个body参数:
除了 @Post
外,还可以用 @Get
、@Put
、@Delete
、@Patch
、@Options
、@Head
装饰器分别接受 get
、put
、delete
、patch
、options
、head
请求。
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;
}
}
10.@Headers
获取请求头
@Get('/header')
header(
@Headers('Accept') accept: string,
@Headers() headers: Record<string, any>,
) {
console.log('accept', accept);
console.log('headers', headers);
}
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();
发送一个请求
会返回 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);
}
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();
}
}
这样我们可以使用上面的方式来使用这个装饰器。测试一下:
现在我们这里的装饰器有点多:
我们可以把这三个装饰合并成一个使用:
// 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:
效果和之前一样。
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;
}
}
这里拿到的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
,小写开头。
下面我们再实现一个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);
}
我们通过断点调试的方式,来看看:
可以看到在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 等装饰器。
转载自:https://juejin.cn/post/7243609307856928827