2019年用了Graphql作为web项目api开发后,前端编程效率有了质的提升。Graphql使得数据的查询,修改等操作变得极其灵活,但Graphql 对于文件上传的支持不是很好,于是笔者花了点时间整理了一个解决方案。
Graphql Multipart Request 规范
为了统一graphql上传文件服务端和客户端接口,好在Apollo社区提出了一份规范jaydenseric/graphql-multipart-request-spec: A spec for GraphQL multipart form requests (file uploads).。
该规范定义了文件上传的数据格式。
文件上传定义为一个graphql的mutation 官方叫Operations。
- Operations 是一个Json对象,其中的files设为null。
- map: 也是一个Json对象定义了一次上传所有的文件,例如
{ "0": ["variables.files.0"], "1": ["variables.files.1"] }
- File fields: 是对应Operations中所有的文件。
工作原理
上传功能分为两部分服务端和客户端,分别按照规范实现接口。
客户端:在客户端,把上传的文件翻译成一个Operations然后发送一个multipart 请求给服务端。
服务端:服务端收到一个multipart请求,按照Operations的格式提取文件。
例子
首先定义服务端 graphql schema。
const { ApolloServer, gql } = require('apollo-server');
const typeDefs = gql`
type File {
filename: String!
mimetype: String!
encoding: String!
}
type Query {
uploads: [File]
}
type Mutation {
singleUpload(file: Upload!): File!
}
`;
其中 Upload为一个Scalar Type 不做任何序列化的操作。
const resolvers = {
Query: {
files: () => {
// Return the record of files uploaded from your DB or API or filesystem.
}
},
Mutation: {
async singleUpload(parent, { file }) {
const { stream, filename, mimetype, encoding } = await file;
// 1. Validate file metadata.
// 2. Stream file contents into cloud storage:
// https://nodejs.org/api/stream.html
// 3. Record the file upload in your DB.
// const id = await recordFile( … )
return { filename, mimetype, encoding };
}
},
};
以上为 resolver 部分的代码。
客户端代码,需要按照apollo-upload-client(https://github.com/jaydenseric/apollo-upload-client) 这个包。
const { ApolloClient } = require('apollo-client')
const { InMemoryCache } = require('apollo-cache-inmemory')
const { createUploadLink } = require('apollo-upload-client')
const client = new ApolloClient({
cache: new InMemoryCache(),
link: createUploadLink()
})
import gql from 'graphql-tag'
import { Mutation } from 'react-apollo'
export const UPLOAD_FILE = gql`
mutation uploadFile($file: Upload!) {
uploadFile(file: $file) {
filename
}
}
`;
const uploadOneFile = () => {
return (
<Mutation mutation={UPLOAD_FILE}>
{uploadFile => (
<input
type="file"
required
onChange={({ target: { validity, files: [file] } }) =>
validity.valid && uploadFile({ variables: { file } });
}
/>
)}
</Mutation>
);
};
apollo client 需要通过createUploadLink来配置,当一个 input file 触发onChange时候,发送文件上传mutation。
网友评论