【问题标题】:How does download in Google Drive (Web) work?Google Drive (Web) 中的下载如何工作?
【发布时间】:2025-12-01 23:50:02
【问题描述】:

我目前正在 Web 应用程序中实现文件下载。 我在弹出窗口拦截器上有点挣扎,下载需要在用户交互后立即开始,所以不能有服务器往返。 现在我注意到,例如 Goolge Drive 允许您下载文件夹。当您这样做时,他们的服务器首先会创建一个压缩的 zip 文件,这需要一些时间。服务器完成后,下载会立即开始,无需其他用户交互。

现在我想知道如何做到这一点?

【问题讨论】:

  • 一个简单的解决方案是客户端简单地请求 zip,服务器创建它,然后交付它。这可能在后台发生,因此 1) fetch() 运行 2) 客户端显示“正在打包...” 3) 服务器打包 4) 服务器交付 5) 下载开始。不过,这可能会导致超时,因此您基本上是在询问如何将消息从服​​务器发送到客户端。这可以使用套接字(WebRTC、socket.io)来完成
  • 据我所知,下载文件的唯一方法是使用anchor。因此,在这种情况下,客户端必须使用anchor 请求 zip,但它会立即出现在浏览器的下载部分。在谷歌浏览器的情况下,它首先显示一个关于打包的通知,然后下载出现在下载部分。
  • 就像我说的,您可能需要一个基于套接字的解决方案。 1) 客户端通过 fetch() 请求 zip 2) 谷歌驱动器准备 zip,完成后,发送带有临时 URL 的套接字消息 3) 客户端使用 URL 开始下载
  • 我经常遇到下载被阻止的问题,如果它发生在服务器往返之后。但@ManhNguyen 的解决方案似乎有效,即使在 Firefox 中也是如此。不知道为什么
  • ManH 下面的回答基本上是我在第一条评论中概述的五个步骤。

标签: javascript html download popup-blocker


【解决方案1】:

我编写了一个通过 url 下载文件的函数。 在您的情况下,您必须使用 ajax 请求在服务器上制作一个 zip 文件,然后返回 url 并运行以下函数进行下载:

function download(url, filename){
    fetch(url)
    .then(resp => resp.blob())
    .then(blob => {
      const url = window.URL.createObjectURL(blob);
      const a = document.createElement('a');
      a.style.display = 'none';
      a.href = url;
      a.download = filename;
      document.body.appendChild(a);
      a.click();
      window.URL.revokeObjectURL(url);
      console.log('your file has downloaded!'); 
    })
    .catch(() => console.log('Download failed!'));
}

在这里测试codepen:download file via url

【讨论】:

  • 感谢您的回答。我经常遇到的问题是,如果在服务器往返之后开始下载,浏览器(尤其是 Firefox)会阻止它,但是这个解决方案似乎有效。
【解决方案2】:

我注意到,例如 Google Drive 允许您下载 文件夹。当您这样做时,他们的服务器首先会创建一个压缩的 zip 文件,这需要一些时间。

或者,您可以将目录的内容请求为 json,然后遍历 json 并将每个文件作为 blob 下载并创建一个 zip 文件,唯一的阻塞是对 json 的请求,然后您可以显示每个下载文件的状态等。

执行此操作的库:

片段示例,使用 vue、s3 等

async download(bucket) {

  this.$snackbar.show({
    type: 'bg-success text-white',
    message: 'Building Zip, please wait...'
  })

  //..eek fetch all items in bucket, plop into a zip, then trigger download
  // - there is some limits on final filesize, will work around it by disabling download :)

  // resolve objects
  const objects = async(bucket) => new Promise(async(resolve, reject) => {
    let objectStream = await this.state.host.client.listObjectsV2(bucket, '', true)
    let objects = []
    //
    objectStream.on('data', (obj) => {
      if (obj && (obj.name || obj.prefix)) {
        this.$snackbar.show({
          type: 'bg-success text-white',
          message: 'Fetching: ' + obj.name
        })
        objects.push(obj)
      }
    })
    objectStream.on('end', () => resolve(objects))
    objectStream.on('error', (e) => reject(e))
  })

  // get an objects data
  const getObject = (bucket, name) => new Promise((resolve, reject) => {
    this.state.host.client.getObject(bucket, name, (err, dataStream) => {
      let chunks = []
      dataStream.on('data', chunk => {
        this.$snackbar.show({
          type: 'bg-success text-white',
          message: 'Downloading: ' + name
        })
        chunks.push(chunk)
      })
      dataStream.on('end', () => resolve(Buffer.concat(chunks || [])))
    })
  })

  // fetch objects info a zip file
  const makeZip = (bucket, objects) => new Promise(async(resolve, reject) => {
    let zip = new JSZip()

    for (let i in objects) {
      this.$snackbar.show({
        type: 'bg-success text-white',
        message: 'Zipping: ' + objects[i].name
      })
      zip.file(objects[i].name, await getObject(bucket, objects[i].name));
    }

    zip.generateAsync({
      type: "blob"
    }).then(content => {
      this.$snackbar.show({
        type: 'bg-success text-white',
        message: 'Zip Created'
      })
      resolve(content)
    })
  })

  // using FileSaver, download file
  const downloadZip = (content) => {
    this.$snackbar.show({
      type: 'bg-success text-white',
      message: `Downloading: ${bucket.name}.zip`
    })
    FileSaver.saveAs(content, `${bucket.name}.zip`)
  }

  try {
    downloadZip(await makeZip(bucket.name, await objects(bucket.name)))
  } catch (e) {
    this.$snackbar.show({
      type: 'bg-danger text-white',
      message: e.message
    })
    console.error(e)
  }
},

如果你想以一种丑陋的方式下载目录,请获取 json 列表,然后在带有 a.setAttribute("target", "_blank") 的 dom 上放置一堆 document.createElement('a'),但你会打开一堆“另存为”对话框。

How to download multiple images?

【讨论】: