You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Apollo v4文件上传报错:POST body缺失等问题求助

Apollo v4 文件上传问题解决方案

错误原因

  • 你遇到的第一个错误是因为文件上传请求采用multipart/form-data格式,但服务端配置了bodyParser.json(),无法解析该格式,导致Apollo无法识别请求体。
  • 第二个错误是手动强制设置Content-Type: application/json后,multipart请求的边界标识(如------WebKit...)无法被JSON解析器识别,引发格式错误。

解决步骤

1. 安装依赖

安装multer用于解析multipart类型的请求:

npm install multer

2. 修改服务端配置(Server.js)

替换原有的bodyParser.json(),改用multer处理multipart请求,并将上传的文件映射到GraphQL变量中:

// Server.js
import { ApolloServer } from '@apollo/server';
import { ApolloServerPluginDrainHttpServer } from '@apollo/server/plugin/drainHttpServer';
import { expressMiddleware } from '@apollo/server/express4';
import express from 'express';
import http from 'http';
import cors from 'cors';
import multer from 'multer';
import { makeExecutableSchema } from '@graphql-tools/schema';
import { typeDefs, resolvers } from "./schema.js";

// 配置multer使用内存存储,临时存放上传文件
const storage = multer.memoryStorage();
const upload = multer({ storage: storage });

async function startServer() {
    const schema = makeExecutableSchema({ resolvers, typeDefs });
    const app = express();
    const httpServer = http.createServer(app);
    const server = new ApolloServer({
        typeDefs,
        resolvers,
        plugins: [ApolloServerPluginDrainHttpServer({ httpServer })],
    });
    await server.start();

    // 自定义中间件,区分处理multipart和普通JSON请求
    app.use('/', cors(), (req, res, next) => {
        if (req.is('multipart/form-data')) {
            upload.any()(req, res, () => {
                // 解析GraphQL multipart格式的operations和map字段
                const operations = JSON.parse(req.body.operations);
                const map = JSON.parse(req.body.map);
                
                // 将上传文件映射到对应的GraphQL变量
                Object.keys(map).forEach(fileKey => {
                    const variablePath = map[fileKey][0];
                    const [, varName] = variablePath.split('.');
                    const file = req.files.find(f => f.fieldname === fileKey);
                    if (file && operations.variables) {
                        operations.variables[varName] = file;
                    }
                });
                
                // 替换请求体为解析后的operations,供Apollo处理
                req.body = operations;
                next();
            });
        } else {
            // 非文件上传请求使用JSON解析
            express.json()(req, res, next);
        }
    });

    app.use(
        '/',
        expressMiddleware(server, {
            context: async ({ req }) => ({ token: req.headers.token }),
        }),
    );

    await new Promise((resolve) => httpServer.listen({ port: 4000 }, resolve));
    console.log(`🚀 Server ready at http://localhost:4000/`);
}
startServer();

3. 完善Upload标量解析(resolvers)

在resolvers中添加Upload标量的解析逻辑,确保文件对象能正确传递到mutation中:

// mutation.js
export default {
    Upload: {
        __parseValue: (value) => value, // 直接返回multer处理后的文件对象
        __serialize: (value) => value,
        __parseLiteral: (ast) => null, // 字面量解析无需处理,返回null
    },
    Mutation: {
        createPost: async (_, { title, content, userId, categoryId, file }) => {
            // file为multer的File对象,包含buffer、originalname、size等属性
            console.log('上传的文件信息:', file);
            // 此处可添加文件存储、数据库写入等业务逻辑
            return { ok: true };
        }
    }
}

4. Altair测试注意事项

  • 不要手动设置Content-Type,Altair在选择文件后会自动设置为multipart/form-data
  • 在变量面板中,将file字段的类型设置为File,并选择本地文件
  • 使用如下mutation模板测试:
mutation CreatePost($title: String!, $content: String!, $userId: Int!, $categoryId: Int!, $file: Upload) {
    createPost(title: $title, content: $content, userId: $userId, categoryId: $categoryId, file: $file) {
        ok
    }
}

内容的提问来源于stack exchange,提问作者tr_korea

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.13 01:39:57