本章将学习如何自定义装饰器 以及如何将多个装饰器合并成一个
首先先创建一个项目:
nest new custom-decorator -p npm
接着我们创建一个装饰器:
nest g decorator test --flat
我们创建一个Guard 用于使用 reflector 来取 metadata:
nest g guard test --flat --no-spec
修改test.guard.ts 里的代码:
import { CanActivate, ExecutionContext, Inject, Injectable } from '@nestjs/common';
import { Reflector } from '@nestjs/core';
import { Observable } from 'rxjs';
@Injectable()
export class TestGuard implements CanActivate {
@Inject(Reflector)
private reflector: Reflector;
canActivate(
context: ExecutionContext,
): boolean | Promise<boolean> | Observable<boolean> {
console.log(this.reflector.get('test', context.getHandler()));
return true;
}
}
作用是打印 this.reflector.get(‘test’, context.getHandler())
接着在Controller 里进行使用
import { Controller, Get, SetMetadata, UseGuards } from '@nestjs/common';
import { AppService } from './app.service';
import { TestGuard } from './test.guard';
@Controller()
export class AppController {
constructor(private readonly appService: AppService) { }
@Get()
@SetMetadata('test', 'admin')
@UseGuards(TestGuard)
getHello(): string {
return this.appService.getHello();
}
}
启动服务:
npm run start:dev
访问 http://localhost:3000 可以看到打印的 metadata
不同 metadata 有不同的业务场景:
- 身份验证和授权:可以使用metadata来标记需要身份验证或授权的路由和方法,然后根据标记的信息进行相应的操作,例如检查用户的权限和角色信息。
- 表单验证:可以使用metadata来标记需要验证的表单字段,然后根据标记的信息进行相应的验证操作,例如检查字段的类型、长度、格式等。
- 日志记录:可以使用metadata来标记需要记录日志的方法和路由,然后根据标记的信息进行日志的记录,例如记录方法的调用时间、参数和返回值。
- 缓存管理:可以使用metadata来标记需要缓存的方法和路由,然后根据标记的信息进行缓存的管理,例如设置缓存的过期时间和清除缓存的策略。
- 路由拦截和转发:可以使用metadata来标记需要进行路由拦截和转发的方法和路由,然后根据标记的信息进行相应的操作,例如拦截路由请求进行权限校验或者转发路由请求到其他服务。
但是 直接使用@SetMetadata 来设置太原始了,所以可以使用之前创建的装饰器 封装一层。
修改 app.controller.ts 新增getTest函数 使用 自定义的Test装饰器
import { Controller, Get, SetMetadata, UseGuards } from '@nestjs/common';
import { AppService } from './app.service';
import { TestGuard } from './test.guard';
import { Test } from './test.decorator';
@Controller()
export class AppController {
constructor(private readonly appService: AppService) { }
@Get()
@SetMetadata('test', 'admin')
@UseGuards(TestGuard)
getHello(): string {
return this.appService.getHello();
}
@Get('test')
@Test('admin')
@UseGuards(TestGuard)
getTest(): string {
return this.appService.getHello();
}
}
访问 http://localhost:3000/test可以看到打印的 metadata
我们也可以将多给装饰器 合成一个使用
创建一个装饰器 demo
nest g decorator demo --flat
修改装饰器代码如下:
import { Get, SetMetadata, UseGuards, applyDecorators } from '@nestjs/common';
import { Test } from './test.decorator';
import { TestGuard } from './test.guard';
export const Demo = (path, role) => {
return applyDecorators(
Get(path),
Test(role),
UseGuards(TestGuard)
)
}
在Controller 里新增加test2接口
@Demo('test2', 'admin')
getTest2(): string {
return this.appService.getHello();
}
访问 http://localhost:3000/test2 可以看到功能也能正常使用
此时 三个接口的装饰器实现的效果都是一样的
除了接口层面 我们也可以自定义参数装饰器:
再次创建一个装饰器:
nest g decorator param --flat
修改 param.decorator.ts 代码如下
import { ExecutionContext, SetMetadata, createParamDecorator } from '@nestjs/common';
export const Param =createParamDecorator(
(data:string,ctx:ExecutionContext)=>{
return 'param'
}
)
在app.controller.ts 中 新增接口test3 使用上面定义的 @Param() 装饰器
@Get('test3')
getTest3(@Param() param): string {
return 'param';
}
访问 http://localhost:3000/test3 可以看到就是装饰器 Param的返回值
createParamDecorator 的回调函数可以接收以下参数:
- data:表示由装饰器传递的数据。这个参数是可选的。
- ctx:表示当前请求的上下文对象,可以通过 @Req() 装饰器获取。
通过这个函数 我们自己也可以实现 内置的@Param、@Query、@Ip、@Headers 等装饰器
我们来尝试一下,实现一个获取请求头信息的装饰器
创建装饰器:
nest g decorator myHeaders --flat
修改 my-headers.decorator.ts 代码如下:
import { ExecutionContext, SetMetadata, createParamDecorator } from '@nestjs/common';
/**
* 创建一个参数装饰器,用于获取HTTP请求的头部信息。
*
* @param key 需要获取的头部信息的键。如果提供,则只返回对应键的头部信息;如果不提供,则返回所有头部信息。
* @param ctx 操作的上下文环境,用于切换到HTTP上下文并获取请求对象。
* @returns 如果提供了键,则返回对应键的头部信息;否则返回所有头部信息。
*/
export const MyHeaders = createParamDecorator(
(key: string, ctx: ExecutionContext) => {
// 切换到HTTP上下文并获取请求对象
const request: Request = ctx.switchToHttp().getRequest();
// 根据是否提供了键来返回对应的头部信息或所有头部信息
return key ? request.headers[key.toLowerCase()] : request.headers;
},
);
这个函数创建了一个参数装饰器MyHeaders,用于在NestJS中获取HTTP请求的头部信息。使用createParamDecorator函数创建装饰器,并接收一个键key和上下文环境ctx作为参数。在装饰器函数内部,通过ctx.switchToHttp().getRequest()切换到HTTP上下文并获取请求对象。然后根据是否提供了键key来返回对应的头部信息或所有头部信息。如果提供了键,则返回对应键的头部信息;否则返回所有头部信息。
修改 app.controller.ts 新增接口:
@Get('test4')
getTest4(@MyHeaders('Accept') headers1, @MyHeaders('Accept') headers2): string {
console.log('headers1', headers1);
console.log('headers2', headers2);
return 'param';
}
访问 http://localhost:3000/test4 可以看到控制台返回了请求头的 Accept信息