Nestjs自定义参数装饰器未触发,如何实现属性自动注入?
问题原因分析
- NestJS 原生参数装饰器仅会对被
@Controller装饰类下的路由处理方法生效,如果你将@InjectTenant写在了 Service 层的方法参数上,装饰器逻辑完全不会被触发 - 你当前的装饰器逻辑直接新建了
CreateAssetDto实例,会覆盖请求端传入的assetName等业务字段,不符合实际使用需求
修复方案
第一步:调整装饰器使用位置
将 @InjectTenant 从 Service 层的 create 方法移到 Controller 层对应路由的参数上。
第二步:修改装饰器逻辑,复用请求中已解析的 DTO 数据
import { createParamDecorator, ExecutionContext } from '@nestjs/common'; import { plainToInstance } from 'class-transformer'; import { CreateAssetDto } from './dto/create-asset.dto'; import { Tenant } from './entities/tenant.entity'; export const InjectTenant = createParamDecorator( (data: unknown, ctx: ExecutionContext) => { const request = ctx.switchToHttp().getRequest(); // 读取请求中已经被全局ValidationPipe解析的原始body数据 const requestBody = request.body; // 转换为CreateAssetDto实例,保留原始请求字段 const createAssetDto = plainToInstance(CreateAssetDto, requestBody); // 注入租户信息,实际场景可从request.user、请求头等位置读取真实租户 createAssetDto.tenant = new Tenant("JAY"); return createAssetDto; } );
第三步:Controller层正确使用示例
import { Controller, Post } from '@nestjs/common'; import { AssetService } from './asset.service'; import { CreateAssetDto } from './dto/create-asset.dto'; import { InjectTenant } from './decorators/inject-tenant.decorator'; @Controller('assets') export class AssetController { constructor(private readonly assetService: AssetService) {} @Post() // 此处直接使用自定义参数装饰器,不需要再加@Body() async create(@InjectTenant() createAssetDto: CreateAssetDto) { console.log(createAssetDto.tenant); // 可正常拿到注入的租户信息 return this.assetService.create(createAssetDto); } }
可选优化:全局统一注入,无需替换@Body()
如果需要对所有同类型DTO统一注入租户,不想每个路由都修改参数装饰器,可以用拦截器实现:
import { Injectable, NestInterceptor, ExecutionContext, CallHandler } from '@nestjs/common'; import { Observable } from 'rxjs'; import { plainToInstance } from 'class-transformer'; import { CreateAssetDto } from './dto/create-asset.dto'; @Injectable() export class TenantInjectInterceptor implements NestInterceptor { intercept(context: ExecutionContext, next: CallHandler): Observable<any> { const request = context.switchToHttp().getRequest(); // 匹配需要注入租户的路由规则,可自行调整 if (request.method === 'POST' && request.route.path === '/assets') { const createAssetDto = plainToInstance(CreateAssetDto, request.body); createAssetDto.tenant = new Tenant("JAY"); request.body = createAssetDto; } return next.handle(); } }
在路由或者全局注册该拦截器后,仍然可以正常使用原生@Body()参数获取注入后的DTO。
内容的提问来源于stack exchange,提问作者Ramdani Muksin
相关产品推荐
相关产品推荐

