nestjs基础之装饰器

2025/3/6 nestjsnodeES6decorator

🌙 装饰器简介

ES2016 装饰器是一个返回函数的表达式,可以接受目标、名称和属性描述符作为参数。您通过在装饰器前加上 @ 字符并放置在您要装饰的内容顶部来应用它。装饰器可以为类、方法或属性定义。

装饰器本质就是函数,主要用于:

  • 装饰类:
@annotation
class MyClass { }

function annotation(target, name, descriptor) {
   target.annotated = true;
}
1
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;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
  1. readonly装饰器接收三个参数:targetname,和descriptor
  2. target是类的原型对象,name是方法的名称,descriptor是该方法的属性描述符对象。
  3. 在装饰器函数中,将descriptor.writable设置为false,这将禁止对方法进行写操作,使其成为只读方法。
  4. 返回修改后的属性描述符对象descriptor

Nest 就是围绕装饰器的语言特性构建的。

🌙 装饰器类别

装饰器主要分为:类装饰器、方法装饰器、参数装饰器、属性装饰器、访问器装饰器。

  • 类装饰器:应用于类声明之前,用于修改类的行为或元数据。
  1. 作用对象:类(classes), 函数签名:type ClassDecorator = <TFunction extends Function>(target: TFunction) => TFunction | void;
  2. 促发时机:在类实例化前执行(实例化阶段)
  3. 典型用途:
    • 自动注册
    • 自动注入
    • 自动加载
import { Module } from '@nestjs/common';
import { CatsController } from './cats.controller';

// 类装饰器
@Module({
    controllers: [CatsController]
})
export class CatsModule {}
1
2
3
4
5
6
7
8
  • 方法装饰器:应用于方法声明之前,用于修改方法的行为或元数据。
  1. 作用对象:方法(methods),函数签名:type MethodDecorator = <T>(target: Object, propertyKey: string | symbol, descriptor: TypedPropertyDescriptor<T>) => TypedPropertyDescriptor<T> | void;
  2. 促发时机:在方法执行前执行(非实例化阶段)
  3. 典型用途:
    • 权限验证
    • 缓存
    • 请求拦截
    • 请求日志
    • 请求限流
import { Controller, Get } from '@nestjs/common';
@Controller('cats')
export class CatsController {
    // 方法装饰器
    @Get()
    findAll() {}
}
1
2
3
4
5
6
7
  • 参数装饰器:应用于方法参数声明之前,用于修改参数的行为或元数据。
  1. 作用对象:方法参数(method parameters),函数签名:type ParameterDecorator = (target: Object, propertyKey: string | symbol, parameterIndex: number) => void;
  2. 促发时机:在方法执行前执行(非实例化阶段)
  3. 典型用途:
    • 参数验证
    • 参数转换
    • 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`;
    }
}
1
2
3
4
5
6
7
8
9
  • 属性装饰器:应用于属性声明之前,用于修改属性的行为或元数据。
  1. 作用对象:类属性(class properties),函数签名:type PropertyDecorator = (target: Object, propertyKey: string | symbol) => void;
  2. 触发时机:在类定义时执行(非实例化阶段)
  3. 典型用途:
    • 添加元数据(Metadata)
    • 参数验证
    • 属性转换
    • OpenAPI/Swagger 文档生成
import { IsString, IsInt, IsOptional } from 'class-validator';

class CreateCatDto {
    @IsString()          // 验证字符串类型
    name: string;

    @IsInt()             // 验证整型
    age: number;

    @IsOptional()        // 允许字段可选
    breed?: string;
}

1
2
3
4
5
6
7
8
9
10
11
12
13
  • 访问器装饰器:应用于类的访问器声明之前,用于修改访问器行为的元数据。
  1. 作用对象:类访问器(class accessors),函数签名:type AccessorDecorator = <T>(target: Object, propertyKey: string | symbol, descriptor: TypedPropertyDescriptor<T>) => TypedPropertyDescriptor<T> | void;
  2. 促发时机:在访问器定义时执行(非实例化阶段)
  3. 典型用途:
    • 缓存装饰器
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;
}
1
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')
1
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
1
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 {}

1
2
3
4
5
6
7
8

🌙 2. 服务标识

@Injectable 装饰器:用于定义一个可注入的服务类。

@Injectable()  // 标记可注入类
export class CatsService {}
1
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 请求
}
1
2
3
4
5
6
7
8
9

🌙 2. 请求处理

@Header('Cache-Control', 'none')    // 设置响应头
@HttpCode(201)                      // 自定义状态码
@Render('index.html')               // 服务端渲染
@Redirect('/new-url', 301)          // 重定向
1
2
3
4

🌙 参数提取装饰器

🌙 1. 请求数据获取

@Param@Query@Body 等参数装饰器:用于获取请求中的参数。

@Param('id')                 // 路由参数 → /cats/:id
@Query('page')               // 查询参数 → ?page=1
@Body()                      // 请求体 → POST/PUT
@Headers('authorization')    // 请求头
1
2
3
4

🌙 2. 上下文对象

@Request() req: FastifyRequest   // 底层请求对象
@Response() res: FastifyReply    // 底层响应对象
@Ip() clientIP: string           // 客户端 IP
@HostParam() domain: string      // 主机名参数

1
2
3
4
5

🌙 功能增强装饰器

🌙 1. 安全控制

@UseGuards 装饰器:用于为路由或控制器指定守卫。

@UseGuards(AuthGuard)       // 路由守卫
@Roles('admin')             // 角色控制(需自定义装饰器)
@Public()                   // 跳过身份验证(自定义)
1
2
3

🌙 2. 请求处理流程

@UseInterceptors 装饰器:用于为路由或控制器指定拦截器。

@UseInterceptors(LoggingInterceptor)  // 拦截器
@UsePipes(ValidationPipe)             // 数据管道
@UseFilters(HttpExceptionFilter)      // 异常过滤器
1
2
3

🌙 Swagger 文档装饰器

@ApiTags('猫咪管理')          // 接口分类
@ApiOperation({ summary: '创建猫咪' })
@ApiResponse({ status: 201, description: '成功创建' })
@ApiBody({ type: CreateCatDto })      // 请求体模型
@ApiQuery({ name: 'page', required: false })
1
2
3
4
5

🌙 其他装饰器

🌙 1. 文件上传

@Post('upload')
@UseInterceptors(FileInterceptor('file'))
uploadFile(
  @UploadedFile() file: Express.Multer.File
) {}
1
2
3
4
5

🌙 2. SSE 服务端推送

@Get('stream')
@SSE()  // 服务端事件流
streamData() {
  return new Observable(subscriber => {
    subscriber.next({ data: '实时数据' });
  });
}
1
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
1
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 实现 */ }
}
1
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',
});
1
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() { /* 支付相关逻辑 */
    }
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18

ES6 系列之我们来聊聊装饰器 (opens new window)