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

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.10.02 20:09:01