Nestjs - 使用 fastify multipart 上传文件

问题描述 投票:0回答:3

我正在尝试使用 fastify 适配器使用 Nestjs 上传多个文件。我可以按照此链接中的教程进行操作 -上传文章

现在这使用 fastify-multipart 完成了文件上传的工作,但我无法在上传之前使用请求验证, 例如,这是我的规则文件模型(后来我想将其保存到 postgre)

    import {IsUUID, Length, IsEnum, IsString, Matches, IsOptional} from "class-validator";
    import { FileExtEnum } from "./enums/file-ext.enum";
    import { Updatable } from "./updatable.model";
    import {Expose, Type} from "class-transformer";
    
    export class RuleFile {
      @Expose()
      @IsUUID("4", { always: true })
      id: string;
    
      @Expose()
      @Length(2, 50, {
        always: true,
        each: true,
        context: {
          errorCode: "REQ-000",
          message: `Filename shouldbe within 2 and can reach a max of 50 characters`,
        },
      })
      fileNames: string[];
    
      @Expose()
      @IsEnum(FileExtEnum, { always: true, each: true })
      fileExts: string[];
    
      @IsOptional({each: true, message: 'File is corrupated'})
      @Type(() => Buffer)
      file: Buffer;
    }
    
    export class RuleFileDetail extends RuleFile implements Updatable {
      @IsString()
      @Matches(/[aA]{1}[\w]{6}/)
      recUpdUser: string;
    }

我想验证多部分请求并查看这些设置是否正确。 我无法使其与基于事件订阅的方法一起使用。以下是我尝试过的一些事情 - 添加拦截器,以检查请求

    @Injectable()
    export class FileUploadValidationInterceptor implements NestInterceptor {
      intercept(context: ExecutionContext, next: CallHandler): Observable<any> {
    
        const req: FastifyRequest = context.switchToHttp().getRequest();
        console.log('inside interceptor', req.body);
        // content type cmes with multipart/form-data;boundary----. we dont need to valdidate the boundary
        // TODO: handle split errors based on semicolon
        const contentType = req.headers['content-type'].split(APP_CONSTANTS.CHAR.SEMI_COLON)[0];
    
        console.log(APP_CONSTANTS.REGEX.MULTIPART_CONTENT_TYPE.test(contentType));
        const isHeaderMultipart = contentType != null?
            this.headerValidation(contentType): this.throwError(contentType);
        
      **// CANNOT check fir req.file() inside this, as it throws undefined**
        return next.handle();
      }
    
      headerValidation(contentType) {
        return APP_CONSTANTS.REGEX.MULTIPART_CONTENT_TYPE.test(contentType) ? true : this.throwError(contentType);
      }
      throwError(contentType: string) {
        throw AppConfigService.getCustomError('FID-HEADERS', `Request header does not contain multipart type: 
        Provided incorrect type - ${contentType}`);
      }
    }

我无法在上面的拦截器中检查 req.file() 。它抛出未定义。我尝试遵循 fastify-multipart

