如何从参数装饰器中获取控制器类的元数据?
问题描述
希望在HashedRouteParam参数装饰器中获取PostsController的@Controller装饰器传入的实体类(示例为PostEntity),当前自定义控制器装饰器中设置元数据正常,但在参数装饰器中获取时entity为undefined。
代码示例
自定义控制器装饰器
import { ControllerOptions as NestControllerOptions } from '@nestjs/common/decorators/core/controller.decorator'; import { Controller as NestController } from '@nestjs/common'; import { Entity } from '../../config/mikro-orm.config'; import { BaseEntity } from '../../database/entities'; export const ENTITY_KEY = Symbol('entity'); interface ControllerOptions extends NestControllerOptions { entity?: Entity; } type ControllerParams = ControllerOptions | string | string[] | Entity; export function Controller<TFunction extends Function>( prefixOrOptionsOrEntity?: ControllerParams, ): ClassDecorator { if ( typeof prefixOrOptionsOrEntity === 'undefined' || typeof prefixOrOptionsOrEntity === 'string' || Array.isArray(prefixOrOptionsOrEntity) ) { return NestController(prefixOrOptionsOrEntity); } let entity: Entity; if ((prefixOrOptionsOrEntity as Entity).prototype instanceof BaseEntity) { entity = prefixOrOptionsOrEntity as Entity; } else if ((prefixOrOptionsOrEntity as ControllerOptions).entity) { entity = (prefixOrOptionsOrEntity as ControllerOptions).entity; delete (prefixOrOptionsOrEntity as ControllerOptions).entity; } return ((target: TFunction) => { // 原错误代码:Reflect.defineMetadata(ENTITY_KEY, entity, target.constructor); Reflect.defineMetadata(ENTITY_KEY, entity, target); // 修正为直接使用target return NestController(prefixOrOptionsOrEntity as any)(target); }) as any; }
PostsController
import { Controller } from '../common/decorators'; @Controller(PostEntity) export class PostsController { // ... @Get(':id') async findOne(@HashedRouteParam('id') id: number) { // ... } }
HashedRouteParam装饰器
export function HashedRouteParam( payload: IHashedRoutePayload, ...pipes: (Type<PipeTransform> | PipeTransform)[] ) { return ( target: Object, propertyKey: string | symbol, parameterIndex: number, ) => { // 参数装饰器的target是类的原型,target.constructor才是类本身 const entity = Reflect.getMetadata(ENTITY_KEY, target.constructor); // ... const extendedPipes = [decodePipe, ...pipes]; return Param(property, ...extendedPipes)( target, propertyKey, parameterIndex, ); }; }
问题根源
自定义Controller装饰器中,错误地将元数据存储到了target.constructor上——类装饰器的target本身就是类的构造函数,target.constructor指向的是Function的构造函数,导致元数据没有存在目标控制器类上。参数装饰器中通过target.constructor获取的是控制器类,自然拿不到之前错误存储的元数据。
修复方案
将自定义Controller装饰器中的Reflect.defineMetadata(ENTITY_KEY, entity, target.constructor)改为Reflect.defineMetadata(ENTITY_KEY, entity, target),确保元数据直接存储在控制器类的构造函数上,这样参数装饰器中就能通过target.constructor正确获取到元数据。
替代方案
1. 直接在参数装饰器中传入实体类
如果场景允许,可以直接在使用HashedRouteParam时传入实体类,无需依赖控制器元数据:
@Get(':id') async findOne(@HashedRouteParam({ key: 'id', entity: PostEntity }) id: number) { // ... }
修改HashedRouteParam的参数定义,直接接收实体类参数即可。
2. 使用Nest的SetMetadata实现
如果需要通过@nestjs/common的SetMetadata实现,可以:
- 在自定义
Controller装饰器中替换为SetMetadata存储实体类:
import { SetMetadata } from '@nestjs/common'; // ... return ((target: TFunction) => { SetMetadata(ENTITY_KEY, entity)(target); return NestController(prefixOrOptionsOrEntity as any)(target); }) as any;
- 参数装饰器中依然通过
Reflect.getMetadata(ENTITY_KEY, target.constructor)获取,因为SetMetadata本质也是基于Reflect实现的元数据存储,存储位置同样是类构造函数。
3. 自定义管道注入实体类
创建自定义管道,将实体类注册为模块提供者,通过依赖注入获取实体类,适合需要在管道中处理实体逻辑的场景。
内容的提问来源于stack exchange,提问作者Uğur İLTER

