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