Express+Typescript项目文件上传遇"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

