使用jQuery(GET)下载文件的困难

时间:2017-08-10 20:48:15

标签: javascript php jquery ajax

我有一个响应这样的请求的API服务器:

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;
}

解决方案:

罗普指出我正确的方向。我实际上不需要做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;        
   });

3 个答案:

答案 0 :(得分:2)

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

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

答案 1 :(得分:1)

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

在我看来,你需要用

替换所有的ajax代码

document.location =“http://localhost:8080/slim3/public/api/v1/files/”+ filename;

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

答案 2 :(得分:1)

如果按需生成文件,您可以执行的操作是在Base64中对您的文件进行PHP编码 - 如this,设置相应的类型 - 并将 返回给客户端。将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 - 可能是数百页。我真的,真的不想要从这些数据中创建一个实际的文件对象,将它保存到磁盘,然后几乎立即从磁盘上读回来(我知道那不是<\ n 完全服务器是如何做的,但无论如何你切分它做更多的工作而不是必要的)。服务器有PDF制作,它在内存中,为什么不只是...把它还给客户端?

返回这样的文件意味着一个人不需要做任何额外的清理工作 - 一旦Base64离开了建筑物,那就是它。磁盘上没有文件,因此以后不需要处理任何内容(根据您的需要可能有好有坏)。