【问题标题】:Rails file download using send_data with follow-up action使用 send_data 下载 Rails 文件并执行后续操作
【发布时间】:2018-05-09 12:00:19
【问题描述】:

我正在开发一个代码库,我需要允许用户下载已经存在于 AWS S3 上的 PDF 文档。我已经实现了一个用于先前功能的下载问题。

对于此功能,我需要在用户完成文件下载后更新 UI(进度步进器)。我最初认为这很简单:

  1. 用户点击下载
  2. 在使用send_data 下载文件的地方进行API 调用。在此 API 调用中,我还将更新 Foo 模型以更改状态以指示用户已下载文件;
  3. 执行redirect_to request.referer 以重新加载数据。 Foo 中的更改状态将负责在 UI 中显示更新的进度;

我错误地认为这会很简单。复杂的原因:

  1. send_data 已经在渲染数据,所以我无法使用 redirect_to 刷新页面,因为这会触发多次渲染错误;
  2. send_data 不适用于 remote: true 选项,因此通过 AJAX 链接请求数据并更新 ERB 模板已失效;
  3. 我可以将所有内容都写入一个 JS on click 函数,但这看起来有点像 hack。我可能需要直接从 AWS 检索文件并跳过我的 api?我怀疑我可能会遇到 CORS 问题,因为我无法控制服务器。

这是我的 rails 下载方法目前的样子:

def download
  attachment = Attachment.find_by_id(params[:attachment_id])
  content = send_data(
    attachment.file.read,
    filename: "#{attachment.title}.#{attachment.file.file.extension}",
    type: attachment.content_type,
    disposition: "attachment",
  )
end

基本工作的 js 代码看起来像这样,其中所有相关的路径和文件名都通过数据属性传递给 JS:

$(document).on("click", "#download", function(e){
  e.preventDefault();
  const data = $('#temp-information').data();
  var req = new XMLHttpRequest();
  req.open("GET", data.path, true);
  req.responseType = "blob";
  const filename = data.title;
  req.onload = function (event) {
    var blob = req.response;
    console.log(blob.size);
    var link=document.createElement('a');
    link.href=window.URL.createObjectURL(blob);
    link.download= filename;
    document.body.appendChild(link);
    link.click();
  };
  if (typeof window.navigator.msSaveBlob !== 'undefined') {
    // Fix to work in IE11
    window.navigator.msSaveBlob(blob, filename);
  } else {
    req.send();
  }
});

在下载完成后处理文件下载和更新 UI 的最有效和最有效的方式是什么?

【问题讨论】:

    标签: javascript ruby-on-rails ruby-on-rails-4 download


    【解决方案1】:

    您要完成的工作并不是 100% 清楚。如果您想让用户看到下载进度,我不确定您是否真的需要做任何事情,除了send_data,然后大多数浏览器都会开始下载文件,包括显示进度条。

    因为看起来你想在文件下载完成后做一些事情,这有点棘手。该问题与 Rails 无关,您使用的方法在我看来非常合理。

    this SO thread 上,您会发现关于这个问题的冗长讨论以及人们尝试解决它的各种方法。一般来说,解决方案遵循相同的基本结构,即简单地轮询服务器。

    在您的 Rails 应用程序中,您可以大致如下实现。假设您在附件模型中添加了一个字段 status...

    def download
      attachment = Attachment.find_by_id(params[:attachment_id])
      attachment.update(status: "downloading")
      send_data(
        attachment.file.read,
        filename: "#{attachment.title}.#{attachment.file.file.extension}",
        type: attachment.content_type,
        disposition: "attachment",
      )
      attachment.update(status: "complete")
    end
    

    然后您可以添加一个返回文件状态的端点。因此,当用户开始下载文件时,您开始轮询该端点。

    def attachment_status
      attachment = Attachment.find_by_id(params[:attachment_id])
      respond_to do |format|
        format.json do
          {status: attachment.status}
        end
      end
    end
    

    然后在Javascript中,例如使用HttpPromise:

    var http = new HttpPromise;
    function poll(doneFn) {
      http.get("/status.json") // you will need to set your actual status endpoint path here
          .success(function(data,xhr){
            if (data.status == "complete") {
              doneFn();
            }
          });
    };
    function downloadFinished(){
      // ... do whatever you want on finish here ...
    };
    setInterval(function(){ poll(downloadFinished) }, 5000);
    

    这不是世界上最美丽的东西,但它应该可以完成工作。

    祝你好运!

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2017-03-01
      • 1970-01-01
      • 2020-02-12
      • 2018-09-08
      • 2019-03-09
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多