NestJS自定义装饰器 #

装饰器概述 #

NestJS提供了强大的装饰器系统,允许开发者创建自定义装饰器来简化代码、增强功能。

参数装饰器 #

创建自定义参数装饰器 #

typescript
import { createParamDecorator, ExecutionContext } from '@nestjs/common';

export const CurrentUser = createParamDecorator(
  (data: string, ctx: ExecutionContext) => {
    const request = ctx.switchToHttp().getRequest();
    const user = request.user;

    return data ? user?.[data] : user;
  },
);

使用自定义装饰器 #

typescript
@Controller('users')
export class UsersController {
  @Get('profile')
  getProfile(@CurrentUser() user: User) {
    return user;
  }

  @Get('email')
  getEmail(@CurrentUser('email') email: string) {
    return { email };
  }
}

结合Pipe使用 #

typescript
@Get(':id')
findOne(
  @CurrentUser('id', ParseIntPipe) id: number,
) {
  return this.usersService.findOne(id);
}

方法装饰器 #

创建方法装饰器 #

typescript
import { SetMetadata } from '@nestjs/common';

export const CACHE_KEY = 'cache_key';
export const CACHE_TTL = 'cache_ttl';

export const Cache = (key: string, ttl: number = 60) => {
  return (target: any, propertyKey: string, descriptor: PropertyDescriptor) => {
    SetMetadata(CACHE_KEY, key)(target, propertyKey, descriptor);
    SetMetadata(CACHE_TTL, ttl)(target, propertyKey, descriptor);
  };
};

使用方法装饰器 #

typescript
@Controller('users')
export class UsersController {
  @Get()
  @Cache('users_all', 120)
  findAll() {
    return this.usersService.findAll();
  }
}

组合装饰器 #

组合多个装饰器 #

typescript
import { applyDecorators, Get, UseGuards, UseInterceptors } from '@nestjs/common';
import { JwtAuthGuard } from '../auth/jwt-auth.guard';
import { RolesGuard } from '../auth/roles.guard';
import { Roles } from '../auth/roles.decorator';
import { Role } from '../auth/role.enum';
import { LoggingInterceptor } from '../common/interceptors/logging.interceptor';

export function Authenticated(...roles: Role[]) {
  return applyDecorators(
    UseGuards(JwtAuthGuard, RolesGuard),
    Roles(...roles),
    UseInterceptors(LoggingInterceptor),
  );
}

使用组合装饰器 #

typescript
@Controller('admin')
export class AdminController {
  @Get('dashboard')
  @Authenticated(Role.ADMIN)
  getDashboard() {
    return 'Admin dashboard';
  }

  @Get('settings')
  @Authenticated(Role.SUPER_ADMIN)
  getSettings() {
    return 'Admin settings';
  }
}

公开路由装饰器 #

typescript
import { SetMetadata } from '@nestjs/common';

export const IS_PUBLIC_KEY = 'isPublic';
export const Public = () => SetMetadata(IS_PUBLIC_KEY, true);
typescript
@Controller('auth')
export class AuthController {
  @Post('login')
  @Public()
  login() {
    return 'Login';
  }

  @Post('register')
  @Public()
  register() {
    return 'Register';
  }
}

类装饰器 #

创建类装饰器 #

typescript
import { SetMetadata } from '@nestjs/common';

export const AUDIT_KEY = 'audit';

export function Audit(enabled: boolean = true) {
  return function (target: any) {
    SetMetadata(AUDIT_KEY, enabled)(target);
  };
}

使用类装饰器 #

typescript
@Controller('users')
@Audit(true)
export class UsersController {
  // 所有方法都会被审计
}

实用装饰器示例 #

用户装饰器 #

typescript
import { createParamDecorator, ExecutionContext } from '@nestjs/common';

export const User = createParamDecorator(
  (data: keyof UserEntity, ctx: ExecutionContext) => {
    const request = ctx.switchToHttp().getRequest();
    const user = request.user;

    if (!user) {
      return null;
    }

    return data ? user[data] : user;
  },
);

export const UserId = () => User('id');
export const UserEmail = () => User('email');
export const UserRole = () => User('role');
typescript
@Get('profile')
getProfile(
  @UserId() userId: number,
  @UserEmail() email: string,
  @UserRole() role: string,
) {
  return { userId, email, role };
}

分页装饰器 #

typescript
import { createParamDecorator, ExecutionContext } from '@nestjs/common';

interface PaginationParams {
  page: number;
  limit: number;
  offset: number;
}

