6

如果我使用graphql-java,我不知道如何上传文件,有人可以给我演示吗?我将不胜感激!

参考: https ://github.com/graphql-java-kickstart/graphql-java-tools/issues/240

我在springboot中使用graphql-java-kickstart graphql-java-tools试过了,但是没有用

@Component
public class FilesUpload implements GraphQLMutationResolver {

    public Boolean testMultiFilesUpload(List<Part> parts, DataFetchingEnvironment env) {
        // get file parts from DataFetchingEnvironment, the parts parameter is not used
        List<Part> attchmentParts = env.getArgument("files");
        System.out.println(attchmentParts);
        return true;
    }
}

这是我的架构

type Mutation {
    testSingleFileUpload(file: Upload): UploadResult
}

我希望这个解析器可以打印attchmentParts,所以我可以获得文件部分。

4

4 回答 4

12
  1. 在我们的模式中定义一个标量类型

    scalar Upload

    我们应该为 Upload 配置 GraphQLScalarType,在下面使用这个:

    @Configuration
    public class GraphqlConfig {
    
       @Bean
       public GraphQLScalarType uploadScalarDefine() {
          return ApolloScalars.Upload;
       } 
    }
    
  2. 然后我们将在模式中定义一个突变,并为 testMultiFilesUpload 定义一个 GraphQLMutationResolver

    type Mutation {
      testMultiFilesUpload(files: [Upload!]!): Boolean
    }
    

这是解析器:

public Boolean testMultiFilesUpload(List<Part> parts, DataFetchingEnvironment env) {
    // get file parts from DataFetchingEnvironment, the parts parameter is not use
    List<Part> attachmentParts = env.getArgument("files");
    int i = 1;
    for (Part part : attachmentParts) {
      String uploadName = "copy" + i;
      try {
        part.write("your path:" + uploadName);
      } catch (IOException e) {
        e.printStackTrace();
      }
      i++;
    }
    return true;   
  }
}
  1. 配置杰克逊反序列化器javax.servlet.http.Part并将其注册到 ObjectMapper

    public class PartDeserializer extends JsonDeserializer<Part> {
    
      @Override
      public Part deserialize(JsonParser p, DeserializationContext ctxt) throws IOException, JsonProcessingException {         
         return null;
      }
    }
    

    为什么我们返回null?因为List<Part> parts总是null,所以在resolver的方法中,从DataFetchingEnvironment中获取parts参数;

    environment.getArgument(“文件”)

将其注册到 ObjectMapper:

@Bean
public ObjectMapper objectMapper() {
  ObjectMapper objectMapper = new ObjectMapper();
  objectMapper.configure(SerializationFeature.FAIL_ON_EMPTY_BEANS, false);
  SimpleModule module = new SimpleModule();
  module.addDeserializer(Part.class, new PartDeserializer());
  objectMapper.registerModule(module);
  return objectMapper;
}
  1. 要对此进行测试,请将以下表单数据(我们使用 Postman)发布到 GraphQL 端点
operations

{ "query": "mutation($files: [Upload!]!) {testMultiFilesUpload(files:$files)}", "variables": {"files": [null,null] } }

map

{ "file0": ["variables.files.0"] , "file1":["variables.files.1"]}

file0

your file

file1

your file

像这样:

记得选择表单数据选项 在此处输入图像描述

通过这个我们可以上传多个文件

于 2019-08-08T02:20:51.963 回答
8

主要问题是,graphql-java-tools对于包含非基本类型(如ListStringIntegerBoolean等)的字段的解析器进行字段映射可能会有问题...

我们通过创建我们自己的自定义标量来解决这个问题,基本上就像ApolloScalar.Upload. 但是,我们不是返回 type 的对象Part,而是返回我们自己的解析器类型FileUpload,其中包含 contentType asString和 inputStream as byte[],然后字段映射工作,我们可以byte[]在解析器中读取。

首先,设置要在解析器中使用的新类型:

public class FileUpload {
    private String contentType;
    private byte[] content;

    public FileUpload(String contentType, byte[] content) {
        this.contentType = contentType;
        this.content = content;
    }

    public String getContentType() {
        return contentType;
    }

    public byte[] getContent() {
        return content;
    }
}

然后我们制作一个看起来很像的自定义标量ApolloScalars.Upload,但返回我们自己的解析器类型FileUpload

