You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何从参数装饰器中获取控制器类的元数据?

问题分析与解决方案

问题描述

希望在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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.23 11:06:18