export const Pagination = createParamDecorator(
  (data: unknown, ctx: ExecutionContext): PaginationParams => {
    const request = ctx.switchToHttp().getRequest();
    const page = parseInt(request.query.page) || 1;
    const limit = parseInt(request.query.limit) || 10;

    return {
      page,
      limit,
      offset: (page - 1) * limit,
    };
  },
);
typescript
@Get()
findAll(@Pagination() pagination: PaginationParams) {
  return this.usersService.findAll(pagination);
}

排序装饰器 #

typescript
import { createParamDecorator, ExecutionContext } from '@nestjs/common';

interface SortParams {
  field: string;
  order: 'ASC' | 'DESC';
}

export const Sort = createParamDecorator(
  (data: unknown, ctx: ExecutionContext): SortParams => {
    const request = ctx.switchToHttp().getRequest();
    const field = request.query.sortBy || 'createdAt';
    const order = (request.query.sortOrder || 'DESC').toUpperCase();

    return {
      field,
      order: order === 'ASC' ? 'ASC' : 'DESC',
    };
  },
);

请求ID装饰器 #

typescript
import { createParamDecorator, ExecutionContext } from '@nestjs/common';

export const RequestId = createParamDecorator(
  (data: unknown, ctx: ExecutionContext): string => {
    const request = ctx.switchToHttp().getRequest();
    return request.id || request.headers['x-request-id'] || generateUUID();
  },
);

function generateUUID(): string {
  return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, c => {
    const r = (Math.random() * 16) | 0;
    const v = c === 'x' ? r : (r & 0x3) | 0x8;
    return v.toString(16);
  });
}

IP装饰器 #

typescript
import { createParamDecorator, ExecutionContext } from '@nestjs/common';

export const ClientIp = createParamDecorator(
  (data: unknown, ctx: ExecutionContext): string => {
    const request = ctx.switchToHttp().getRequest();
    return (
      request.headers['x-forwarded-for']?.split(',')[0] ||
      request.headers['x-real-ip'] ||
      request.connection?.remoteAddress ||
      request.socket?.remoteAddress
    );
  },
);

响应格式装饰器 #

typescript
import { SetMetadata } from '@nestjs/common';

export const RESPONSE_FORMAT_KEY = 'response_format';

export interface ResponseFormat {
  wrap?: boolean;
  message?: string;
}

export const ResponseFormat = (options: ResponseFormat = {}) => {
  return SetMetadata(RESPONSE_FORMAT_KEY, options);
};

版本装饰器 #

typescript
import { SetMetadata } from '@nestjs/common';

export const API_VERSION_KEY = 'api_version';

export const ApiVersion = (version: string | string[]) => {
  return SetMetadata(API_VERSION_KEY, version);
};

限流装饰器 #

typescript
import { SetMetadata } from '@nestjs/common';

export const RATE_LIMIT_KEY = 'rate_limit';

export interface RateLimitOptions {
  limit: number;
  windowMs: number;
}

export const RateLimit = (options: RateLimitOptions) => {
  return SetMetadata(RATE_LIMIT_KEY, options);
};
typescript
@Get()
@RateLimit({ limit: 100, windowMs: 60000 })
findAll() {
  return this.usersService.findAll();
}

事务装饰器 #

typescript
import { SetMetadata } from '@nestjs/common';

export const TRANSACTION_KEY = 'transaction';

export const Transaction = () => SetMetadata(TRANSACTION_KEY, true);

日志装饰器 #

typescript
import { SetMetadata } from '@nestjs/common';

export const LOG_KEY = 'log';

export interface LogOptions {
  enabled: boolean;
  level?: 'debug' | 'log' | 'warn' | 'error';
  includeBody?: boolean;
  includeQuery?: boolean;
}

export const Log = (options: LogOptions = { enabled: true }) => {
  return SetMetadata(LOG_KEY, options);
};

装饰器工厂 #

创建装饰器工厂 #

typescript
import { applyDecorators, Post, Body, UsePipes, ValidationPipe } from '@nestjs/common';

interface CreateOptions {
  path?: string;
  validate?: boolean;
}

export function Create(options: CreateOptions = {}) {
  const decorators = [
    Post(options.path || ''),
  ];

  if (options.validate !== false) {
    decorators.push(UsePipes(new ValidationPipe({ transform: true })));
  }

  return applyDecorators(...decorators);
}
typescript
@Controller('users')
export class UsersController {
  @Create()
  create(@Body() createUserDto: CreateUserDto) {
    return this.usersService.create(createUserDto);
  }
}

总结 #

本章学习了NestJS自定义装饰器:

  • 参数装饰器
  • 方法装饰器
  • 组合装饰器
  • 实用装饰器示例
  • 装饰器工厂

接下来,让我们学习 事件与Emitter

最后更新:2026-03-28