【问题标题】:NestJS upload using GraphQL [closed]NestJS 使用 GraphQL 上传 [关闭]
【发布时间】:2018-08-08 14:30:03
【问题描述】:

有没有人举例说明如何使用 GraphQl 在 NestJs 中上传文件?

我可以通过控制器使用给定的示例上传

https://github.com/nestjs/nest/issues/262#issuecomment-366098589,

但我找不到任何全面的文档如何在 NestJS 中使用 GrahpQL 进行上传

【问题讨论】:

    标签: node.js upload graphql nestjs


    【解决方案1】:

    Apollo Server 2.0 现在应该可以做到这一点(封装在 Nest 中),虽然我需要安装 graphql-upload 并导入 GraphQLUpload,因为我找不到 Upload 类型:

    @Mutation(() => Image, { nullable: true })
    async addImage(@Args({ name: 'image', type: () => GraphQLUpload }) image) {
      // Do stuff with image...
    }
    

    【讨论】:

      【解决方案2】:

      在回答此问题时,FileInterceptor 正在使用 multer 并通过将 ExecutionContext 转换为 http 它使用 getRequestgetResponse 方法来提供 reqresmulter.single它们在 GraphQL 中未定义(reqres)。

      我尝试使用以下方法从上下文中获取请求:

      const ctx = GqlExecutionContext.create(context);
      

      ctx 中有 req 属性,但我找不到使用 multer 的方法(还)。

      无论如何,我对FileFieldsInterceptor 进行了一些更改以在我的项目中使用它,但是当我有时间清理它时,我可能会提出拉取请求:

      import { Observable } from 'rxjs';
      import {
        NestInterceptor,
        Optional,
        ExecutionContext,
        mixin,
      } from '@nestjs/common';
      import { GqlExecutionContext } from '@nestjs/graphql';
      import { storeFile } from './storeFile';
      
      interface IField {
        name: string;
        options?: any;
      }
      
      export function GraphqlFileFieldsInterceptor(
        uploadFields: IField[],
        localOptions?: any,
      ) {
        class MixinInterceptor implements NestInterceptor {
          options: any = {};
          constructor(@Optional() options: any = {}) {
            this.options = { ...options, ...localOptions };
          }
      
          async intercept(
            context: ExecutionContext,
            call$: Observable<any>,
          ): Promise<Observable<any>> {
            const ctx = GqlExecutionContext.create(context);
            const args = ctx.getArgs();
      
            let storeFilesResult = await Promise.all(
              uploadFields.map(uploadField => {
                const file = args[uploadField.name];
                return storeFile(file, {
                  ...uploadField.options,
                  ...this.options,
                }).then(address => {
                  args[uploadField.name] = address;
                  return address;
                });
              }),
            );
      
            return call$;
          }
        }
        const Interceptor = mixin(MixinInterceptor);
        return Interceptor;
      }
      

      和存储文件是这样的(可能不会这样使用):

      import uuid from 'uuid/v4';
      import fs from 'fs';
      import path from 'path';
      
      const dir = './files';
      if (!fs.existsSync(dir)) {
        fs.mkdirSync(dir);
      }
      
      export const storeFile = async (file, options): Promise<any> => {
        // options is not doing anything right now
        const { stream } = await file;
        const filename = uuid();
      
        const fileAddress = path.join(dir, filename + '.jpg');
        return new Promise((resolve, reject) =>
          stream
            .on('error', error => {
              if (stream.truncated)
                // Delete the truncated file
                fs.unlinkSync(fileAddress);
              reject(error);
            })
            .pipe(fs.createWriteStream(fileAddress))
            .on('error', error => reject(error))
            .on('finish', () => resolve(fileAddress)),
        );
      };
      

      在我的Cats.resolvers.ts

      ...
        @Mutation()
        @UseInterceptors(
          GraphqlFileFieldsInterceptor([
            { name: 'catImage1' },
            { name: 'catImage2' },
            { name: 'catImage3' },
          ]),
        )
        async cats(
          @Args('catImage1') catImage1: string,
          @Args('catImage2') catImage2: string,
          @Args('catImage3') catImage3: string,
        ){
          console.log(catImage1) // will print catImage1 address
          ...
      

      【讨论】:

        【解决方案3】:

        此实现与 Node >= v14 完美配合

        1. package.json

        如果添加了 fs-capacitor 和 graphql-upload 条目,请从解决方案部分删除它们,并安装最新版本的 graphql-upload(此时为 v11.0.0)包作为依赖项。

        1. src/app.module.ts

        禁用 Apollo Server 的内置上传处理并将 graphqlUploadExpress 中间件添加到您的应用程序。

        import { graphqlUploadExpress } from "graphql-upload"
        import { MiddlewareConsumer, Module, NestModule } from "@nestjs/common"
        
        @Module({
          imports: [
            GraphQLModule.forRoot({
              uploads: false, // disable built-in upload handling
            }),
          ],
        })
        export class AppModule implements NestModule {
          configure(consumer: MiddlewareConsumer) {
            consumer.apply(graphqlUploadExpress()).forRoutes("graphql")
          }
        }
        
        1. src/blog/post.resolver.ts(示例解析器)

        从 apollo-server-core 中移除 GraphQLUpload 导入,改为从 graphql-upload 导入

        import { FileUpload, GraphQLUpload } from "graphql-upload"
        
        @Mutation(() => Post)
        async postCreate(
          @Args("title") title: string,
          @Args("body") body: string,
          @Args("attachment", { type: () => GraphQLUpload }) attachment: Promise<FileUpload>,
        ) {
          const { filename, mimetype, encoding, createReadStream } = await attachment
          console.log("attachment:", filename, mimetype, encoding)
        
          const stream = createReadStream()
          stream.on("data", (chunk: Buffer) => /* do stuff with data here */)
        }
        

        来源: https://github.com/nestjs/graphql/issues/901#issuecomment-780007582

        我发现其他一些有用的链接:

        【讨论】:

          【解决方案4】:

          编辑:根据下面的Developia comment,apollo-server 现在实现了file upload。应该是首选方式。

          以下,原答案,供参考。

          通常不使用 GraphQL 进行上传。 GraphQL 是花哨的“API 规范”,这意味着最终,低级别的 HTTP 请求和响应会被转换为 JSON 对象或从 JSON 对象转换(如果您没有自定义传输)。

          一种解决方案可能是在 GraphQL 架构中定义特殊端点,例如:

          mutation Mutation {
            uploadFile(base64: String): Int
          }
          

          然后客户端会将二进制数据转换为 base64 字符串,这将在解析器端进行相应处理。这样,文件将成为 GraphQL 客户端和服务器之间交换的 JSON 对象的一部分。

          虽然这可能适合小文件,少量操作,但绝对不是上传服务的解决方案。

          【讨论】:

          • 如果您要上传多个文件或文件很大,则使用 Base64 上传文件是不可扩展的。现在可以使用当前合并到 apollo-server@2 中的 apollo-upload-server,因此它可以处理多部分表单数据。 Nestjs 提供 apollo-graphql@2 作为 gql 服务器,OP 要求提供文档,因为尽管(逻辑上)可能在 nestjs 文档中没有样本。
          【解决方案5】:

          试试这个

          import { Resolver, Mutation, Args } from '@nestjs/graphql';
          import { createWriteStream } from 'fs';
          
          import {GraphQLUpload} from "apollo-server-express"
          
          @Resolver('Download')
          export class DownloadResolver {
              @Mutation(() => Boolean)
              async uploadFile(@Args({name: 'file', type: () => GraphQLUpload})
              {
                  createReadStream,
                  filename
              }): Promise<boolean> {
                  return new Promise(async (resolve, reject) => 
                      createReadStream()
                          .pipe(createWriteStream(`./uploads/${filename}`))
                          .on('finish', () => resolve(true))
                          .on('error', () => reject(false))
                  );
              }
              
          }
          

          【讨论】:

            【解决方案6】:

            您可以使用 apollo-upload-server 库。在我看来,这似乎是最简单的事情。干杯

            【讨论】:

            • 这不是 OP 要求的答案。
            【解决方案7】:

            您需要定义一个上传控制器并将其添加到您的 app.module 中,这是一个控制器应该是(后端)的示例:

            @Controller()
            export class Uploader {
              @Post('sampleName')
              @UseInterceptors(FileInterceptor('file'))
              uploadFile(@UploadedFile() file) {
              // file name selection 
                const path = `desired path`;
                const writeStream = fs.createWriteStream(path);  
                writeStream.write(file.buffer);
                writeStream.end();
                return {
                  result: [res],
                };
              }
            }
            

            并在前端通过 fetch 调用您的控制器:

                fetch('controller address', {
                      method: 'POST',
                      body: data,
                    })
                      .then((response) => response.json())
                      .then((success) => {
                        // What to do when succeed 
            });
                      })
                      .catch((error) => console.log('Error in uploading file: ', error));
            

            【讨论】:

            • 这个答案没有使用 GraphQL。
            猜你喜欢
            • 1970-01-01
            • 1970-01-01
            • 2019-09-29
            • 2021-01-07
            • 2021-06-23
            • 2020-05-20
            • 2021-10-08
            • 2022-01-06
            • 2021-04-10
            相关资源
            最近更新 更多