通过 AJAX 调用 PHP 下载文件

40

我有一个按钮,onclick时它将调用一个ajax函数。

这是我的ajax函数

function csv(){

    ajaxRequest = ajax();//ajax() is function that has all the XML HTTP Requests

    postdata = "data=" + document.getElementById("id").value;

    ajaxRequest.onreadystatechange = function(){
        var ajaxDisplay = document.getElementById('ajaxDiv');
        if(ajaxRequest.readyState == 4 && ajaxRequest.status==200){
            ajaxDisplay.innerHTML = ajaxRequest.responseText;           
        }
    }

    ajaxRequest.open("POST","csv.php",false);
    ajaxRequest.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded');
    ajaxRequest.send(postdata);
}

我根据用户输入创建csv文件。创建完成后,我希望它提示下载或强制下载(最好是强制下载)。我在php文件结尾使用以下脚本来下载文件。如果我在一个单独的文件中运行这个脚本,它可以正常工作。

$fileName = 'file.csv';
$downloadFileName = 'newfile.csv';

if (file_exists($fileName)) {
    header('Content-Description: File Transfer');
    header('Content-Type: text/csv');
    header('Content-Disposition: attachment; filename='.$downloadFileName);
    ob_clean();
    flush();
    readfile($fileName);
    exit;
}
echo "done";

但是,如果我在csv.php的末尾运行它,它会将file.csv文件的内容输出到页面上(即ajaxDiv中),而不是下载。

有没有一种方法可以强制下载csv.php的最后一个文件?

6个回答

45

AJAX不适用于下载文件。弹出一个新窗口,以下载链接作为其地址,或者使用document.location =...


18
我认为从技术上讲最好使用window.location进行设置;请参考此讨论:http://stackoverflow.com/questions/7857878/window-location-vs-document-location - yitwail
3
为什么 AJAX 不适用于下载文件? - Daniel
我无法设置window.location,因为我需要为我的请求设置一些特殊的头文件。 - Ionel Lupu

21

使用jQuery的非常简单的解决方案:

在客户端上:

$('.act_download_statement').click(function(e){
    e.preventDefault();
    form = $('#my_form');
    form.submit();
});

同时在服务器端,确保你发送回正确的Content-Type头信息,这样浏览器就会知道这是一个附件并开始下载。


1
这对我来说非常有效,可以将数据POST到服务器并获得ZIP文件下载作为响应,无需重新加载页面。+1 - stephen.hanson
你能否给出更多细节并提供一些代码,说明两端发生了什么? - Ilan Biala
1
代码很好,但如果出现错误,它会重定向到一个不必要的错误页面。 - neel shah
如果我想在业务逻辑实现和创建文件内容的过程中显示一些加载器,该怎么办? - Sunil Pachlangia

15

@joe:非常感谢,这是一个很好的提示!

我的问题稍微有些难一些:

  1. 发送一个带有POST数据的AJAX请求,让服务器生成一个ZIP文件

  2. 获取响应

  3. 下载ZIP文件

这是我使用jQuery处理AJAX请求时的方法:

  1. 初始POST请求:

var parameters = {
     pid     : "mypid",
   "files[]": ["file1.jpg","file2.jpg","file3.jpg"]
}<p></p>

</code><p><code>var options = {
   url: "request/url",//replace with your request url
   type: "POST",//replace with your request type
   data: parameters,//see above
   context: document.body,//replace with your contex
   success: function(data){
    if (data) {
        if (data.path) {
                    //Create an hidden iframe, with the 'src' attribute set to the created ZIP file.
            var dlif = $('<code><iframe/></code>',{'src':data.path}).hide();
            //Append the iFrame to the context
            this.append(dlif);
        } else if (data.error) {
            alert(data.error);
        } else {
            alert('Something went wrong');
        }
    }
}
};
$.ajax(options);</code></p></pre>「request/url」处理zip文件的创建(不属于本问题的讨论范围, 不会贴出完整代码), 并返回以下JSON对象. 类似于:<pre class="guess-JavaScript"><code> //Code to create the zip file
 //......
 //Id of the file
 $zipid = "myzipfile.zip"
 //Download Link - it can be prettier
 $dlink = 'http://'.$_SERVER["SERVER_NAME"].'/request/download&file='.$zipid;
 //JSON response to be handled on the client side
 $result = '{"success":1,"path":"'.$dlink.'","error":null}';
 header('Content-type: application/json;');
 echo $result;

