Express实现文件直传FTP报错求助:storageErrors为空
文件直传FTP报错排查(Express+multer-sftp)
需求:实现前端→后端→FTP服务器的文件直传流程,文件不落地存储在后端,基于Express框架开发。
问题:先后编写两段代码,均返回{"storageErrors":[]}错误,上传至FTP的图片仅160KB(远小于原文件),已在server.ts中配置bodyParser的大小限制参数,请求排查问题。
初始代码
baseServer.post( "/api/upload-image", upload.single('file'), function (req: Request, res: Response, next: NextFunction) { uploadToFTP(req, res, next); } ); function uploadToFTP(req: Request, res: Response, next: NextFunction) { // sftp settings var storage = sftpStorage({ sftp: { host: process.env.FTP_URL, port: process.env.FTP_PORT, username: process.env.FTP_USERNAME, password: process.env.FTP_PASSWORD, }, destination: function (req: Request, file: any, cb: any) { cb(null, ""); // designation folder in host }, filename: function (req: Request, file: any, cb: any) { // file name settings cb(null, file.fieldname + "-" + Date.now()); }, }); var upload = multer({ storage: storage }).single("file"); upload(req, res, function (err: any) { if (err) { console.log("Error Occured", JSON.stringify(err)); res.json({ error: err }); } else { console.log("Files uploaded successfully"); res.json({ success: true}); } }); }
修改后的代码
import { NextFunction, Request, Response } from "express"; import { admin } from "../../middlewares/authMiddleware"; import { baseServer } from "../../server"; var multer = require("multer"); var sftpStorage = require("multer-sftp"); const upload = multer(); // sftp settings var storage = sftpStorage({ sftp: { host: process.env.FTP_URL, port: process.env.FTP_PORT, username: process.env.FTP_USERNAME, password: process.env.FTP_PASSWORD, }, destination: function (req: Request, file: any, cb: any) { cb(null, ""); // designation folder in host }, filename: function (req: Request, file: any, cb: any) { // file name settings cb(null, file.fieldname + "-" + Date.now()); }, }); var uploadToFTP = multer({ storage: storage }).single('file'); baseServer.post( "/api/upload-image", upload.single("file"), function (req: Request, res: Response, next: NextFunction) { uploadToFTP(req, res, function (err: any) { if (err) { console.log("Error Occured", JSON.stringify(err)); res.json({ error: err }); } else { console.log("Files uploaded successfully"); res.json({ success: true }); } }); } );
server.ts代码
export const baseServer = express(); const PORT = process.env.PORT; const bodyParser = require('body-parser'); baseServer.use(express.json()); baseServer.use(bodyParser.json({limit:'50mb'})); baseServer.use(bodyParser.urlencoded({extended:true, limit:'50mb'}));
问题原因与解决方案
核心问题
两段代码都重复调用了multer的single('file')中间件:
- 第一个
upload.single("file")已经将前端传来的文件解析到内存中,文件流被消费; - 后续再调用带sftp存储的
uploadToFTP时,已经没有完整的文件流可以读取,导致上传到FTP的文件不完整(仅160KB内存残留),同时触发无具体错误的storageErrors。
另外,bodyParser的配置对multipart/form-data格式的文件上传无效,multer会自行处理该格式,所以无需依赖bodyParser的大小限制。
修正方案
直接使用带sftp存储的multer实例处理请求,去掉多余的upload.single("file")中间件,让文件通过multer-sftp流式上传至FTP,全程不落地存储在后端。
同时注意:FTP_PORT环境变量需要转为数字类型(multer-sftp的port参数要求为number,而非字符串)。
修正后的完整代码
import { NextFunction, Request, Response } from "express"; import { admin } from "../../middlewares/authMiddleware"; import { baseServer } from "../../server"; const multer = require("multer"); const sftpStorage = require("multer-sftp"); // SFTP配置 const storage = sftpStorage({ sftp: { host: process.env.FTP_URL, port: parseInt(process.env.FTP_PORT as string, 10), // 转为数字类型 username: process.env.FTP_USERNAME, password: process.env.FTP_PASSWORD, }, destination: (req: Request, file: any, cb: any) => { cb(null, "/"); // 指定FTP根目录,根据需求修改为具体文件夹 }, filename: (req: Request, file: any, cb: any) => { // 生成唯一文件名,保留原后缀 const uniqueName = `${file.fieldname}-${Date.now()}-${file.originalname}`; cb(null, uniqueName); }, }); // 创建带SFTP存储的multer实例,可添加文件大小限制 const uploadToFTP = multer({ storage, limits: { fileSize: 50 * 1024 * 1024 } // 限制最大50MB }).single("file"); // 路由配置 baseServer.post( "/api/upload-image", uploadToFTP, (req: Request, res: Response) => { console.log("文件已成功上传至FTP"); res.json({ success: true, filename: req.file?.filename }); } );
额外注意事项
- 确保FTP服务器的目标目录有写入权限;
- 若需验证用户身份,可在路由中添加对应的认证中间件(如示例中的
admin)。
内容的提问来源于stack exchange,提问作者Alvin Stefanus
相关产品推荐
相关产品推荐

