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

Node-FTP实现断点续传下载后文件大小正常但损坏问题

FTP下载断点续传后ZIP文件损坏修复方案

问题根因

你当前使用r+文件写入标志配合FTP restart方法的用法存在逻辑缺陷,这是文件损坏的直接原因:

  • r+模式打开文件时不会自动截断文件内容,如果上次下载中途异常退出,本地文件restartSize偏移量之后可能残留无效脏数据。新的下载流只会覆盖restartSize之后对应长度的内容,文件末尾残留的旧数据不会被清除,最终文件总大小和服务端一致,但字节序列存在错误,ZIP这类带完整性校验的压缩包会直接识别为损坏。
  • 现有代码缺少边界校验、事件顺序错误:首次下载本地无文件时fs.statSync会直接抛错;pipe调用后才绑定data事件可能导致进度统计丢失;没有流错误、结束监听,容易出现资源泄漏。

修复后完整代码

const fs = require('fs');

clientFtp.on('ready', function(){
    const serverFile  = "example_file_on_server.zip";
    const localFile   = "example_file_on_client.zip";
    let serverFileSize = 0;
    let restartSize = 0;

    clientFtp.list(serverFile, function(erreurListage, serverElementList) {
        if (erreurListage) throw erreurListage;
        if (!serverElementList?.length) throw new Error('Remote file not exist');
        serverFileSize = serverElementList[0].size;

        // 计算续传偏移量,兼容本地文件不存在场景
        try {
            const localFileInfo = fs.statSync(localFile);
            restartSize = localFileInfo.size;
            // 本地文件大小已超过/等于服务端文件,直接判定下载完成
            if (restartSize >= serverFileSize) {
                console.log('Download already completed');
                clientFtp.end();
                return;
            }
            // 核心修复:截断本地文件到已确认下载完成的长度,清除残留脏数据
            fs.truncateSync(localFile, restartSize);
        } catch (e) {
            // 本地文件不存在,从头开始下载
            restartSize = 0;
        }

        clientFtp.restart(restartSize, function(erreurRedemarrage){
            if (erreurRedemarrage) throw erreurRedemarrage;
            let tailleDownload = restartSize;
            // 区分首次下载/续传的文件打开模式
            const writeStream = fs.createWriteStream(localFile, {
                flags: restartSize === 0 ? 'w' : 'r+',
                start: restartSize
            });

            clientFtp.get(serverFile, function(erreurRecuperation, stream) {
                if (erreurRecuperation) throw erreurRecuperation;
                // 先绑定事件再pipe,避免漏收数据
                stream.on('data', function(buffer){
                    tailleDownload += buffer.length;
                    const pourcentageDownload = (tailleDownload / serverFileSize * 100).toFixed(0);
                    console.log(pourcentageDownload);
                });
                stream.on('end', () => {
                    writeStream.end();
                    clientFtp.end();
                });
                stream.on('error', (err) => {
                    writeStream.destroy();
                    clientFtp.destroy();
                    throw err;
                });
                stream.pipe(writeStream);
            });
        });
    });         
});

关键改动说明

  • 续传前调用fs.truncateSync将本地文件截断到已验证完成的restartSize长度,彻底清除历史残留的无效字节,从根源解决文件内容错乱问题
  • 新增本地文件存在性判断,首次下载自动从头开始,不会因为找不到本地文件报错
  • 增加本地与服务端文件大小校验,已完成下载时直接返回,不发起无效传输请求
  • 调整事件绑定顺序,先绑定data/error/end事件再执行pipe,避免进度统计丢失、异常未捕获问题
  • 区分首次下载和续传的文件打开标识,首次下载用w模式创建新文件,续传用r+模式从指定偏移写入,减少文件打开异常
  • 增加流生命周期监听,下载完成、出错时正确关闭文件流和FTP连接,避免资源泄漏

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.28 13:57:11