NestJS 如何在装饰器中获取 Service 实例?
前言
在现代Web开发中,NestJS以其模块化和可扩展性受到了广大开发者的青睐。装饰器作为NestJS核心特性之一,能够以简洁的语法增强应用的功能。然而,在实际开发中,我们常常需要在装饰器中访问服务实例,以实现更复杂的业务逻辑。
那么,如何在NestJS装饰器中高效、安全地获取服务实例呢?本文将通过详细的步骤和示例,实现这一目标,以提升代码的可维护性和可扩展性。
准备工作
在深入探讨之前,确保你已经了解以下概念:
- NestJS 基础:包括模块、控制器、服务等。
- 装饰器:理解什么是装饰器以及如何在 TypeScript 中使用它们。
实现步骤
1. 创建一个自定义装饰器
首先,我们需要创建一个简单的自定义装饰器。装饰器其实就是一个函数,可以在类、方法或属性上使用。
TypeScriptimport { SetMetadata } from '@nestjs/common'; export const CustomDecorator = (...args: any[]) => SetMetadata('custom', args);
2. 使用 Reflector 获取元数据
Reflector
是 NestJS 提供的一个工具类,用于获取装饰器的元数据。首先,创建一个拦截器或 Guard 来处理装饰器。
TypeScriptimport { Injectable, CanActivate, ExecutionContext } from '@nestjs/common'; import { Reflector } from '@nestjs/core'; @Injectable() export class CustomGuard implements CanActivate { constructor(private reflector: Reflector) {} canActivate(context: ExecutionContext): boolean { const customData = this.reflector.get<string[]>('custom', context.getHandler()); console.log('Custom Data:', customData); // 逻辑处理... return true; } }
3. 在装饰器中获取 Service 实例
要在装饰器中获取 Service 实例,我们可以使用依赖注入(Dependency Injection)。我们将需要的服务先注入到一个全局的模块中。
TypeScriptimport { Module, Global } from '@nestjs/common'; import { CustomService } from './custom.service'; @Global() @Module({ providers: [CustomService], exports: [CustomService], }) export class CustomModule {}
在任何需要使用该装饰器的地方导入 CustomModule
。
4. 创建装饰器工厂函数
我们需要一个工厂函数来创建装饰器,并在其中获取 Service 实例。
TypeScriptimport { Inject, Injectable, createParamDecorator, ExecutionContext } from '@nestjs/common'; import { CustomService } from './custom.service'; @Injectable() export class CustomDecoratorFactory { constructor(private readonly customService: CustomService) {} createCustomDecorator() { return createParamDecorator( (data: unknown, ctx: ExecutionContext) => { console.log(this.customService.getSomeData()); return this.customService.getSomeData(); }, )(); } }
5. 使用装饰器工厂
在你的控制器中使用这个装饰器工厂。
TypeScriptimport { Controller, Get } from '@nestjs/common'; import { CustomDecoratorFactory } from './custom-decorator.factory'; @Controller('example') export class ExampleController { constructor(private readonly customDecoratorFactory: CustomDecoratorFactory) {} @Get() @this.customDecoratorFactory.createCustomDecorator() findAll() { return 'This action returns all items'; } }
优化手段
1. 改进装饰器工厂函数
在之前的实现中,我们已经通过装饰器工厂函数成功获取了 Service 实例。现在我们可以对装饰器工厂函数进行一些改进,使其更加灵活和可配置。
TypeScriptimport { createParamDecorator, ExecutionContext } from '@nestjs/common'; export function CustomDecorator(data?: any): ParameterDecorator { return createParamDecorator( (data: unknown, ctx: ExecutionContext) => { const request = ctx.switchToHttp().getRequest(); const customService = request.customService; // 从请求中获取服务实例 console.log(customService.getSomeData()); return customService.getSomeData(); }, )(data); }
2. 在请求中注入 Service 实例
为了确保我们的 Service 实例可以在请求中使用,我们可以创建一个中间件将 Service 实例注入到请求中。
TypeScriptimport { Injectable, NestMiddleware } from '@nestjs/common'; import { Request, Response, NextFunction } from 'express'; import { CustomService } from './custom.service'; @Injectable() export class CustomMiddleware implements NestMiddleware { constructor(private readonly customService: CustomService) {} use(req: Request, res: Response, next: NextFunction) { req.customService = this.customService; next(); } }
然后在你的模块中应用这个中间件:
TypeScriptimport { Module, MiddlewareConsumer, NestModule } from '@nestjs/common'; import { CustomMiddleware } from './custom.middleware'; import { ExampleController } from './example.controller'; import { CustomService } from './custom.service'; @Module({ controllers: [ExampleController], providers: [CustomService], }) export class ExampleModule implements NestModule { configure(consumer: MiddlewareConsumer) { consumer .apply(CustomMiddleware) .forRoutes(ExampleController); } }
3. 在控制器中使用改进后的装饰器
现在,我们可以在控制器中使用改进后的自定义装饰器:
TypeScriptimport { Controller, Get } from '@nestjs/common'; import { CustomDecorator } from './custom.decorator'; @Controller('example') export class ExampleController { @Get() @CustomDecorator() findAll(@CustomDecorator() customData: any) { return `This action returns all items with custom data: ${customData}`; } }
高级使用场景
1. 基于角色的访问控制
在实际项目中,我们可能需要基于用户角色进行访问控制。可以使用自定义装饰器来实现这一功能。
TypeScriptimport { SetMetadata } from '@nestjs/common'; export const Roles = (...roles: string[]) => SetMetadata('roles', roles);
然后创建一个守卫来处理角色验证:
TypeScriptimport { Injectable, CanActivate, ExecutionContext } from '@nestjs/common'; import { Reflector } from '@nestjs/core'; @Injectable() export class RolesGuard implements CanActivate { constructor(private reflector: Reflector) {} canActivate(context: ExecutionContext): boolean { const roles = this.reflector.get<string[]>('roles', context.getHandler()); if (!roles) { return true; } const request = context.switchToHttp().getRequest(); const user = request.user; return roles.some(role => user.roles?.includes(role)); } }
在模块中注册守卫:
TypeScriptimport { Module } from '@nestjs/common'; import { APP_GUARD } from '@nestjs/core'; import { RolesGuard } from './roles.guard'; import { ExampleController } from './example.controller'; @Module({ controllers: [ExampleController], providers: [ { provide: APP_GUARD, useClass: RolesGuard, }, ], }) export class ExampleModule {}
在控制器中使用 Roles
装饰器:
TypeScriptimport { Controller, Get } from '@nestjs/common'; import { Roles } from './roles.decorator'; @Controller('example') export class ExampleController { @Get() @Roles('admin') findAll() { return 'This action returns all items for admins'; } }
2. 日志记录
我们可以使用装饰器来简化日志记录的工作:
TypeScriptimport { createParamDecorator, ExecutionContext, Injectable } from '@nestjs/common'; import { CustomService } from './custom.service'; @Injectable() export class LoggingDecoratorFactory { constructor(private readonly customService: CustomService) {} createLoggingDecorator() { return createParamDecorator( (data: unknown, ctx: ExecutionContext) => { const request = ctx.switchToHttp().getRequest(); console.log(`[${new Date().toISOString()}] Request URL: ${request.url}`); return this.customService.getSomeData(); }, )(); } }
在控制器中使用日志装饰器:
TypeScriptimport { Controller, Get } from '@nestjs/common'; import { LoggingDecoratorFactory } from './logging-decorator.factory'; @Controller('') export class ExampleController { constructor(private loggingDecoratorFactory: LoggingDecoratorFactory) @Get() @this.loggingDecoratorFactory.createLoggingDecorator() findAll() { return 'This action returns all items'; } }
总结
通过本文的详细讲解,我们深入探讨了如何在NestJS的装饰器中获取服务实例,并进一步探索了基于角色的访问控制和日志记录等高级应用场景。这不仅增强了装饰器的功能,也提升了应用的灵活性和可维护性。希望通过本文的讲解,你能更好地掌握NestJS装饰器的使用技巧,并在实际项目中充分发挥其强大功能。