Node.js上传图片到FTP单张正常多张失败,如何优化代码逻辑?
问题根本原因
- 全局仅创建了1个FTP客户端实例,同时上传两张图时两个
ready事件回调都绑定到同一个实例上,触发时会同时执行两个上传操作 - 第一个上传完成就直接调用
ftpClient.end()关闭连接,第二个上传还未执行完就被中断 ready事件重复绑定,多次请求会积累回调逻辑,导致内存泄漏和逻辑混乱- 两个文件的校验逻辑里都单独调用了
response.send,如果两个文件同时校验失败会重复触发响应,抛错
修复后的代码
const ftp = require('ftp'); const formidable = require('formidable'); const sizeOf = require('image-size'); var form = new formidable.IncomingForm(); form.parse(request, async function(err, fields, files) { // 统一校验参数,收集待上传任务 const uploadTasks = []; let validateError = null; // 校验店铺logo if (files.shoplogo?.name) { const dimensions = sizeOf(files.shoplogo.path); if (dimensions.width > 255 || dimensions.height > 255 || dimensions.width < 145 || dimensions.height < 145) { validateError = 'Das Bild darf nicht größer 250x250 oder kleiner als 150x150 sein.'; } else if (files.shoplogo.size > 200000) { validateError = 'Das Bild darf nicht größer als 150 kb sein.'; } else { uploadTasks.push({ oldPath: files.shoplogo.path, remotePath: `/web/shopContent/logo_${fields.shopid}.jpg` }); } } // 校验店铺封面,前置校验失败则不再执行 if (!validateError && files.shopcover?.name) { const dimensions = sizeOf(files.shopcover.path); if (dimensions.width > 505 || dimensions.height > 505 || dimensions.width < 245 || dimensions.height < 245) { validateError = 'Das Bild darf nicht größer 500x500 oder kleiner als 250x250 sein.'; } else if (files.shopcover.size > 200000) { validateError = 'Das Bild darf nicht größer als 150 kb sein.'; } else { uploadTasks.push({ oldPath: files.shopcover.path, remotePath: `/web/shopContent/cover_${fields.shopid}.jpg` }); } } // 校验失败统一返回 if (validateError) { return response.send(`<script>alert("${validateError}"); history.back(); </script>`); } // 无待上传任务直接返回 if (uploadTasks.length === 0) { return response.send('<script>alert("未选择需要上传的文件"); history.back(); </script>'); } // 每次请求新建独立FTP实例,避免连接复用冲突 const ftpClient = new ftp(); ftpClient.on('error', (err) => { ftpClient.end(); return response.send(`<script>alert("上传失败:${err.message}"); history.back(); </script>`); }); ftpClient.on('ready', async () => { try { // 按顺序执行所有上传任务 for (const task of uploadTasks) { await new Promise((resolve, reject) => { ftpClient.put(task.oldPath, task.remotePath, (err) => { if (err) return reject(err); resolve(); }); }); } // 全部上传完成后再关闭连接 ftpClient.end(); // 可替换为自定义的成功返回逻辑 response.send('<script>alert("上传成功"); location.href="你的跳转页面地址"; </script>'); } catch (err) { ftpClient.end(); return response.send(`<script>alert("上传失败:${err.message}"); history.back(); </script>`); } }); // 启动FTP连接 ftpClient.connect({ 'host': 'host', 'user': 'user', 'password': 'pw' }); });
核心优化点
- 每次请求单独创建FTP客户端实例,避免多个请求/多个上传任务复用同一个连接产生冲突
- 先统一完成所有文件的校验,收集所有待上传任务后再启动FTP连接,避免创建无效连接浪费资源
- 同一个FTP连接内按顺序执行所有上传任务,全部完成后再关闭连接,不会出现中途断连的问题
- 统一处理错误响应,不会出现重复调用
response.send的异常 - 增加FTP连接的错误监听,避免未捕获的异常导致服务崩溃
内容的提问来源于stack exchange,提问作者piguy
相关产品推荐
相关产品推荐

