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

Express+Typescript项目文件上传遇"Unexpected end of form"错误求解

解决Express + no-stress + TypeScript应用中的文件上传问题(multer报错"Unexpected end of form")

你遇到的这个Unexpected end of form错误,核心原因其实很明确:body-parser中间件提前处理了multipart/form-data类型的请求,把multer需要读取的原始数据流破坏了,导致它无法正确解析完整的表单内容。结合你的代码,我给你一套完整的修复方案:

1. 先解决body-parser的干扰问题

在你的server/common/server.ts里,你同时使用了bodyParser.urlencoded()和bodyParser.text(),这些中间件会默认尝试解析所有请求的body——包括multipart/form-data格式的文件上传请求。这就相当于在multer拿到请求之前,已经有人把表单数据拆过一遍了,自然会报错。

修改server/common/server.ts里的解析器配置,明确指定只处理非multipart类型的请求:

import express, { Application } from 'express';
import path from 'path';
import cookieParser from 'cookie-parser';
import http from 'http';
import os from 'os';
import l from './logger';

import errorHandler from '../api/middlewares/error.handler';
import * as OpenApiValidator from 'express-openapi-validator';

const app = express();

export default class ExpressServer {
  private routes: (app: Application) => void;
  constructor() {
    const root = path.normalize(__dirname + '/../..');
    
    // 改用express内置解析器(express4.16+已内置body-parser功能),明确限定解析类型
    app.use(express.json({ limit: process.env.REQUEST_LIMIT || '100kb' }));
    app.use(
      express.urlencoded({
        extended: true,
        limit: process.env.REQUEST_LIMIT || '100kb',
        // 关键:只解析application/x-www-form-urlencoded类型,跳过multipart
        type: 'application/x-www-form-urlencoded'
      })
    );
    app.use(express.text({ 
      limit: process.env.REQUEST_LIMIT || '100kb',
      // 只解析text/plain类型
      type: 'text/plain'
    }));
    
    app.use(cookieParser(process.env.SESSION_SECRET));
    app.use(express.static(`${root}/public`));

    const apiSpec = path.join(__dirname, 'api.yml');
    const validateResponses = !!(
      process.env.OPENAPI_ENABLE_RESPONSE_VALIDATION &&
      process.env.OPENAPI_ENABLE_RESPONSE_VALIDATION.toLowerCase() === 'true'
    );
    app.use(process.env.OPENAPI_SPEC || '/spec', express.static(apiSpec));
    app.use(
      OpenApiValidator.middleware({
        apiSpec,
        validateResponses,
        ignorePaths: /.*\/spec(\/|$)/,
      })
    );
  }

  // ... 其余代码保持不变
}

2. 优化multer的使用方式(避免重复初始化)

你现在在控制器的post方法里每次都重新创建multer实例,这不仅不是最佳实践,还可能引发潜在问题。建议把multer配置抽成独立的中间件:

第一步:创建multer配置文件

新建server/common/multer-config.ts:

import multer from 'multer';
import path from 'path';
import { Request } from 'express';
import fs from 'fs';

const storage = multer.diskStorage({
  destination: (req: Request, file, cb) => {
    const dirPath = path.join('/tmp', req.params.dir);
    // 自动创建目标目录(如果不存在的话),避免报错
    fs.mkdirSync(dirPath, { recursive: true });
    cb(null, dirPath);
  },
  filename: (_req, file, cb) => {
    console.log(`File will be saved as: ${file.originalname}`);
    cb(null, file.originalname);
  }
});

export const uploadSingleDoc = multer({
  storage: storage,
  limits: { 
    fieldNameSize: 1000000, 
    fieldSize: 1000000
  }
}).single('doc');

第二步:在路由中直接使用multer中间件

修改server/api/controllers/examples/router.ts:

/* eslint-disable prettier/prettier */
import express from 'express';
import controller from './controller';
import { uploadSingleDoc } from '../../../../common/multer-config';

export default express
  .Router()
  // 把multer中间件放到控制器之前
  .post('/:dir', uploadSingleDoc, controller.post.bind(controller));

第三步:简化控制器逻辑

现在控制器不需要再处理multer的初始化,直接读取req.file即可:

/* eslint-disable prettier/prettier */
import { Request, Response } from 'express';

export class Controller {
  post(req: Request, res: Response): void {
    console.log("Received file upload request");
    
    if (!req.file) {
      return res.status(400).json({ message: 'No file was uploaded' });
    }

    console.log(`Successfully uploaded: ${req.file.originalname} to ${req.file.path}`);
    res.status(200).json({
      message: 'File uploaded successfully',
      fileDetails: {
        originalName: req.file.originalname,
        savedPath: req.file.path,
        size: req.file.size
      }
    });
  }
}

export default new Controller();

3. 验证OpenAPI配置兼容性

你的api.yml配置基本没问题,不过可以补充encoding字段让Swagger的测试更准确:

requestBody:
  content:
    multipart/form-data:
      schema:
        type: object
        properties:
          doc:
            type: string
            format: binary
      # 明确指定文件的内容类型
      encoding:
        doc:
          contentType: application/octet-stream
  required: true

做完这些调整后,重启服务器,用Swagger或者Postman测试文件上传,应该就能正常工作,不会再出现Unexpected end of form的错误了。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.04 16:30:34