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

NestJS中如何在FilesInterceptor作用域内调用productRepository?

解决NestJS文件上传destination回调无法访问Repository的问题

问题原因

你原来的代码里,FilesInterceptor是在控制器类初始化时就创建好的,diskStorage的destination回调函数作用域不属于请求上下文,没法直接访问控制器类里注入的productRepository;而且你在回调里用await但没把回调标记为async,这也会导致语法错误。

解决方案一:自定义文件上传拦截器

创建带依赖注入的自定义拦截器,在拦截器里先查询商品品牌,再动态配置multer的存储目录:

1. 编写自定义拦截器

import { Injectable, NestInterceptor, ExecutionContext, CallHandler, NotFoundException } from '@nestjs/common';
import { FilesInterceptor } from '@nestjs/platform-express';
import { diskStorage } from 'multer';
import { InjectRepository } from '@nestjs/typeorm';
import { Repository } from 'typeorm';
import { Product } from './your-product-entity-path'; // 替换成你的Product实体路径

@Injectable()
export class ProductImageUploadInterceptor implements NestInterceptor {
  constructor(
    @InjectRepository(Product)
    private readonly productRepository: Repository<Product>,
  ) {}

  async intercept(context: ExecutionContext, next: CallHandler) {
    const request = context.switchToHttp().getRequest();
    const productId = request.params.productId;
    
    // 查询商品品牌
    const product = await this.productRepository.findOne({
      where: { id: productId },
      select: { productBrand: true },
    });

    if (!product) {
      throw new NotFoundException('商品不存在');
    }

    const storageDir = `../products/${product.productBrand}`;

    // 动态创建FilesInterceptor并配置存储
    const fileInterceptor = FilesInterceptor('files', 1, {
      storage: diskStorage({
        destination: (req, file, cb) => {
          cb(null, storageDir);
        },
        filename: (req, file, cb) => {
          cb(null, file.originalname);
        },
      }),
    });

    return fileInterceptor.intercept(context, next);
  }
}

2. 在控制器中使用自定义拦截器

把原来的FilesInterceptor替换成自定义的拦截器:

@Post('product/image/:productId/')
@UseInterceptors(ProductImageUploadInterceptor) // 替换为自定义拦截器
@ApiBody({
  required: true,
  type: "multipart/form-data",
  schema: {
    type: "object",
    properties: {
      files: {
        type: "string",
        format: "binary",
      },
    },
  },
})
@ApiConsumes("multipart/form-data")
async uploadProductImage(
  @Param('productId') productId,
  @Res() res:Response,
  @UploadedFiles(
      new ParseFilePipeBuilder()
      .addFileTypeValidator({
          fileType: /(jpg|jpeg|png|gif)$/,
      })
      .addMaxSizeValidator({ maxSize: 20242880 })
      .build({
          errorHttpStatusCode: HttpStatus.UNPROCESSABLE_ENTITY
      })
  ) files: Array<Express.Multer.File>
){
  // 数据库存储逻辑...
}

3. 模块注册拦截器

记得在你的模块的providers数组里注册这个自定义拦截器,同时确保TypeOrmModule.forFeature([Product])已经导入:

@Module({
  imports: [TypeOrmModule.forFeature([Product])],
  providers: [ProductImageUploadInterceptor],
  controllers: [YourController],
})
export class YourModule {}

解决方案二:前置拦截器挂载商品品牌

先通过前置拦截器查询商品品牌并挂载到请求对象上,再在FilesInterceptor的回调里读取这个值:

1. 编写前置拦截器

import { Injectable, NestInterceptor, ExecutionContext, CallHandler, NotFoundException } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { Repository } from 'typeorm';
import { Product } from './your-product-entity-path';

@Injectable()
export class AttachProductBrandInterceptor implements NestInterceptor {
  constructor(
    @InjectRepository(Product)
    private readonly productRepository: Repository<Product>,
  ) {}

  async intercept(context: ExecutionContext, next: CallHandler) {
    const request = context.switchToHttp().getRequest();
    const productId = request.params.productId;
    
    const product = await this.productRepository.findOne({
      where: { id: productId },
      select: { productBrand: true },
    });

    if (!product) {
      throw new NotFoundException('商品不存在');
    }

    // 将品牌挂载到请求对象上
    request.productBrand = product.productBrand;
    return next.handle();
  }
}

2. 控制器中组合使用拦截器

先使用前置拦截器,再使用FilesInterceptor:

@Post('product/image/:productId/')
@UseInterceptors(
  AttachProductBrandInterceptor,
  FilesInterceptor('files', 1, {
    storage: diskStorage({
      destination: (req, file, cb) => {
        // 从请求对象读取之前挂载的品牌
        const storageDir = `../products/${req.productBrand}`;
        cb(null, storageDir);
      },
      filename: (req, file, cb) => {
        cb(null, file.originalname);
      },
    }),
  })
)
// ...其他装饰器和方法

这两种方案都能解决你的问题,第一种更紧凑,第二种逻辑拆分更清晰,你可以根据需求选择。

内容的提问来源于stack exchange,提问作者Hikaru Shindo

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.07 01:12:13