How to upload files with graphql-java?

荒凉一梦 提交于 2019-12-01 03:51:30

问题


I can't find out how to upload files if i use graphql-java, can someone show me a demo? I will be appreciated!

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

I tried it in springboot by using graphql-java-kickstart graphql-java-tools, but it didn't work

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

this is my schema

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

I expect this resolver can print attchmentParts,so i can get the file part.


回答1:


  1. define a scalar type in our schema

    scalar Upload

    and we should configure GraphQLScalarType for Upload, use this below:

    @Configuration

    public class GraphqlConfig {

    @Bean
    public GraphQLScalarType uploadScalarDefine() {
        return ApolloScalars.Upload;
    }
    

    }

  2. then we would define a mutation in schema and a GraphQLMutationResolver for testMultiFilesUpload

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

here is Resolver:

  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. configure a jackson deserializer for javax.servlet.http.Part and register it to ObjectMapper

public class PartDeserializer extends JsonDeserializer {

    @Override
    public Part deserialize(JsonParser p, DeserializationContext ctxt) throws IOException, JsonProcessingException {

        return null;
    }


}

why we return null? because the List<Part> parts always null ,In the resolver's method, get the parts argument from the DataFetchingEnvironment;

environment.getArgument("files")

register it to 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. To test this, post the following form data (we use Postman) to GraphQL endpoint
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

like this:

remember to select the form-data option

through this we can upload multiple files




回答2:


The main problem is that graphql-java-tools might have issues to do the field mapping for resolvers that contain fields of not basic types like List, String, Integer, Boolean, etc...

We solved this issue by just creating our own custom scalar that is basically like ApolloScalar.Upload. But instead of returning an object of the type Part, we return our own resolver type FileUpload which contains the contentType as String and the inputStream as byte[], then the field mapping works and we can read the byte[] within the resolver.

First, set up the new type to be used in the resolver:

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

Then we make a custom scalar that looks pretty much like ApolloScalars.Upload, but returns our own resolver type 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");
            }
    });
}

In the resolver, you would now be able to get the file from the resolver arguments:

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

In the schema, you declare it like:

scalar FileUpload

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

Let me know if it doesn't work for you :)



来源:https://stackoverflow.com/questions/57372259/how-to-upload-files-with-graphql-java

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!