"request/download" 可以进行某些安全检查(如果需要的话),并生成文件传输:

$fn = $_GET['file'];
if ($fn) {
  //Perform security checks
  //.....check user session/role/whatever
  $result = $_SERVER['DOCUMENT_ROOT'].'/path/to/file/'.$fn;
  if (file_exists($result)) {
    header('Content-Description: File Transfer');
    header('Content-Type: application/force-download');
    header('Content-Disposition: attachment; filename='.basename($result));
    header('Content-Transfer-Encoding: binary');
    header('Expires: 0');
    header('Cache-Control: must-revalidate, post-check=0, pre-check=0');
    header('Pragma: public');
    header('Content-Length: ' . filesize($result));
    ob_clean();
    flush();
    readfile($result);
    @unlink($result);
  }

}

只需要将查询字符串中的 & 更改为 ?。非常感谢您对此的帮助!- 我尝试进行编辑,但它太小了,不允许我进行编辑!$dlink = 'http://'.$_SERVER["SERVER_NAME"].'/request/download?file='.$zipid; - Mike Wells

9

我用一个隐藏的iframe完成了这个任务。我使用perl而不是php,所以只能提供概念,而非代码解决方案。

客户端发送Ajax请求到服务器,导致文件内容生成。这将保存为服务器上的临时文件,并将文件名返回给客户端。

客户端(JavaScript)接收文件名,并设置iframe的src为某个会传递该文件的url,例如:

$('iframe_dl').src="/app?download=1&filename=" + the_filename

服务器读取文件,删除它,并将流发送给客户端,并带有以下标头:
Content-Type:'application/force-download'
Content-Disposition:'attachment; filename=the_filename'

运作得像魔法一样顺畅。

2
“slurps” 是一个技术术语吗? - Zach Smith
@ZachSmith,是的。Slurping是在处理之前读取整个文件的行为。我正在描述自己的过程,而且这些文件都是已知大小的,远远小于1mb。如果文件大小无法确定或非常大,则某种流程处理会更好。 - joe

6

您无法通过ajax直接下载文件。

您可以在页面上放置一个链接,链接的URL是通过ajax调用返回的文件地址。另一种方法是使用一个隐藏的iframe,并动态设置该iframe的来源URL。这样,您就可以在不刷新页面的情况下下载文件了。

以下是代码示例:

$.ajax({
    url : "yourURL.php",
    type : "GET",
    success : function(data) {
        $("#iframeID").attr('src', 'downloadFileURL');
    }
});

发布相关代码将有助于说明您在这里想要表达的内容。 - larsAnders
在您的 Ajax 成功响应中,可以设置隐藏的 iframe src。 - Naimish B. Makwana

1
你可以这样做:
在你的 PHP REST API(后端)上:
header('Content-Description:File Transfer');
header('Content-Type:application/octet-stream');
header('Content-Disposition:attachment; filename=' . $toBeDownloaded);
header('Content-Transfer-Encoding:binary');
header('Expires:0');
header('Cache-Control:must-revalidate');
header('Pragma:public');
header('Content-Length:'.filesize($toBeDownloaded));
readfile($toBeDownloaded);
exit;

在您的 JavaScript 代码中:(前端)

const REQUEST = `API_PATH`;
try {
  
  const response = await fetch(REQUEST, {
    method: 'GET',
  })

  const fileUploaded = await response.blob();
  const url = window.URL.createObjectURL(fileUploaded);
  const link = document.createElement('a');
  link.href = url;
  link.setAttribute('download', 'YOUR_FILE_NAME_WITH_EXTENSION');
  document.body.appendChild(link);
  link.click();
} catch (error) {
  console.log(error)
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接