public class MyScalars {
    public static final GraphQLScalarType FileUpload = new GraphQLScalarType(
        "FileUpload",
        "A file part in a multipart request",
        new Coercing<FileUpload, Void>() {

            @Override
            public Void serialize(Object dataFetcherResult) {
                throw new CoercingSerializeException("Upload is an input-only type");
            }

            @Override
            public FileUpload parseValue(Object input) {
                if (input instanceof Part) {
                    Part part = (Part) input;
                    try {
                        String contentType = part.getContentType();
                        byte[] content = new byte[part.getInputStream().available()];
                        part.delete();
                        return new FileUpload(contentType, content);

                    } catch (IOException e) {
                        throw new CoercingParseValueException("Couldn't read content of the uploaded file");
                    }
                } else if (null == input) {
                    return null;
                } else {
                    throw new CoercingParseValueException(
                            "Expected type " + Part.class.getName() + " but was " + input.getClass().getName());
                }
            }

            @Override
            public FileUpload parseLiteral(Object input) {
                throw new CoercingParseLiteralException(
                        "Must use variables to specify Upload values");
            }
    });
}

在解析器中,您现在可以从解析器参数中获取文件:

public class FileUploadResolver implements GraphQLMutationResolver {

    public Boolean uploadFile(FileUpload fileUpload) {

        String fileContentType = fileUpload.getContentType();
        byte[] fileContent = fileUpload.getContent();

        // Do something in order to persist the file :)


        return true;
    }
}

在模式中,您将其声明为:

scalar FileUpload

type Mutation {
    uploadFile(fileUpload: FileUpload): Boolean
}

让我知道它是否不适合你:)

于 2019-10-24T14:58:42.147 回答
1

只是添加到上面的答案中,对于像我这样可以找到 0 个使用 GraphQLSchemaGenerator 与模式优先方法的文件上传示例的人,您只需创建一个 TypeMapper 并将其添加到您的 GraphQLSchemaGenerator 中:

public class FileUploadMapper implements TypeMapper {

  @Override
  public GraphQLOutputType toGraphQLType(
      final AnnotatedType javaType, final OperationMapper operationMapper,
      final Set<Class<? extends TypeMapper>> mappersToSkip, final BuildContext buildContext) {
    return MyScalars.FileUpload;
  }

  @Override
  public GraphQLInputType toGraphQLInputType(
      final AnnotatedType javaType, final OperationMapper operationMapper,
      final Set<Class<? extends TypeMapper>> mappersToSkip, final BuildContext buildContext) {
    return MyScalars.FileUpload;
  }

  @Override
  public boolean supports(final AnnotatedType type) {
     return type.getType().equals(FileUpload.class); //class of your fileUpload POJO from the previous answer
  }
}

然后在您构建 GraphQLSchema 的 GraphQL @Configuration 文件中:

public GraphQLSchema schema(GraphQLSchemaGenerator schemaGenerator) {
    return schemaGenerator
        .withTypeMappers(new FileUploadMapper()) //add this line
        .generate();
  }

然后在你的变异解析器中

  @GraphQLMutation(name = "fileUpload")
  public void fileUpload(      
      @GraphQLArgument(name = "file") FileUpload fileUpload //type here must be the POJO.class referenced in your TypeMapper
  ) {
    //do something with the byte[] from fileUpload.getContent();
    return;
  }
于 2020-05-14T21:04:50.903 回答
0

由于字节没有数据类型,我决定使用 String 类型在base64中发送数据。我先解释一下架构:

type Mutation{ 
  uploadCSV(filedatabase64: String!): Boolean
}

弹簧靴:

public DataFetcher<Boolean> uploadCSV() { 
    return dataFetchingEnvironment -> {
        String input= dataFetchingEnvironment.getArgument("filedatabase64");
        byte[] bytes = Base64.getDecoder().decode(input);
        //in my case is textfile:
        String strCSV = new String(bytes);
        //....
        return true;
    };
}

Http 客户端发送者,例如在 python3 中:

import requests
import base64
import json

with open('myfile.csv', 'r',encoding='utf-8') as file:
    content = file.read().rstrip()
file.close()
    
base64data = base64.b64encode(content.encode()).decode()
url = 'https://www.misite/graphql/'
query = "mutation{uploadCSV(filedatabase64:\""+base64data+"\")}"
r = requests.post(url, json={'query': query})
print("response " + r.status_code + " " + r.text)
    

演示捕获

关于java中的base64编码/解码这篇文章很有帮助:https ://www.baeldung.com/java-base64-encode-and-decode

于 2022-02-11T07:57:37.577 回答