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
相关产品推荐
相关产品推荐

