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

React/Node.js项目实现谷歌云存储单文件及批量下载问题

问题分析与解决方案

前端代码修复(单条下载)

你的前端代码核心问题是获取到Blob后没有触发浏览器下载动作,仅停留在获取Blob对象的步骤;同时变量名重复覆盖也容易引发混淆。修复后的代码如下:

const downloadSingleFile = async () => {
  try {
    // 获取GCS文件的有效下载URL
    const downloadUrl = await getDownloadURL(ref(storage, cellInfo.fileName));
    
    // 用fetch替代XHR,代码更简洁易维护
    const response = await fetch(downloadUrl);
    const blob = await response.blob();
    
    // 创建临时下载链接并触发浏览器下载
    const url = window.URL.createObjectURL(blob);
    const a = document.createElement('a');
    a.href = url;
    // 从文件路径中提取文件名,避免下载后显示完整路径
    a.download = cellInfo.fileName.split('/').pop() || '未知文件';
    document.body.appendChild(a);
    a.click();
    
    // 清理临时资源,避免内存泄漏
    window.URL.revokeObjectURL(url);
    document.body.removeChild(a);
  } catch (error) {
    console.error('单文件下载失败:', error);
  }
};

前端批量下载实现

如果需要批量下载选中文件,可使用jszip库打包后下载(需先执行npm install jszip安装依赖):

import JSZip from 'jszip';

const downloadBatchFiles = async (selectedRows) => {
  const zip = new JSZip();
  
  try {
    // 并行下载所有选中文件并添加到zip包
    await Promise.all(selectedRows.map(async (row) => {
      const downloadUrl = await getDownloadURL(ref(storage, row.fileName));
      const response = await fetch(downloadUrl);
      const blob = await response.blob();
      // 用文件名作为zip内的文件路径
      zip.file(row.fileName.split('/').pop(), blob);
    }));
    
    // 生成zip包并触发下载
    const zipBlob = await zip.generateAsync({ type: 'blob' });
    const url = window.URL.createObjectURL(zipBlob);
    const a = document.createElement('a');
    a.href = url;
    a.download = '批量文件.zip';
    document.body.appendChild(a);
    a.click();
    
    // 清理资源
    window.URL.revokeObjectURL(url);
    document.body.removeChild(a);
  } catch (error) {
    console.error('批量下载失败:', error);
  }
};

Node.js后端接口方案

单文件下载接口

使用@google-cloud/storage库直接从GCS获取文件流并返回给前端(需先执行npm install @google-cloud/storage安装依赖):

const { Storage } = require('@google-cloud/storage');
const storage = new Storage({ keyFilename: '你的GCS服务账号密钥.json' });
const YourModel = require('../models/你的数据模型'); // 替换为你的MongoDB模型

// 单文件下载接口
app.get('/api/:location/download/:id', async (req, res) => {
  try {
    // 从MongoDB查询对应文件路径
    const data = await YourModel.findById(req.params.id);
    if (!data || !data.fileName) {
      return res.status(404).send('文件不存在');
    }
    
    const bucket = storage.bucket('你的GCS存储桶名称');
    const file = bucket.file(data.fileName);
    
    // 设置响应头,强制浏览器触发下载
    res.setHeader('Content-Disposition', `attachment; filename="${data.fileName.split('/').pop()}"`);
    res.setHeader('Content-Type', 'application/octet-stream');
    
    // 将GCS文件流管道到响应
    file.createReadStream().pipe(res);
  } catch (error) {
    console.error('后端下载错误:', error);
    res.status(500).send('下载失败');
  }
});

后端批量下载接口

使用archiver库打包多个文件后返回(需先执行npm install archiver安装依赖):

const archiver = require('archiver');

// 批量下载接口
app.post('/api/:location/download/batch', async (req, res) => {
  try {
    const { ids } = req.body; // 前端传入选中数据的ID数组
    const dataList = await YourModel.find({ _id: { $in: ids } });
    
    if (dataList.length === 0) {
      return res.status(404).send('未找到对应文件');
    }
    
    // 创建zip归档器
    const archive = archiver('zip', { zlib: { level: 9 } });
    
    // 设置响应头
    res.setHeader('Content-Disposition', 'attachment; filename="批量文件.zip"');
    res.setHeader('Content-Type', 'application/zip');
    
    // 将归档器管道到响应
    archive.pipe(res);
    
    // 遍历文件并添加到zip包
    for (const data of dataList) {
      if (data.fileName) {
        const bucket = storage.bucket('你的GCS存储桶名称');
        const file = bucket.file(data.fileName);
        archive.append(file.createReadStream(), { name: data.fileName.split('/').pop() });
      }
    }
    
    // 完成归档
    await archive.finalize();
  } catch (error) {
    console.error('后端批量下载错误:', error);
    res.status(500).send('批量下载失败');
  }
});

前端调用后端批量接口代码

const downloadBatchViaBackend = async (selectedRows) => {
  try {
    const ids = selectedRows.map(row => row._id);
    const response = await axios.post(`http://localhost:3000/api/${location}/download/batch`, { ids }, {
      responseType: 'blob' // 关键:告知axios接收Blob类型响应
    });
    
    // 触发浏览器下载
    const url = window.URL.createObjectURL(response.data);
    const a = document.createElement('a');
    a.href = url;
    a.download = '批量文件.zip';
    document.body.appendChild(a);
    a.click();
    
    window.URL.revokeObjectURL(url);
    document.body.removeChild(a);
  } catch (error) {
    console.error('后端批量下载失败:', error);
  }
};

关键注意事项

  1. CORS配置:确保GCS的CORS规则允许前端域名(包括http://localhost:3000)的GET请求,示例规则:
[
  {
    "origin": ["http://localhost:3000", "你的生产环境域名"],
    "method": ["GET"],
    "responseHeader": ["Content-Type"],
    "maxAgeSeconds": 3600
  }
]
  1. 本地跨域处理:若前端与后端端口不同,需在Node.js后端配置cors中间件,允许前端域名的请求。
  2. 文件名处理:始终从GCS路径中提取最后一段作为下载文件名,避免用户下载后看到完整路径名称。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.21 12:20:02