如何使用graphql-java上传文件?
How to upload files with graphql-java?
如果我使用 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,所以我可以得到文件部分。
在我们的模式中定义一个标量类型
scalar Upload
我们应该为上传配置 GraphQLScalarType,在下面使用它:
@Configuration
public class GraphqlConfig {
@Bean
public GraphQLScalarType uploadScalarDefine() {
return ApolloScalars.Upload;
}
}
然后我们将在模式中定义一个突变,并为 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;
}
}
为javax.servlet.http.Part
配置一个jackson反序列化器并将其注册到ObjectMapper
public class PartDeserializer extends JsonDeserializer<Part> {
@Override
public Part deserialize(JsonParser p, DeserializationContext ctxt) throws IOException, JsonProcessingException {
return null;
}
}
为什么我们 return null?因为 List<Part> parts
始终为 null ,在解析器的方法中,从 DataFetchingEnvironment 获取 parts 参数;
environment.getArgument("files")
将其注册到 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;
}
- 为了测试这一点,post 将以下表单数据(我们使用 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
像这样:
记住 select 表单数据选项
通过这个我们可以上传多个文件
主要问题是 graphql-java-tools
可能无法为包含 List
、String
、Integer
等非基本类型字段的解析器进行字段映射, Boolean
, 等...
我们通过创建基本上类似于 ApolloScalar.Upload
的自定义标量解决了这个问题。但是我们 return 我们自己的解析器类型 FileUpload
不是 returning 类型 Part
的对象,它包含 String
的 contentType 和 String
的 inputStream 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
的自定义标量,但是 return 是我们自己的解析器类型 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
}
如果它对你不起作用,请告诉我:)
只是为了补充上面的答案,对于像我这样可以找到 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;
}
由于字节没有数据类型,我决定使用 String 类型发送 base64 中的数据。我解释一下,模式优先:
type Mutation{
uploadCSV(filedatabase64: String!): Boolean
}
spring开机:
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)
about base64 encoding/decoding in java 这篇文章很有帮助:https://www.baeldung.com/java-base64-encode-and-decode
如果我使用 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,所以我可以得到文件部分。
在我们的模式中定义一个标量类型
scalar Upload
我们应该为上传配置 GraphQLScalarType,在下面使用它:
@Configuration public class GraphqlConfig { @Bean public GraphQLScalarType uploadScalarDefine() { return ApolloScalars.Upload; } }
然后我们将在模式中定义一个突变,并为 testMultiFilesUpload
定义一个 GraphQLMutationResolvertype 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;
}
}
为
javax.servlet.http.Part
配置一个jackson反序列化器并将其注册到ObjectMapperpublic class PartDeserializer extends JsonDeserializer<Part> { @Override public Part deserialize(JsonParser p, DeserializationContext ctxt) throws IOException, JsonProcessingException { return null; } }
为什么我们 return null?因为
List<Part> parts
始终为 null ,在解析器的方法中,从 DataFetchingEnvironment 获取 parts 参数;environment.getArgument("files")
将其注册到 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;
}
- 为了测试这一点,post 将以下表单数据(我们使用 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
像这样:
记住 select 表单数据选项
通过这个我们可以上传多个文件
主要问题是 graphql-java-tools
可能无法为包含 List
、String
、Integer
等非基本类型字段的解析器进行字段映射, Boolean
, 等...
我们通过创建基本上类似于 ApolloScalar.Upload
的自定义标量解决了这个问题。但是我们 return 我们自己的解析器类型 FileUpload
不是 returning 类型 Part
的对象,它包含 String
的 contentType 和 String
的 inputStream 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
的自定义标量,但是 return 是我们自己的解析器类型 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
}
如果它对你不起作用,请告诉我:)
只是为了补充上面的答案,对于像我这样可以找到 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;
}
由于字节没有数据类型,我决定使用 String 类型发送 base64 中的数据。我解释一下,模式优先:
type Mutation{
uploadCSV(filedatabase64: String!): Boolean
}
spring开机:
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)
about base64 encoding/decoding in java 这篇文章很有帮助:https://www.baeldung.com/java-base64-encode-and-decode