NestJS 使用 GraphQL 上传

NestJS upload using GraphQL

有没有人有如何使用 GraphQl 在 NestJs 中上传文件的示例?

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

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

但我找不到任何关于如何在 NestJS 中使用 GrahpQL 上传的综合文档

编辑:根据 below, apollo-server now implements file upload。应该是首选方式。

以下为原答案,供参考

人们通常不会使用 GraphQL 进行上传。 GraphQL 很花哨 "specification of API",这意味着在一天结束时,低级 HTTP 请求和响应被翻译成 to/from JSON 个对象(如果您没有自定义传输)。

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

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

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

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

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

在回答这个问题时 FileInterceptor 使用 multer 并且通过将 ExecutionContext 转换为 http 它使用 getRequestgetResponsemulter.single 提供 reqres 的方法,它们在 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
    ...

您需要定义一个上传控制器并将其添加到您的 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));

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...
}

试试这个

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))
        );
    }
    
}

此实现与 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

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