别再死记硬背了!用这5个真实场景,彻底搞懂NestJS装饰器的用法
在NestJS开发中,装饰器(Decorator)就像瑞士军刀一样,能优雅地解决各种架构问题。但很多开发者仅仅停留在"知道语法"层面,面对实际业务需求时依然无从下手。本文将带你跳出枯燥的API文档,通过5个真实场景感受装饰器如何提升代码质量。
1. 请求参数验证与转换的艺术
想象一个用户注册接口需要处理以下数据:
class CreateUserDto { username: string; password: string; birthDate: Date; }传统验证方式需要在方法体内写大量if-else判断,而使用装饰器可以这样实现:
import { IsString, IsDateString, MinLength } from 'class-validator'; class CreateUserDto { @IsString() @MinLength(3) username: string; @IsString() @MinLength(8) password: string; @IsDateString() birthDate: Date; }进阶技巧:自定义转换装饰器
function ToLowerCase() { return function (target: any, key: string) { let value = target[key]; Object.defineProperty(target, key, { get: () => value, set: (v: string) => { value = v.toLowerCase(); }, enumerable: true, configurable: true }); }; } class SearchDto { @ToLowerCase() keyword: string; }提示:结合
class-transformer的@Transform装饰器,可以实现更复杂的数据转换逻辑
2. 自动化接口日志与性能监控
手动在每个方法里写console.log不仅低效,还会污染业务代码。方法装饰器能完美解决这个问题:
function LogExecution() { return function ( target: any, propertyKey: string, descriptor: PropertyDescriptor ) { const originalMethod = descriptor.value; descriptor.value = async function (...args: any[]) { const start = Date.now(); console.log(`[${new Date().toISOString()}] Calling ${propertyKey}`); try { const result = await originalMethod.apply(this, args); const duration = Date.now() - start; console.log(`[${new Date().toISOString()}] ${propertyKey} executed in ${duration}ms`); return result; } catch (error) { console.error(`[${new Date().toISOString()}] ${propertyKey} failed:`, error); throw error; } }; }; } class UserController { @LogExecution() async getUser(id: string) { // 业务逻辑 } }监控指标扩展:
| 装饰器类型 | 采集指标 | 存储方式 |
|---|---|---|
| 方法装饰器 | 执行时间 | Prometheus |
| 类装饰器 | 请求量统计 | Elasticsearch |
| 参数装饰器 | 异常参数记录 | Sentry |
3. 数据库字段映射的智能处理
TypeORM等ORM库虽然提供了基础装饰器,但实际业务中我们经常需要:
function EncryptedColumn() { return function (target: any, propertyKey: string) { const secretKey = process.env.ENCRYPTION_KEY; Object.defineProperty(target, propertyKey, { get: function() { return decrypt(this[`_${propertyKey}`], secretKey); }, set: function(value: string) { this[`_${propertyKey}`] = encrypt(value, secretKey); } }); }; } class User { @PrimaryGeneratedColumn() id: number; @Column() @EncryptedColumn() privateKey: string; }字段映射对照表:
function DbField(mapTo: string) { return function (target: any, propertyKey: string) { Reflect.defineMetadata('db:field', mapTo, target, propertyKey); }; } class Product { @DbField('product_name') name: string; @DbField('sale_price') price: number; }4. API版本控制的优雅实现
用类装饰器实现多版本API共存:
function ApiVersion(version: string) { return function (constructor: Function) { Reflect.defineMetadata('api:version', version, constructor); }; } @Controller('users') @ApiVersion('v1') class UserControllerV1 { @Get() getUsers() { return legacyUserService.list(); } } @Controller('users') @ApiVersion('v2') class UserControllerV2 { @Get() getUsers() { return newUserService.listWithDetails(); } }路由配置时通过metadata筛选:
const controllers = [UserControllerV1, UserControllerV2]; controllers.forEach(controller => { const version = Reflect.getMetadata('api:version', controller); const pathPrefix = `/api/${version}`; // 注册路由逻辑... });5. 动态权限校验中间件开发
参数装饰器可以创建智能权限系统:
function RequirePermission(resource: string, action: string) { return function ( target: any, propertyKey: string, parameterIndex: number ) { const existingPermissions = Reflect.getMetadata('permissions', target, propertyKey) || []; existingPermissions.push({ index: parameterIndex, resource, action }); Reflect.defineMetadata( 'permissions', existingPermissions, target, propertyKey ); }; } class DocumentController { @Post() updateDocument( @RequirePermission('document', 'edit') userId: string, @Body() data: any ) { // 业务逻辑 } }权限校验中间件实现:
function PermissionMiddleware(req, res, next) { const handler = req.route.handler; const permissions = Reflect.getMetadata( 'permissions', handler.constructor.prototype, handler.name ); if (permissions) { permissions.forEach(({ index, resource, action }) => { const userId = req.params[index]; if (!permissionService.check(userId, resource, action)) { throw new ForbiddenException(); } }); } next(); }这些场景只是装饰器应用的冰山一角。在实际项目中,我经常组合使用多种装饰器类型——比如用类装饰器标记服务类别,方法装饰器处理缓存逻辑,参数装饰器做细粒度验证。这种声明式的编程方式让代码既简洁又易于维护。