【问题标题】:Difficulties with downloading a file with jQuery (GET)使用 jQuery (GET) 下载文件的困难
【发布时间】:2018-01-19 06:20:09
【问题描述】:

我有一个 API-Server 可以响应这样的请求:

http://localhost:8080/slim3/public/api/v1/files/Test1.jpg
http://localhost:8080/slim3/public/api/v1/files/Test2.txt
...

如果我将这样的 URL 放入我的浏览器,我可以得到下载提示。现在我正在努力通过 jQuery / Ajax 处理文件的下载。 我在 Stackoverflow 上找到的每个线程都告诉我发回实际的下载 url 并通过window.location 打开它。我不明白这怎么可能 当我的服务器已经为我下载了文件并且我只需要在客户端以某种方式“抓取”它时?

我很清楚,我无法通过 jQuery / Javascript 强制下载对话框。我在这里阅读了多个线程。但相同的线程不会告诉我 我怎样才能获得直接下载网址。还是不幸我把事情搞混了?

这是我所拥有的:

客户端 (jQuery)

$(document).ready(function(){
   $(document).on('click', '#file', function(e){   
        e.preventDefault();
        var filename = $(this).data('url'); 
        $.ajax({
            type : "GET",
            cache: false,
            url : "http://localhost:8080/slim3/public/api/v1/files/" + filename,   
            success : function(data) {
                console.log(data) // the console writes nothing 
                //window.location = "data:application/octet-stream," + encodeURIComponent(data); // not working            
                //var downloadUrl = data.url; // not working
                //window.location = downloadUrl; // // not working
            },
            error : function(data) {}
        });
   });
});

服务器 (PHP)

public function show($request, $response, $args)
{               
    $file = 'C:\xampp\htdocs\slim3\storage\Test1.jpg';

    $res = $response->withHeader('Content-Description', 'File Transfer')
               ->withHeader('Content-Type', 'application/octet-stream')
               ->withHeader('Content-Disposition', 'attachment;filename="'.basename($file).'"')
               ->withHeader('Expires', '0')
               ->withHeader('Cache-Control', 'must-revalidate')
               ->withHeader('Pragma', 'public')
               ->withHeader('Content-Length', filesize($file));
    readfile($file);
    return $res;
}

解决方案:

Rob 为我指明了正确的方向。我实际上不需要发出GET Ajax 请求。所以最终的 jQuery 函数看起来就像这样并且可以工作:

   $(document).on('click', '#file', function(e){   
        e.preventDefault();
        var filename = $(this).data('url'); 
        window.location = "http://localhost:80/slimmi/public/api/v1/files/" + filename;        
   });

【问题讨论】:

  • 你能解释一下你真正想要做什么吗?我真的不明白你在问什么。
  • @lexith 我正在尝试在我的客户端上获取用 HTML / jQuery 编写的“另存为”对话框。
  • 好的,但是您提到的建议解决方案有什么问题?你说你知道你不能用 ajax 来做。
  • 问题是它在客户端上不起作用,这意味着不会出现“另存为”对话框。我可能把事情搞混了,因为它不能通过 Rob 提供的解决方案起作用。无论如何谢谢lexith!

标签: javascript php jquery ajax


【解决方案1】:

在客户端,filename 变量会出错。应该是Test1.jpgTest2.txt。我认为 $(this).data('url'); 返回当前 url 而不是 Test1.jpgTest2.txtnames。您是否尝试使用以下方法减去文件名:

var url = $(this).data('url');
var filename = url.substring(url.lastIndexOf("/") + 1, url.length);

【讨论】:

    【解决方案2】:

    您的服务器只是发回 URL 中按名称请求的实际文件,对吗?

    在我看来,您只需要将所有 ajax 代码替换为

    document.location = "http://localhost:8080/slim3/public/api/v1/files/" + 文件名;

    您在 PHP 中设置的标题将决定浏览器是否显示保存对话框或尝试显示文件 - 看起来正确。

    【讨论】:

    • 哇。我只见树木不见森林。我用解决方案更新了我的问题。谢谢!
    【解决方案3】:

    如果文件是按需生成的,您可以做的是让 PHP 在 Base64 中对您的文件进行编码 - 例如 this,设置适当的类型 - 并将 that 返回给客户端。将 Base64 转换为 Blob - 您可以将 Base64 放入 anchor's href 但 IE 的 URI 大小非常小 - 然后从该 Blob 创建一个 URL 对象。除其他外,这可确保数据是 URL 安全的。最后,创建一个“不可见”的锚标签并点击它。

    $.ajax({
            type: "GET",
            url: target,
            success: function (response) {
                // create a download anchor tag
                var downloadLink = document.createElement('a');
                downloadLink.target = '_blank';
                downloadLink.download = 'your-file-name-here';
    
                // convert Base64 to Blob - don't forget to set content type!
                var blob = b64toBlob(response, [file type here]);
    
                // create an object URL from the Blob
                var URL = window.URL || window.webkitURL;
                var downloadUrl = URL.createObjectURL(blob);
    
                // set object URL as the anchor's href
                downloadLink.href = downloadUrl;
    
                // append the anchor to document body
                document.body.appendChild(downloadLink);
    
                // fire a click event on the anchor
                downloadLink.click();
    
                // cleanup: remove element and revoke object URL
                document.body.removeChild(downloadLink);
                URL.revokeObjectURL(downloadUrl);
            }
        });
    

    像这样将 Base64 转换为 Blob - source

    function b64toBlob(b64Data, contentType, sliceSize) {
        contentType = contentType || '';
        sliceSize = sliceSize || 512;
    
        var byteCharacters = atob(b64Data);
        var byteArrays = [];
    
        for (var offset = 0; offset < byteCharacters.length; offset += sliceSize) {
            var slice = byteCharacters.slice(offset, offset + sliceSize);
    
            var byteNumbers = new Array(slice.length);
            for (var i = 0; i < slice.length; i++) {
                byteNumbers[i] = slice.charCodeAt(i);
            }
    
            var byteArray = new Uint8Array(byteNumbers);
    
            byteArrays.push(byteArray);
        }
    
        var blob = new Blob(byteArrays, {type: contentType});
        return blob;
    }
    

    这是我用来下载由我们的 Django 服务器按需生成的 PDF 文件的工具,她似乎运行得非常好。

    附录

    我们的网站之所以这样做,而不是只为后续调用返回文件名,是因为它在服务器 I/O 上更容易一些。所选择的解决方案意味着所请求的文件必须存在于服务器某处 - 很可能在磁盘上。 (也许可以使用 PHP 的 tmpfile() 将生成的文件保存在内存中,但我对 PHP 的了解有限,所以我不知道如何在 HTTP 调用之间保存该文件)。

    我的项目制作了大量的 PDF 文件 - 可能有数百页。我真的,真的不想从这些数据中创建一个实际的文件对象,将它保存到磁盘,然后几乎立即从磁盘上读回它(我知道这不是 完全 服务器是如何做的,但无论如何你切片它做的工作比必要的要多)。服务器制作了 PDF,它在内存中,为什么不只是......把它还给客户端?

    像这样返回文件意味着不需要做任何额外的清理工作 - 一旦 Base64 离开大楼,就是这样。磁盘上没有文件,因此以后无需处理任何事情(好坏取决于您的需要)。

    【讨论】:

    • 你为什么这样做而不是直接分发 pdf 文件?我的意思是除了造成额外的流量。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2017-08-15
    • 2011-07-25
    • 2012-03-10
    • 1970-01-01
    • 2020-06-23
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多