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

使用@aws-sdk/client-s3 V3的GetObjectCommand下载S3文件随机损坏求助

排查Electron中从S3下载PDF随机损坏的问题

核心代码问题:未正确等待文件写入完成

你的代码里await objData.Body.pipe(...)逻辑无效——pipe()返回的是目标流而非Promise,await不会等待文件写入完成就进入下一次循环。这种情况下,若循环迭代速度快于文件写入速度,极易导致文件写入不完整,表现为PDF损坏。

修复方法是将流的finish事件包装为Promise,确保每一次文件写入完成后再处理下一个文件:

const downloadEachObject = async (files) => {
  let lastFile;
  const basePath = '/Users/downloadTestDir';
  try {
    for (let i = 0; i < files.length; i++) {
      const file = files[i];
      if (file.Key === lastFile) {
        continue;
      }
      lastFile = file.Key;
      const fileParams = {
        Bucket: 'bucket',
        Key: lastFile,
      };
      const objData = await s3Client.send(new GetObjectCommand(fileParams));
      
      const newFilePath = path.join(basePath, file.Key);
      // 先创建父目录(避免子目录不存在导致写入失败)
      const parentDir = path.dirname(newFilePath);
      await fs.promises.mkdir(parentDir, { recursive: true });

      // 包装流事件为Promise,等待写入完成
      await new Promise((resolve, reject) => {
        objData.Body.pipe(fs.createWriteStream(newFilePath))
          .on('error', (error) => {
            console.error('写入文件出错: ', error);
            reject(error);
          })
          .on('finish', () => {
            console.log('文件已写入磁盘: ', file.Key);
            resolve();
          });
      });
    }
  } catch (er) {
    console.log('遍历文件出错', er);
  }
};

其他排查方向

  • 目录创建缺失:如果文件Key包含多级子目录(如docs/report.pdf),直接调用createWriteStream会因父目录不存在而失败,导致文件写入不完整。上面的修复代码已通过mkdir({ recursive: true })处理该场景。
  • 文件完整性校验:下载后对比本地文件哈希与S3对象的ETag(注意:分块上传的S3对象ETag会带-<分块数>后缀,需去掉后缀再对比)。示例校验代码:
    const calculateMD5 = async (filePath) => {
      const hash = crypto.createHash('md5');
      const stream = fs.createReadStream(filePath);
      await new Promise((resolve, reject) => {
        stream.on('data', (data) => hash.update(data));
        stream.on('end', () => resolve());
        stream.on('error', reject);
      });
      return hash.digest('hex');
    };
    
    // 文件写入完成后执行校验
    const localMD5 = await calculateMD5(newFilePath);
    const s3ETag = objData.ETag.replace(/"/g, '').split('-')[0];
    if (localMD5 !== s3ETag) {
      console.error(`文件校验失败: ${file.Key},本地MD5: ${localMD5},S3 ETag: ${s3ETag}`);
      // 可在此添加重试逻辑
    }
    
  • AWS SDK重试配置:网络波动可能导致下载中断,可调整S3客户端的重试策略:
    const s3Client = new S3Client({
      region: 'your-region',
      retryMode: 'standard',
      maxAttempts: 5, // 增加重试次数
    });
    
  • Electron文件权限:Electron环境下写入目录可能需要额外系统权限(如macOS的文件访问授权),可尝试将下载目录切换为Electron默认下载目录(app.getPath('downloads'))测试。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.02 22:01:13