Skip to content

自定义装饰器

自定义方法装饰器#

除了NestJS给我们提供的众多装饰器之外,其实我们还可以自定义装饰器,我们来测试一下,首先还是生成测试项目:

Terminal window
nest new custom-decorator -g -p npm

然后直接通过命令生成自定义装饰器文件:

nest g decorator custom --flat

文件内就有自动生成的一个函数:

import { SetMetadata } from '@nestjs/common'
export const Custom = (...args: string[]) => SetMetadata('custom', args)

Custom这个自动生成的函数其实就是一个自定义方法装饰器,我们稍微改一个名字试试

import { SetMetadata } from '@nestjs/common'
export const SetUser = (...args: string[]) => SetMetadata('SetUser', args)

然后把这个装饰器用在路由方法之上。

@Controller()
export class AppController {
constructor(private readonly appService: AppService) {}
@Get('hello1')
@SetUser('admin', 'user')
getHello(): string {
return this.appService.getHello()
}
}

由于是用在Controller上的,要取出SetUser装饰器上的值,我们可以使用守卫

创建一个守卫:

Terminal window
nest g guard custom --flat --no-spec

在guard中使用 reflector 来取 metadata元数据:

import {
CanActivate,
ExecutionContext,
Inject,
Injectable,
} from '@nestjs/common'
import { Reflector } from '@nestjs/core'
import { Observable } from 'rxjs'
@Injectable()
export class CustomGuard implements CanActivate {
@Inject(Reflector)
private reflector: Reflector
canActivate(
context: ExecutionContext
): boolean | Promise<boolean> | Observable<boolean> {
const users = this.reflector.get('SetUser', context.getHandler())
console.log(users)
return true
}
}

在Controller上添加上守卫

@Get('hello1')
@SetUser('admin', 'user')
@UseGuards(CustomGuard)
getHello(): string {
return this.appService.getHello();
}

当访问路由之后,就会打印通过装饰器设置的值

image-20250212173620442

自定义参数装饰器#

我们可以通过自定义装饰器获取请求中的特定属性:

import {
createParamDecorator,
ExecutionContext,
SetMetadata,
} from '@nestjs/common'
export const SetUser = (...args: string[]) => SetMetadata('SetUser', args)
export const GetUser = createParamDecorator(
(data: string, ctx: ExecutionContext) => {
return 'jack'
}
)

在Controller中简单处理一下:

@Get('hello2')
getHello2(@GetUser() u: string): string {
console.log(u);
return this.appService.getHello();
}

返回结果:

image-20250212173833476

实际上,data: string, ctx: ExecutionContext都是有用的。

data 很明显就是传入的参数,而 ExecutionContext 前面用过,可以取出 requestresponse 对象。

比如我们写成下面的样子:

export const GetUser = createParamDecorator(
(data: string, ctx: ExecutionContext) => {
const request = ctx.switchToHttp().getRequest()
console.log(request)
console.log(data)
return request.query[data]
}
)

在Controller中进行处理

@Get('hello2')
getHello2(@GetUser('name') u: string): string {
console.log('---', u);
return this.appService.getHello();
}

页面访问:

image-20250212175527647

现在看一下打印信息,就知道data和ExecutionContext的作用了

image-20250212174923332

很明显,data其实就是装饰器中声明的参数名,而且既然我们可以获取request对象,那么我们完全可以复刻NestJS自带的一些参数装饰器,比如@Param@Query@Headers等等

自定义的MyHeaders#

export const MyHeaders = createParamDecorator(
(data: string, ctx: ExecutionContext) => {
const request: Request = ctx.switchToHttp().getRequest()
return data ? request.headers[data.toLowerCase()] : request.headers
}
)

Controller中使用和@Headers用法一模一样

@Get('hello3')
getHello3(@Headers('host') header1, @MyHeaders('host') header2): string {
console.log(header1);
console.log(header2);
return this.appService.getHello();
}

自定义MyQuery#

export const MyQuery = createParamDecorator(
(data: string, ctx: ExecutionContext) => {
const request: Request = ctx.switchToHttp().getRequest()
return request.query[data]
}
)

Controller中使用和@Query用法一模一样

@Get('hello4')
getHello4(
@Query('username') username: string,
@MyQuery('age') age: number,
): string {
console.log(username);
console.log(age);
return this.appService.getHello();
}

image-20250212180036943

image-20250212180100326

自定义MyParam#

export const MyParam = createParamDecorator(
(data: string, ctx: ExecutionContext) => {
const request: Request = ctx.switchToHttp().getRequest()
return data ? request.params[data] : request.params
}
)

Controller上访问

@Get(':username')
getHello5(
@Param('username') username1: string,
@MyParam('username') username2: string,
): string {
console.log(username1);
console.log(username2);
return this.appService.getHello();
}

image-20250212180558917

image-20250212180624459

装饰器合并#

如果我们觉得装饰太多,也可以自定义装饰器将众多装饰器合并为一个,比如之前的装饰器:

image-20250213120336654

我们可以使用applyDecorators组合多个装饰器:

export function MyCombinedDecorator(path: string, ...user: string[]) {
return applyDecorators(Get(path), SetUser(...user), UseGuards(CustomGuard))
}

然后用这一个装饰器,去替换掉之前的三个装饰器就行了:

// @Get('hello1')
// @SetUser('admin', 'user')
// @UseGuards(CustomGuard)
@MyCombinedDecorator('hello1', 'admin', 'user')
getHello(): string {
return this.appService.getHello();
}

自定义 class 装饰器#

类装饰器需要结合着applyDecorators一起来使用:

export function MyController(path: string, metaData: string) {
return applyDecorators(Controller(path), SetMetadata('MyClass', metaData))
}

我们直接在AppController上使用:

image-20250213122051718

由于在CustomGuard上我们有拦截,所以为了获取MyController上的metaData数据,我们可以在CustomGuard稍微处理一下:

@Injectable()
export class CustomGuard implements CanActivate {
@Inject(Reflector)
private reflector: Reflector;
canActivate(
context: ExecutionContext,
): boolean | Promise<boolean> | Observable<boolean> {
const metaData = this.reflector.get('MyClass', context.getClass());
console.log(metaData);
const users = this.reflector.get('SetUser', context.getHandler());
console.log(users);
return true;
}
}