Nestjs-带有分段固定功能的文件上传

问题描述

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

在这可以使用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应用程序中使用。我试过了

裸机服务器:

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',handler: FILE_CONTROLLER_INSTANCE.fileReadHandler,};

const apiRoutes = [testRoute,fileRoute,fileUploadRoute];
export default apiRoutes;

有人可以让我知道获取字段名称的正确方法,然后在nestjs中调用的服务之前对其进行验证

解决方法

嗯,我做过这样的事情,它对我很有用。也许它也适合你。

// 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;
    }
}
,

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

这些是我所做的更改。我注册了multer内容流程。

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文档所说使用它。实际上,这使得与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 link

相关问答

Selenium Web驱动程序和Java。元素在(x,y)点处不可单击。其...
Python-如何使用点“。” 访问字典成员?
Java 字符串是不可变的。到底是什么意思?
Java中的“ final”关键字如何工作?(我仍然可以修改对象。...
“loop:”在Java代码中。这是什么,为什么要编译?
java.lang.ClassNotFoundException:sun.jdbc.odbc.JdbcOdbc...