🌙 装饰器简介
ES2016 装饰器是一个返回函数的表达式,可以接受目标、名称和属性描述符作为参数。您通过在装饰器前加上 @
字符并放置在您要装饰的内容顶部来应用它。装饰器可以为类、方法或属性定义。
装饰器本质就是函数,主要用于:
- 装饰类:
@annotation
class MyClass { }
function annotation(target, name, descriptor) {
target.annotated = true;
}
2
3
4
5
6
- 装饰方法或属性
class MyClass {
@readonly
method() { }
}
function readonly(target, name, descriptor) {
// descriptor对象原来的值如下
// {
// value: specifiedFunction,
// enumerable: false,
// configurable: true,
// writable: true
// };
descriptor.writable = false;
return descriptor;
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
readonly
装饰器接收三个参数:target
,name
,和descriptor
。target
是类的原型对象,name
是方法的名称,descriptor
是该方法的属性描述符对象。- 在装饰器函数中,将
descriptor.writable
设置为false
,这将禁止对方法进行写操作,使其成为只读方法。 - 返回修改后的属性描述符对象
descriptor
。
Nest 就是围绕装饰器的语言特性构建的。
🌙 装饰器类别
装饰器主要分为:类装饰器、方法装饰器、参数装饰器、属性装饰器、访问器装饰器。
- 类装饰器:应用于类声明之前,用于修改类的行为或元数据。
- 作用对象:类(classes), 函数签名:
type ClassDecorator = <TFunction extends Function>(target: TFunction) => TFunction | void;
- 促发时机:在类实例化前执行(实例化阶段)
- 典型用途:
- 自动注册
- 自动注入
- 自动加载
import { Module } from '@nestjs/common';
import { CatsController } from './cats.controller';
// 类装饰器
@Module({
controllers: [CatsController]
})
export class CatsModule {}
2
3
4
5
6
7
8
- 方法装饰器:应用于方法声明之前,用于修改方法的行为或元数据。
- 作用对象:方法(methods),函数签名:
type MethodDecorator = <T>(target: Object, propertyKey: string | symbol, descriptor: TypedPropertyDescriptor<T>) => TypedPropertyDescriptor<T> | void;
- 促发时机:在方法执行前执行(非实例化阶段)
- 典型用途:
- 权限验证
- 缓存
- 请求拦截
- 请求日志
- 请求限流
import { Controller, Get } from '@nestjs/common';
@Controller('cats')
export class CatsController {
// 方法装饰器
@Get()
findAll() {}
}
2
3
4
5
6
7
- 参数装饰器:应用于方法参数声明之前,用于修改参数的行为或元数据。
- 作用对象:方法参数(method parameters),函数签名:
type ParameterDecorator = (target: Object, propertyKey: string | symbol, parameterIndex: number) => void;
- 促发时机:在方法执行前执行(非实例化阶段)
- 典型用途:
- 参数验证
- 参数转换
- OpenAPI/Swagger 文档生成
import { Controller, Get, Param } from '@nestjs/common';
@Controller('cats')
export class CatsController {
// @Param: 参数装饰器
@Get(':id')
findOne(@Param('id') id: string) {
return `This action returns a #${id} cat`;
}
}
2
3
4
5
6
7
8
9
- 属性装饰器:应用于属性声明之前,用于修改属性的行为或元数据。
- 作用对象:类属性(class properties),函数签名:
type PropertyDecorator = (target: Object, propertyKey: string | symbol) => void;
- 触发时机:在类定义时执行(非实例化阶段)
- 典型用途:
- 添加元数据(Metadata)
- 参数验证
- 属性转换
- OpenAPI/Swagger 文档生成
import { IsString, IsInt, IsOptional } from 'class-validator';
class CreateCatDto {
@IsString() // 验证字符串类型
name: string;
@IsInt() // 验证整型
age: number;
@IsOptional() // 允许字段可选
breed?: string;
}
2
3
4
5
6
7
8
9
10
11
12
13
- 访问器装饰器:应用于类的访问器声明之前,用于修改访问器行为的元数据。
- 作用对象:类访问器(class accessors),函数签名:
type AccessorDecorator = <T>(target: Object, propertyKey: string | symbol, descriptor: TypedPropertyDescriptor<T>) => TypedPropertyDescriptor<T> | void;
- 促发时机:在访问器定义时执行(非实例化阶段)
- 典型用途:
- 缓存装饰器
class TMac{
private _name:string;
constructor(name:string){
this._name = name;
}
// getter访问器
get name(){
return this._name
}
// setter访问器
@visitDecorator
set name(name:string){
this._name = name;
}
}
//访问器装饰器
function visitDecorator(target:any,key:string,descriptor:PropertyDescriptor){
descriptor.writable = false;
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
不允许同时装饰一个成员的get和set访问器。取而代之的是,一个成员的所有装饰的必须应用在文档顺序的第一个访问器上。这是因为,在装饰器应用于一个属性描述符时,它联合了get和set访问器,而不是分开声明的。简而言之:上述代码中name方法实际为一个属性,在setter写装饰器和在getter上写装饰器最终的效果都是一样的,两个上边都写,就会造成重复。
🌙 装饰器执行顺序
方法装饰器、属性装饰器、访问器装饰器,按照从上到下的顺序执行(根据定义的位置),优先于 类装饰器执行, 即类装饰器最后执行。
参数装饰器优先于方法装饰器执行。
如果存在多个装饰器,按照 **从下到上(从右到左)**的 顺序执行。
@classDecorator1
@classDecorator2
export class MyClass {
private name: string;
constructor(name: string) {
this.name = name
}
@methodDecorator1
@methodDecorator2
method(@paramDecorator1 @paramDecorator2 param: string) {
console.log('method', param);
}
@propertyDecorator1
@propertyDecorator2
property: string = 'property';
@accessorDecorator1
@accessorDecorator2
get accessor() {
return 'accessor';
}
}
function classDecorator1(target: Function) {
console.log('classDecorator1', target);
}
function classDecorator2(target: Function) {
console.log('classDecorator2', target);
}
function methodDecorator1(target: Object, name: string, descriptor: PropertyDescriptor) {
console.log('methodDecorator1', target, name, descriptor);
return descriptor;
}
function methodDecorator2(target: Object, name: string, descriptor: PropertyDescriptor) {
console.log('methodDecorator2', target, name, descriptor);
return descriptor;
}
function paramDecorator1(target: Object, name: string, paramIndex: number) {
console.log('paramDecorator1', target, name, paramIndex);
}
function paramDecorator2(target: Object, name: string, paramIndex: number) {
console.log('paramDecorator2', target, name, paramIndex);
}
function propertyDecorator1(target: Object, name: string) {
console.log('propertyDecorator1', target, name);
}
function propertyDecorator2(target: Object, name: string) {
console.log('propertyDecorator2', target, name);
}
function accessorDecorator1(target: Object, name: string, descriptor: PropertyDescriptor) {
console.log('propertyDecorator2', target, name, descriptor);
return descriptor;
}
function accessorDecorator2(target: Object, name: string, descriptor: PropertyDescriptor) {
console.log('propertyDecorator2', target, name, descriptor);
return descriptor;
}
const myClass = new MyClass('myClass')
myClass.method('method params')
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
输出结果:
paramDecorator2 {} method 0
paramDecorator1 {} method 0
methodDecorator2 {} method {
value: [Function: method],
writable: true,
enumerable: false,
configurable: true
}
methodDecorator1 {} method {
value: [Function: method],
writable: true,
enumerable: false,
configurable: true
}
propertyDecorator2 {} property
propertyDecorator1 {} property
accessorDecorator2 {} accessor {
get: [Function: get accessor],
set: undefined,
enumerable: false,
configurable: true
}
accessorDecorator1 {} accessor {
get: [Function: get accessor],
set: undefined,
enumerable: false,
configurable: true
}
classDecorator2 [class MyClass]
classDecorator1 [class MyClass]
method method params
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
装饰器在编译时运行,因此它们不能用于运行时动态创建和修改代码。 装饰器不能用于运行时动态创建和修改代码。
🌙 nestjs 常用装饰器
🌙 核心架构装饰器
🌙 1. 模块定义
@Module
装饰器:用于定义一个模块。
@Module({
imports: [OtherModule], // 导入依赖模块
controllers: [CatsController],
providers: [CatsService], // 服务/管道/守卫等
exports: [CatsService] // 暴露给其他模块
})
export class AppModule {}
2
3
4
5
6
7
8
🌙 2. 服务标识
@Injectable
装饰器:用于定义一个可注入的服务类。
@Injectable() // 标记可注入类
export class CatsService {}
2
🌙 控制器装饰器
@Controller
装饰器:用于定义一个控制器。
🌙 1. 路由定义
@Get
、@Post
、@Put
等 HTTP 方法装饰器:用于定义路由处理方法和路由路径。
@Controller('cats') // 基础路由路径
export class CatsController {
@Get() // GET /cats
@Post() // POST /cats
@Put(':id') // PUT /cats/:id
@Delete(':id') // DELETE /cats/:id
@Patch(':id') // PATCH /cats/:id
@Options() // OPTIONS 请求
}
2
3
4
5
6
7
8
9
🌙 2. 请求处理
@Header('Cache-Control', 'none') // 设置响应头
@HttpCode(201) // 自定义状态码
@Render('index.html') // 服务端渲染
@Redirect('/new-url', 301) // 重定向
2
3
4
🌙 参数提取装饰器
🌙 1. 请求数据获取
@Param
、@Query
、@Body
等参数装饰器:用于获取请求中的参数。
@Param('id') // 路由参数 → /cats/:id
@Query('page') // 查询参数 → ?page=1
@Body() // 请求体 → POST/PUT
@Headers('authorization') // 请求头
2
3
4
🌙 2. 上下文对象
@Request() req: FastifyRequest // 底层请求对象
@Response() res: FastifyReply // 底层响应对象
@Ip() clientIP: string // 客户端 IP
@HostParam() domain: string // 主机名参数
2
3
4
5
🌙 功能增强装饰器
🌙 1. 安全控制
@UseGuards
装饰器:用于为路由或控制器指定守卫。
@UseGuards(AuthGuard) // 路由守卫
@Roles('admin') // 角色控制(需自定义装饰器)
@Public() // 跳过身份验证(自定义)
2
3
🌙 2. 请求处理流程
@UseInterceptors
装饰器:用于为路由或控制器指定拦截器。
@UseInterceptors(LoggingInterceptor) // 拦截器
@UsePipes(ValidationPipe) // 数据管道
@UseFilters(HttpExceptionFilter) // 异常过滤器
2
3
🌙 Swagger 文档装饰器
@ApiTags('猫咪管理') // 接口分类
@ApiOperation({ summary: '创建猫咪' })
@ApiResponse({ status: 201, description: '成功创建' })
@ApiBody({ type: CreateCatDto }) // 请求体模型
@ApiQuery({ name: 'page', required: false })
2
3
4
5
🌙 其他装饰器
🌙 1. 文件上传
@Post('upload')
@UseInterceptors(FileInterceptor('file'))
uploadFile(
@UploadedFile() file: Express.Multer.File
) {}
2
3
4
5
🌙 2. SSE 服务端推送
@Get('stream')
@SSE() // 服务端事件流
streamData() {
return new Observable(subscriber => {
subscriber.next({ data: '实时数据' });
});
}
2
3
4
5
6
7
🌙 总结
Nest框架的装饰器可以分为以下三类:核心类装饰器、HTTP类装饰器和模块类装饰器。
🌙 1.核心类装饰器:
@Module()
:用于标识一个类作为Nest模块,用于组织应用程序中的组件。@Controller()
:用于标识一个类作为Nest控制器,负责处理传入的HTTP请求。@Injectable()
:用于标识一个类作为Nest提供者,用于实例化和管理应用程序的依赖项。@Middleware()
:用于标识一个类作为Nest中间件,用于处理HTTP请求和响应的中间操作。@Guard()
:用于标识一个类作为Nest守卫,用于执行身份验证和授权逻辑的中间件。
🌙 2.HTTP类装饰器:
@Get()
、@Post()
、@Put()
、@Delete()
等:用于标识控制器中的方法作为特定HTTP请求方法的处理程序。@Param()
、@Query()
、@Body()
等:用于标识控制器方法中的参数来源,从URL路径参数、查询参数和请求体中提取值。@Render()
:用于标识控制器方法返回视图模板的装饰器。
🌙 3.模块类装饰器:
@Imports()
:用于在模块中导入其他模块。@Providers()
:用于在模块中定义提供者,以供依赖注入使用。@Controllers()
:用于在模块中定义控制器。@Exports()
:用于将模块中的提供者暴露给其他模块。@Global()
:用于标识一个模块为全局模块,使得其提供者在整个应用程序中可见。
🌙 自定义装饰器
🌙 设置默认值
function defaultValue<T extends Record<string, any>>(defaults: T) {
return function <C extends new (...args: any[]) => any>(constructor: C) {
return class extends constructor {
constructor(...args: any[]) {
super(...args)
Object.entries(defaults).forEach(([key, value]) => {
if (this[key] === undefined) {
this[key] = value
}
})
}
}
}
}
@defaultValue({
theme: 'dark',
language: 'zh-CN'
})
class Settings {
[key: string]: any
}
const settings = new Settings()
console.log('settings.theme', settings.theme) // settings.theme dark
console.log('settings.language', settings?.language) // settings.theme dark
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
🌙 版本化路由装饰器
- 基础版
import { applyDecorators, Controller, SetMetadata } from '@nestjs/common';
import { ApiHeader, ApiTags } from '@nestjs/swagger';
// 创建可复用的版本装饰器工厂
export function ApiVersion(version: string, tag?: string) {
return applyDecorators(
Controller({ path: `v${version}`, version }), // 路径和版本号双保险
ApiTags(tag || `V${version} 版本`), // Swagger 分类
ApiHeader({ // 添加版本头说明
name: 'X-API-Version',
description: `当前接口版本 v${version}`
}),
SetMetadata('apiVersion', version) // 存储元数据供后续使用
);
}
// 控制器使用示例
@ApiVersion('1', '用户管理') // 路径前缀 /v1,Swagger 分类"用户管理"
export class UserControllerV1 {
@Get('profile')
getProfile() { /* V1 实现 */ }
}
@ApiVersion('2', '用户管理') // 路径前缀 /v2
export class UserControllerV2 {
@Get('profile')
getProfile() { /* V2 实现 */ }
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
- 多版本共存
import {applyDecorators, Controller, SetMetadata} from '@nestjs/common';
import {ApiHeader, ApiTags} from '@nestjs/swagger';
export function ApiVersion(
versions: string | string[],
tag?: string
) {
const versionArray = Array.isArray(versions) ? versions : [versions];
const pathPrefix = `v[${versionArray.join('|')}]`; // 生成正则表达式友好路径
return applyDecorators(
// 使用动态路径参数捕获版本号
Controller({path: `:apiVersion(${versionArray.join('|')})`}),
// 存储版本元数据供全局守卫使用
SetMetadata('supportedVersions', versionArray),
// Swagger 文档增强
ApiTags(tag || `Version ${versionArray.join('/')}`),
ApiHeader({
name: 'X-API-Version',
description: `支持版本: ${versionArray.join(', ')}`,
example: versionArray[0]
}),
// 版本路由重定向逻辑
VersionRedirectMiddleware(versionArray)
);
}
// 版本重定向中间件工厂
function VersionRedirectMiddleware(versions: string[]) {
return class implements NestMiddleware {
use(req: Request, res: Response, next: Function) {
const versionParam = req.params.apiVersion;
if (!versions.includes(versionParam)) {
return res.redirect(`/v${versions[0]}${req.path}`);
}
req.url = req.url.replace(`/${versionParam}`, '');
next();
}
};
}
// 使用示例:支持 v1 和 v2 版本
@ApiVersion(['1', '2'], '用户模块')
export class UserController {
@Get('profile')
@Version(['1', '2']) // 方法级别声明支持版本
getProfile(@Param('apiVersion') version: string) {
return version === '1' ? this.getV1Profile() : this.getV2Profile();
}
}
// main.ts
app.enableVersioning({
type: VersioningType.URI,
prefix: 'v',
});
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
- 高级版装饰器(带自动路由前缀)
import { applyDecorators, Controller, SetMetadata } from '@nestjs/common';
export function VersionedController(modulePath: string) {
return (version: string) =>
applyDecorators(
ApiVersion(version),
Controller(`api/${modulePath}/v${version}`) // 自动生成路由前缀
);
}
// 使用示例
@VersionedController('payments')('3') // 路径:api/payments/v3
export class PaymentControllerV3 {
@Post()
createPayment() { /* 支付相关逻辑 */
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18