但是我无法按照 fastify-multipart 文档中提供的方式在预处理程序中获取请求数据

    fastify.post('/', async function (req, reply) {
      // process a single file
      // also, consider that if you allow to upload multiple files
      // you must consume all files othwise the promise will never fulfill
      const data = await req.file()
     
      data.file // stream
      data.fields // other parsed parts
      data.fieldname
      data.filename
      data.encoding
      data.mimetype
     
      // to accumulate the file in memory! Be careful!
      //
      // await data.toBuffer() // Buffer
      //
      // or
     
      await pump(data.file, fs.createWriteStream(data.filename))

我尝试通过像这样注册我自己的预处理程序钩子(作为 iife 执行)

    (async function bootstrap() {
      const appConfig = AppConfigService.getAppCommonConfig();
      const fastifyInstance = SERVERADAPTERINSTANCE.configureFastifyServer();
      // @ts-ignore
      const fastifyAdapter = new FastifyAdapter(fastifyInstance);
      app = await NestFactory.create<NestFastifyApplication>(
        AppModule,
        fastifyAdapter
      ).catch((err) => {
        console.log("err in creating adapter", err);
        process.exit(1);
      });
    
      .....
      app.useGlobalPipes(
        new ValidationPipe({
          errorHttpStatusCode: 500,
          transform: true,
          validationError: {
            target: true,
            value: true,
          },
          exceptionFactory: (errors: ValidationError[]) => {
            // send it to the global exception filter\
            AppConfigService.validationExceptionFactory(errors);
          },
        }),
    
      );
      
      app.register(require('fastify-multipart'), {
        limits: {
          fieldNameSize: 100, // Max field name size in bytes
          fieldSize: 1000000, // Max field value size in bytes
          fields: 10, // Max number of non-file fields
          fileSize: 100000000000, // For multipart forms, the max file size
          files: 3, // Max number of file fields
          headerPairs: 2000, // Max number of header key=>value pairs
        },
      });
    
      
    
      (app.getHttpAdapter().getInstance() as FastifyInstance).addHook('onRoute', (routeOptions) => {
        console.log('all urls:', routeOptions.url);
    
        if(routeOptions.url.includes('upload')) {

    // The registration actually works, but I cant use the req.file() in the prehandler
          console.log('###########################');
          app.getHttpAdapter().getInstance().addHook('preHandler', FilePrehandlerService.fileHandler);
        }
    
      });
    
      SERVERADAPTERINSTANCE.configureSecurity(app);
    
      //Connect to database
      await SERVERADAPTERINSTANCE.configureDbConn(app);
    
      app.useStaticAssets({
        root: join(__dirname, "..", "public"),
        prefix: "/public/",
      });
      app.setViewEngine({
        engine: {
          handlebars: require("handlebars"),
        },
        templates: join(__dirname, "..", "views"),
      });
    
      await app.listen(appConfig.port, appConfig.host, () => {
        console.log(`Server listening on port - ${appConfig.port}`);
      });
    })();

这是预处理程序,

    export class FilePrehandlerService {
      constructor() {}
    
      static fileHandler = async (req, reply) => {
          console.log('coming inside prehandler');
    
              console.log('req is a multipart req',await req.file);
              const data = await req.file();
              console.log('data received -filename:', data.filename);
              console.log('data received- fieldname:', data.fieldname);
              console.log('data received- fields:', data.fields);
    
          return;
      };
    }

这种使用 preHandler 注册和获取文件的模式适用于裸 fastify 应用程序。我试过了

裸fastify服务器:

    export class FileController {
        constructor() {}
    
        async testHandler(req: FastifyRequest, reply: FastifyReply) {
            reply.send('test reading dne');
        }
    
        async fileReadHandler(req, reply: FastifyReply) {
            const data = await req.file();
    
            console.log('field val:', data.fields);
            console.log('field filename:', data.filename);
            console.log('field fieldname:', data.fieldname);
            reply.send('done');
        }
    }
    
    export const FILE_CONTROLLER_INSTANCE = new FileController();

这是我的路线文件

    const testRoute: RouteOptions<Server, IncomingMessage, ServerResponse, RouteGenericInterface, unknown> = {
        method: 'GET',
        url: '/test',
        handler: TESTCONTROLLER_INSTANCE.testMethodRouteHandler,
    };
    
    const fileRoute: RouteOptions = {
        method: 'GET',
        url: '/fileTest',
        preHandler: fileInterceptor,
        handler: FILE_CONTROLLER_INSTANCE.testHandler,
    };
    
    const fileUploadRoute: RouteOptions = {
        method: 'POST',
        url: '/fileUpload',
        preHandler: fileInterceptor,
        handler: FILE_CONTROLLER_INSTANCE.fileReadHandler,
    };
    
    const apiRoutes = [testRoute, fileRoute, fileUploadRoute];
    export default apiRoutes;

有人可以让我知道获取字段名称的正确方法,并在 Nestjs 中调用服务之前验证它们

file-upload nestjs fastify
3个回答
22
投票

嗯,我已经做了类似的事情,它对我来说非常有用。也许它也适合你。

// main.ts
import multipart from "fastify-multipart";

const app = await NestFactory.create<NestFastifyApplication>(
    AppModule,
    new FastifyAdapter(),
);
app.register(multipart);
// upload.guard.ts
import {
    Injectable,
    CanActivate,
    ExecutionContext,
    BadRequestException,
} from "@nestjs/common";
import { FastifyRequest } from "fastify";

@Injectable()
export class UploadGuard implements CanActivate {
    public async canActivate(ctx: ExecutionContext): Promise<boolean> {
        const req = ctx.switchToHttp().getRequest() as FastifyRequest;
        const isMultipart = req.isMultipart();
        if (!isMultipart)
            throw new BadRequestException("multipart/form-data expected.");
        const file = await req.file();
        if (!file) throw new BadRequestException("file expected");
        req.incomingFile = file;
        return true;
    }
}
// file.decorator.ts
import { createParamDecorator, ExecutionContext } from "@nestjs/common";
import { FastifyRequest } from "fastify";

export const File = createParamDecorator(
    (_data: unknown, ctx: ExecutionContext) => {
        const req = ctx.switchToHttp().getRequest() as FastifyRequest;
        const file = req.incomingFile;
        return file
    },
);
// post controller
@Post("upload")
@UseGuards(UploadGuard)
uploadFile(@File() file: Storage.MultipartFile) {
    console.log(file); // logs MultipartFile from "fastify-multipart"
    return "File uploaded"
}

最后是我的打字文件

declare global {
    namespace Storage {
        interface MultipartFile {
            toBuffer: () => Promise<Buffer>;
            file: NodeJS.ReadableStream;
            filepath: string;
            fieldname: string;
            filename: string;
            encoding: string;
            mimetype: string;
            fields: import("fastify-multipart").MultipartFields;
        }
    }
}

declare module "fastify" {
    interface FastifyRequest {
        incomingFile: Storage.MultipartFile;
    }
}

1
投票

所以我找到了一个更简单的替代方案。我开始使用 fastify-multer。我将它与这个很棒的库一起使用 - 这使我使用 multer 进行 fastify - @webundsoehne/nest-fastify-file-upload

这些是我所做的改变。我注册了 multer content 流程。

app.register(multer( {dest:path.join(process.cwd()+'/upload'),
        limits:{
            fields: 5, //Number of non-file fields allowed
            files: 1,
            fileSize: 2097152,// 2 MB,
        }}).contentParser);

然后在控制器中 - 我按照 Nestjs 文档所述使用它。这实际上使得 fasitfy 可以与 multer 一起工作

@UseInterceptors(FileUploadValidationInterceptor, FileInterceptor('file'))
      @Post('/multerSample')
      async multerUploadFiles(@UploadedFile() file, @Body() ruleFileCreate: RuleFileCreate) {
        console.log('data sent', ruleFileCreate);
        console.log(file);
        // getting the original name of the file - no matter what
        ruleFileCreate.originalName = file.originalname;
        return await this.fileService.fileUpload(file.buffer, ruleFileCreate);
}

奖励 - 将文件存储在本地并将其存储在数据库中 - 请参考

github 链接


0
投票

我使用了

fastify-file-interceptor
npm 包来实现与 NestJS-Express 框架中通常使用的相同功能。请参考以下代码(专门针对文件上传)获取多部分详细信息。

// main.ts

import { NestFactory } from '@nestjs/core';
import { contentParser } from 'fastify-file-interceptor';
import {
  FastifyAdapter,
  NestFastifyApplication,
} from '@nestjs/platform-fastify';
import { AppModule } from './app.module';

async function main() {
  const app = await NestFactory.create<NestFastifyApplication>(
    AppModule,
    new FastifyAdapter(),
  );
  // Important thing for multipart request
  app.register(contentParser);
  
  await app.listen(4000, '0.0.0.0');
}
main();

控制器代码

// file controller

import {
    Controller,
    Post,
    UploadedFile,
    UseInterceptors,
  } from '@nestjs/common';
  import { FileFastifyInterceptor } from 'fastify-file-interceptor';
  
  
  @Controller('upload-files')
  export class UploadFileController {
    constructor() {}
  
    @Post()
    @UseInterceptors(
      FileFastifyInterceptor('file', {
        limits: { files: 1 },
      }),
    )
    async uploadFile(@UploadedFile() file) {
     // here you will get all the metadata for your file   
     const { fieldname, originalname, encoding, mimetype, buffer, size } = file;
     
     // <Your code for file upload>
    }
  }

注意:它是经过尝试和测试的解决方案。

© www.soinside.com 2019 - 2024. All rights reserved.