使用GraphQL与Dropzone上传文件遇400错误求助
我帮你排查下这个文件上传的400错误问题,结合你用的apollo-server-express和前端代码来看,大概率是文件上传的请求格式处理或者Apollo客户端/服务端配置出了问题,下面一步步来解决:
1. 先检查前端Apollo客户端的文件上传配置
GraphQL文件上传需要使用multipart/form-data格式发送请求,默认的Apollo客户端并不支持这个,你需要安装并配置apollo-upload-client:
- 首先安装依赖:
npm install apollo-upload-client - 然后修改你的Apollo客户端初始化代码,替换默认的
HttpLink为createUploadLink:import { createUploadLink } from 'apollo-upload-client'; import { ApolloClient, InMemoryCache } from '@apollo/client'; const client = new ApolloClient({ link: createUploadLink({ uri: '/graphql', // 替换成你的GraphQL API地址 }), cache: new InMemoryCache(), });
如果没做这个配置,前端还是会用JSON格式发送请求,服务端无法解析文件,直接返回400错误。
2. 检查服务端的文件上传中间件配置
注意:apollo-server-express从v3版本开始,已经移除了内置的文件上传支持,需要手动安装graphql-upload并配置中间件:
- 安装依赖:
npm install graphql-upload - 在Express应用中添加文件上传中间件(必须在ApolloServer启动前添加):
import { ApolloServer } from 'apollo-server-express'; import { graphqlUploadExpress } from 'graphql-upload'; import express from 'express'; import fs from 'fs'; const app = express(); // 先添加文件上传中间件,配置最大文件大小等参数 app.use(graphqlUploadExpress({ maxFileSize: 10 * 1024 * 1024, maxFiles: 10 })); const server = new ApolloServer({ typeDefs, // 你的schema定义 resolvers, // 你的resolver csrfPrevention: true, cache: 'bounded', }); await server.start(); server.applyMiddleware({ app });
如果服务端没配置这个中间件,收到multipart请求时无法正确解析,就会返回400错误。
3. 确认前端传递的是原生File对象
你用的dropzone组件,@vdropzone-file-added回调拿到的file可能是组件包装后的对象,而非原生的File对象。可以尝试打印file.raw(很多dropzone组件会把原生File存到raw属性里),并传递这个原生对象:
sendingFile(file) { // 拿到原生File对象 const nativeFile = file.raw || file; console.log(nativeFile); this.$store .dispatch('post/uploadPostImage', nativeFile) .then((response) => { console.log('Succes : Upload file') }) .catch(() => { console.log('Error') }) },
如果传递的不是原生File对象,Apollo上传客户端无法正确序列化文件,也会导致请求格式错误。
4. 检查服务端Resolver的实现
确保你的uploadFileResolver正确处理了Upload类型(Upload是一个异步类型,需要await获取文件流):
import { GraphQLUpload } from 'graphql-upload'; const resolvers = { // 必须定义Upload标量类型 Upload: GraphQLUpload, Mutation: { uploadFile: async (parent, { file }) => { // 解析Upload对象,拿到文件流和元数据 const { createReadStream, filename, mimetype, encoding } = await file; // 这里可以处理文件流,比如保存到本地或者云存储 const stream = createReadStream(); const savePath = `./uploads/${filename}`; // 处理文件保存的异步操作 await new Promise((resolve, reject) => { stream.pipe(fs.createWriteStream(savePath)) .on('finish', resolve) .on('error', reject); }); return { filename, mimetype, encoding }; }, }, };
如果Resolver没有正确await解析Upload对象,也会导致服务端报错,返回400。
按照上面的步骤逐一排查,应该就能解决你的400错误问题了。
内容的提问来源于stack exchange,提问作者Jérémie Chazelle

