别再死记硬背了!用这5个真实场景案例,彻底搞懂NestJS装饰器的用法
装饰器是NestJS框架中极具特色的功能,但很多开发者仅仅停留在"知道"层面,遇到实际项目仍然无从下手。本文将带你跳出枯燥的语法定义,通过5个真实项目场景,掌握装饰器的实战应用技巧。
1. 用装饰器实现API请求日志记录
在线上项目中,我们经常需要记录每个API的请求信息用于监控和调试。传统做法是在每个控制器方法中手动添加日志代码,这不仅重复劳动,还会污染业务逻辑。来看看如何用装饰器优雅解决:
// 定义日志装饰器 function LogRequest() { return function(target: any, propertyKey: string, descriptor: PropertyDescriptor) { const originalMethod = descriptor.value; descriptor.value = async function(...args: any[]) { const request = args[0]; // Express的request对象 console.log(`[${new Date().toISOString()}] ${request.method} ${request.url}`); return originalMethod.apply(this, args); }; }; } // 在控制器中使用 @Controller('users') export class UsersController { @Get() @LogRequest() // 只需添加这个装饰器 async findAll() { // 纯净的业务逻辑 return userService.findAll(); } }这个案例展示了方法装饰器的典型应用场景。通过将横切关注点(日志记录)与业务逻辑分离,代码变得更加清晰可维护。
2. 属性装饰器自动验证DTO字段
表单验证是Web开发中的常见需求。传统做法是在业务代码中手动检查每个字段,不仅繁琐而且容易遗漏。下面这个例子演示如何用属性装饰器自动验证DTO:
// 定义验证装饰器 function ValidateEmail() { return function(target: any, propertyKey: string) { let value: string; const getter = () => value; const setter = (newVal: string) => { if (!newVal.includes('@')) { throw new Error('Invalid email format'); } value = newVal; }; Object.defineProperty(target, propertyKey, { get: getter, set: setter, enumerable: true, configurable: true }); }; } // 在DTO中使用 export class CreateUserDto { @ValidateEmail() email: string; // 其他字段... } // 控制器中自动验证 @Post() create(@Body() createUserDto: CreateUserDto) { // 如果email格式错误,上面已经自动抛出异常 return userService.create(createUserDto); }提示:NestJS内置了class-validator库,实际项目中可以直接使用@IsEmail()等现成装饰器。这里展示的是其底层实现原理。
3. 方法装饰器实现权限控制
权限系统是后台管理项目的标配。下面我们实现一个简单的角色检查装饰器:
// 角色检查装饰器 function Roles(...roles: string[]) { return function(target: any, propertyKey: string, descriptor: PropertyDescriptor) { const originalMethod = descriptor.value; descriptor.value = function(...args: any[]) { const request = args[0]; const userRole = request.user?.role; // 假设用户信息已注入请求 if (!roles.includes(userRole)) { throw new ForbiddenException('Insufficient permissions'); } return originalMethod.apply(this, args); }; }; } // 使用示例 @Controller('admin') export class AdminController { @Get('dashboard') @Roles('admin', 'superadmin') // 只允许admin和superadmin访问 getDashboard() { return adminService.getDashboardData(); } }这个装饰器可以灵活地应用到任何需要权限控制的方法上,而且权限规则一目了然。
4. 参数装饰器便捷获取用户信息
在认证过的请求中,我们经常需要获取当前用户信息。传统方式是从request对象中手动提取,而参数装饰器可以让这个过程更加优雅:
// 定义用户信息装饰器 function CurrentUser() { return createParamDecorator((data: unknown, ctx: ExecutionContext) => { const request = ctx.switchToHttp().getRequest(); return request.user; // 假设认证中间件已注入user }); } // 使用示例 @Controller('profile') export class ProfileController { @Get() getProfile(@CurrentUser() user: User) { // 直接注入用户对象 return profileService.getProfile(user.id); } }NestJS内置了@Req()、@Res()等参数装饰器,理解了这个原理,你就能创建自己的参数装饰器来简化开发。
5. 组合装饰器实现自定义缓存逻辑
最后一个案例展示如何组合多个装饰器实现复杂功能。我们将创建一个缓存装饰器,可以指定缓存时间和键前缀:
// 缓存装饰器工厂 function Cache(ttl: number, keyPrefix?: string) { return function(target: any, propertyKey: string, descriptor: PropertyDescriptor) { const originalMethod = descriptor.value; const cache = new Map(); // 实际项目应该用Redis等 descriptor.value = async function(...args: any[]) { const cacheKey = keyPrefix ? `${keyPrefix}_${JSON.stringify(args)}` : `${propertyKey}_${JSON.stringify(args)}`; if (cache.has(cacheKey)) { return cache.get(cacheKey); } const result = await originalMethod.apply(this, args); cache.set(cacheKey, result); // 设置过期时间 setTimeout(() => cache.delete(cacheKey), ttl * 1000); return result; }; }; } // 使用示例 @Controller('products') export class ProductsController { @Get() @Cache(60, 'products_list') // 缓存60秒 async findAll(@Query() query: PaginationQuery) { return productService.findAll(query); } }这个案例展示了装饰器的强大组合能力。通过参数化配置,我们可以创建高度可复用的功能模块。
装饰器使用的最佳实践
在项目中使用装饰器时,有几个关键点需要注意:
- 保持单一职责:每个装饰器应该只做一件事,复杂逻辑可以通过组合多个装饰器实现
- 注意执行顺序:方法装饰器从下往上执行,类装饰器从内往外执行
- 避免过度使用:装饰器虽然强大,但过度使用会让代码变得难以理解
- 考虑性能影响:装饰器会在运行时修改目标,可能带来轻微性能开销
装饰器是NestJS框架的核心特性之一,掌握它的实战应用能显著提升代码质量和开发效率。