【问题标题】:Download a file by jQuery.Ajax通过 jQuery.Ajax 下载文件
【发布时间】:2021-05-13 13:26:28
【问题描述】:

我在服务器端有一个用于文件下载的 Struts2 动作。

<action name="download" class="com.xxx.DownAction">
    <result name="success" type="stream">
        <param name="contentType">text/plain</param>
        <param name="inputName">imageStream</param>
        <param name="contentDisposition">attachment;filename={fileName}</param>
        <param name="bufferSize">1024</param>
    </result>
</action>

但是,当我使用 jQuery 调用操作时:

$.post(
  "/download.action",{
    para1:value1,
    para2:value2
    ....
  },function(data){
      console.info(data);
   }
);

在 Firebug 中,我看到使用 二进制流 检索数据。我想知道如何打开文件下载窗口让用户可以将文件保存到本地?

【问题讨论】:

  • 尽管平台不同,但我将其标记为重复,因为据我所知,解决方案是相同的(您不能也不需要通过 Ajax 执行此操作)。跨度>
  • 所以,不用 ajax,只需使用 window.location="download.action?para1=value1...."?

标签: javascript jquery ajax jsp download


【解决方案1】:

2019 年现代浏览器更新

这是我现在推荐的方法,但有几点需要注意:

  • 需要相对现代的浏览器
  • 如果文件预计非常大,您可能应该执行类似于原始方法(iframe 和 cookie)的操作,因为以下某些操作可能会消耗至少与正在下载的文件和/或其他有趣的 CPU 副作用。

fetch('https://jsonplaceholder.typicode.com/todos/1')
  .then(resp => resp.blob())
  .then(blob => {
    const url = window.URL.createObjectURL(blob);
    const a = document.createElement('a');
    a.style.display = 'none';
    a.href = url;
    // the filename you want
    a.download = 'todo-1.json';
    document.body.appendChild(a);
    a.click();
    window.URL.revokeObjectURL(url);
    alert('your file has downloaded!'); // or you know, something with better UX...
  })
  .catch(() => alert('oh no!'));

2012 年原创的基于 jQuery/iframe/Cookie 的方法

Bluish 在这点上是完全正确的,你不能通过 Ajax 来做到这一点,因为 JavaScript 不能将文件直接保存到用户的计算机上(出于安全考虑)。不幸的是,将主窗口的 URL 指向您的文件下载意味着您​​几乎无法控制文件下载发生时的用户体验。

我创建了jQuery File Download,它允许通过 OnSuccess 和 OnFailure 回调完成“类似 Ajax”的文件下载体验,以提供更好的用户体验。看看我的blog post 关于插件解决的常见问题和一些使用它的方法以及demo of jQuery File Download in action。这是source

这是一个简单的用例演示,使用带有 Promise 的插件 sourcedemo page 还包括许多其他“更好的用户体验”示例。

$.fileDownload('some/file.pdf')
    .done(function () { alert('File download a success!'); })
    .fail(function () { alert('File download failed!'); });

根据您需要支持的浏览器,您可以使用https://github.com/eligrey/FileSaver.js/,它允许比 jQuery File Download 使用的 IFRAME 方法更明确的控制。

【讨论】:

  • 我喜欢你构建的东西,但我怀疑为了获得更多 StackOverFlow 功劳,你的答案应该包含更多细节。特别是关于你是如何解决问题的。
  • 如果你能确切地提到这个“插件”是如何绕过限制的,而不是强迫我们去你的博客/插件源查看它,那就太好了。例如,它是否改为发布到 iframe?是不是需要远程脚本来保存文件并返回一个 url?
  • @asgerhallas 当然可以,但是如果链接消失,那将完全没用。
  • 我同意,博客是一个更好的地方,可以详细说明如何使用您的插件以及它是如何工作的。但是您至少可以简要概述一下该插件如何解决问题。例如,这通过让服务器设置 cookie 并让您的 javascript 不断查找 cookie 直到它存在来解决问题。一旦它存在,我们可以假设下载完成。有了这些信息,人们就可以轻松快速地推出自己的解决方案,并且答案不再 100% 依赖于您的博客/插件/jquery,并且可以应用于其他库。
  • Royi,据我了解,AJAX从不支持文件下载,导致文件下载弹出窗口保存到磁盘。你找到了我不知道的方法吗?
【解决方案2】:

没有人发布这个@Pekka's solution...所以我会发布它。它可以帮助某人。

您不需要通过 Ajax 执行此操作。只需使用

window.location="download.action?para1=value1...."

【讨论】:

  • 不错的一个......因为我正在努力处理下载文件提示和使用 jquery ajax..这个解决方案非常适合我..+1
  • 请注意,这需要服务器将 Content-Disposition 标头值设置为“附件”,否则浏览器将重定向到(并显示)响应内容
  • 或者使用window.open(&lt;url&gt;, '_blank'); 确保下载不会替换您当前的浏览器内容(无论 Content-Disposition 标头如何)。
  • 这个方案的问题是如果操作失败/服务器返回错误,你的页面会被重定向到错误页面。要解决这个问题,请使用 iFrame 解决方案
  • 这个解决方案的真正问题 - 问题是关于 POST 请求。
【解决方案3】:

您可以使用 HTML5

注意:返回的文件数据必须是 base64 编码,因为您不能对二进制数据进行 JSON 编码

在我的AJAX 回复中,我有一个如下所示的数据结构:

{
    result: 'OK',
    download: {
        mimetype: string(mimetype in the form 'major/minor'),
        filename: string(the name of the file to download),
        data: base64(the binary data as base64 to download)
    }
}

这意味着我可以通过 AJAX 执行以下操作来保存文件

var a = document.createElement('a');
if (window.URL && window.Blob && ('download' in a) && window.atob) {
    // Do it the HTML5 compliant way
    var blob = base64ToBlob(result.download.data, result.download.mimetype);
    var url = window.URL.createObjectURL(blob);
    a.href = url;
    a.download = result.download.filename;
    a.click();
    window.URL.revokeObjectURL(url);
}

函数base64ToBlob取自here,必须按照该函数使用

function base64ToBlob(base64, mimetype, slicesize) {
    if (!window.atob || !window.Uint8Array) {
        // The current browser doesn't have the atob function. Cannot continue
        return null;
    }
    mimetype = mimetype || '';
    slicesize = slicesize || 512;
    var bytechars = atob(base64);
    var bytearrays = [];
    for (var offset = 0; offset < bytechars.length; offset += slicesize) {
        var slice = bytechars.slice(offset, offset + slicesize);
        var bytenums = new Array(slice.length);
        for (var i = 0; i < slice.length; i++) {
            bytenums[i] = slice.charCodeAt(i);
        }
        var bytearray = new Uint8Array(bytenums);
        bytearrays[bytearrays.length] = bytearray;
    }
    return new Blob(bytearrays, {type: mimetype});
};

如果您的服务器正在转储要保存的文件数据,这很好。但是,我还没有完全弄清楚如何实现 HTML4 后备

【讨论】:

  • a.click() 在 Firefox 中似乎不起作用...有什么想法吗?
  • 在某些浏览器中,您可能需要将 a 添加到 dom 才能使此代码正常工作和/或删除 revokeObjectURL 部分:document.body.appendChild(a)
  • 拯救了我的一天(也可能是一份工作:))无论如何都不是 javascript 专家……更多的是 java 人。但是,我不知道为什么简单的“createObjectURL(new Blob([atob(base64)]))”不起作用!它根本没有,而所有的直觉都说它必须。咕噜……
  • var bytechars = atob(base64) 行会引发错误JavaScript runtime error: InvalidCharacterError。我正在使用 Chrome 版本 75.0.3770.142 但我不知道,这里有什么问题。
【解决方案4】:

让浏览器下载文件的简单方法是这样发出请求:

 function downloadFile(urlToSend) {
     var req = new XMLHttpRequest();
     req.open("GET", urlToSend, true);
     req.responseType = "blob";
     req.onload = function (event) {
         var blob = req.response;
         var fileName = req.getResponseHeader("fileName") //if you have the fileName header available
         var link=document.createElement('a');
         link.href=window.URL.createObjectURL(blob);
         link.download=fileName;
         link.click();
     };

     req.send();
 }

这会打开浏览器下载弹出窗口。

【讨论】:

  • 谢谢,我使用了这个解决方案。像魅力一样工作。此外,如果您没有从响应中获得 blob,只需创建一个新的 Blob。
  • 一个更好的IE处理版本link
  • 如果您使用 IE11,来自@startsWith_R 的链接真的很有帮助
【解决方案5】:

1.框架无关:Servlet 下载文件作为附件

<!-- with JS -->
<a href="javascript:window.location='downloadServlet?param1=value1'">
    download
</a>

<!-- without JS -->
<a href="downloadServlet?param1=value1" >download</a>

2。 Struts2 Framework:动作下载文件作为附件

<!-- with JS -->
<a href="javascript:window.location='downloadAction.action?param1=value1'">
    download
</a>

<!-- without JS -->
<a href="downloadAction.action?param1=value1" >download</a>

最好使用&lt;s:a&gt; 标签和OGNL 指向一个用&lt;s:url&gt; 标签创建的URL

<!-- without JS, with Struts tags: THE RIGHT WAY -->    
<s:url action="downloadAction.action" var="url">
    <s:param name="param1">value1</s:param>
</s:ulr>
<s:a href="%{url}" >download</s:a>

在上述情况下,您需要Content-Disposition标头写入response,指定需要下载文件(attachment) 并且没有被浏览器打开 (inline)。您也需要指定内容类型,并且您可能想要添加文件名和长度(以帮助浏览器绘制逼真的进度条)。

例如,下载 ZIP 时:

response.setContentType("application/zip");
response.addHeader("Content-Disposition", 
                   "attachment; filename=\"name of my file.zip\"");
response.setHeader("Content-Length", myFile.length()); // or myByte[].length...

使用 Struts2(除非您将 Action 用作 Servlet,例如 hack for direct streaming),您无需直接向响应写入任何内容;只需使用 Stream result type 并在 struts.xml 中进行配置即可:EXAMPLE

<result name="success" type="stream">
   <param name="contentType">application/zip</param>
   <param name="contentDisposition">attachment;filename="${fileName}"</param>
   <param name="contentLength">${fileLength}</param>
</result>

3.框架无关(/Struts2 框架):Servlet(/Action) 在浏览器中打开文件

如果你想在浏览器中打开文件,而不是下载它,Content-disposition必须设置为inline,但目标不能是当前窗口位置;您必须定位由 javascript 创建的新窗口、页面中的 &lt;iframe&gt;,或使用“已讨论”目标“_blank”动态创建的新窗口:

<!-- From a parent page into an IFrame without javascript -->   
<a href="downloadServlet?param1=value1" target="iFrameName">
    download
</a>

<!-- In a new window without javascript --> 
<a href="downloadServlet?param1=value1" target="_blank">
    download
</a>

<!-- In a new window with javascript -->    
<a href="javascript:window.open('downloadServlet?param1=value1');" >
    download
</a>

【讨论】:

  • 先生,您的输入:“Content-Disposition”、“inline;.... 拯救了可怜的程序员的一天 :)
  • 这是唯一提到“window.open”的答案(其中一个 cmets 提到了它)。
  • 如果参数很多就不行,因为会报too long url错误。
【解决方案6】:

我创建了一个小函数作为解决方案(受@JohnCulviner 插件的启发):

// creates iframe and form in it with hidden field,
// then submit form with provided data
// url - form url
// data - data to form field
// input_name - form hidden input name

function ajax_download(url, data, input_name) {
    var $iframe,
        iframe_doc,
        iframe_html;

    if (($iframe = $('#download_iframe')).length === 0) {
        $iframe = $("<iframe id='download_iframe'" +
                    " style='display: none' src='about:blank'></iframe>"
                   ).appendTo("body");
    }

    iframe_doc = $iframe[0].contentWindow || $iframe[0].contentDocument;
    if (iframe_doc.document) {
        iframe_doc = iframe_doc.document;
    }

    iframe_html = "<html><head></head><body><form method='POST' action='" +
                  url +"'>" +
                  "<input type=hidden name='" + input_name + "' value='" +
                  JSON.stringify(data) +"'/></form>" +
                  "</body></html>";

    iframe_doc.open();
    iframe_doc.write(iframe_html);
    $(iframe_doc).find('form').submit();
}

带有点击事件的演示:

$('#someid').on('click', function() {
    ajax_download('/download.action', {'para1': 1, 'para2': 2}, 'dataname');
});

【讨论】:

  • 这会以一种非常奇怪的方式将数据发送到服务器。我想知道是否可以对其进行更改以创建合规的 POST?
【解决方案7】:

我遇到了同样的问题并成功解决了。我的用例是这样的。

"将 JSON 数据发送到服务器并接收一个 excel 文件。 该 excel 文件由服务器创建并作为对客户端的响应返回。在浏览器中将该响应下载为具有自定义名称的文件

$("#my-button").on("click", function(){

// Data to post
data = {
    ids: [1, 2, 3, 4, 5]
};

// Use XMLHttpRequest instead of Jquery $ajax
xhttp = new XMLHttpRequest();
xhttp.onreadystatechange = function() {
    var a;
    if (xhttp.readyState === 4 && xhttp.status === 200) {
        // Trick for making downloadable link
        a = document.createElement('a');
        a.href = window.URL.createObjectURL(xhttp.response);
        // Give filename you wish to download
        a.download = "test-file.xls";
        a.style.display = 'none';
        document.body.appendChild(a);
        a.click();
    }
};
// Post data to URL which handles post request
xhttp.open("POST", excelDownloadUrl);
xhttp.setRequestHeader("Content-Type", "application/json");
// You should set responseType as blob for binary responses
xhttp.responseType = 'blob';
xhttp.send(JSON.stringify(data));
});

上面的sn-p只是在做后续

  • 使用 XMLHttpRequest 将数组作为 JSON 发布到服务器。
  • 在以 blob(二进制)形式获取内容后,我们将创建一个可下载的 URL 并将其附加到不可见的“a”链接,然后单击它。我在这里做了一个 POST 请求。相反,您也可以使用简单的 GET。我们不能通过 Ajax 下载文件,必须使用 XMLHttpRequest。

这里我们需要在服务器端仔细设置一些东西。我在 Python Django HttpResponse 中设置了一些标头。如果您使用其他编程语言,则需要相应地设置它们。

# In python django code
response = HttpResponse(file_content, content_type="application/vnd.openxmlformats-officedocument.spreadsheetml.sheet")

由于我在这里下载 xls(excel),所以我将 contentType 调整为上述一个。您需要根据您的文件类型进行设置。您可以使用此技术下载任何类型的文件。

【讨论】:

  • "我们不能通过 Ajax 下载文件,必须使用 XMLHttpRequest"。 XMLHttpRequest 根据定义是 AJAX。否则,现代网络浏览器的绝佳解决方案。对于不支持HTMLAnchorElement.download 的IE,我正在考虑将其与专有的msSaveOrOpenBlob 方法结合起来。
【解决方案8】:

好的,基于 ndpu 的代码,这里有一个改进的(我认为)ajax_download 版本;-

function ajax_download(url, data) {
    var $iframe,
        iframe_doc,
        iframe_html;

    if (($iframe = $('#download_iframe')).length === 0) {
        $iframe = $("<iframe id='download_iframe'" +
                    " style='display: none' src='about:blank'></iframe>"
                   ).appendTo("body");
    }

    iframe_doc = $iframe[0].contentWindow || $iframe[0].contentDocument;
    if (iframe_doc.document) {
        iframe_doc = iframe_doc.document;
    }

    iframe_html = "<html><head></head><body><form method='POST' action='" +
                  url +"'>" 

    Object.keys(data).forEach(function(key){
        iframe_html += "<input type='hidden' name='"+key+"' value='"+data[key]+"'>";

    });

        iframe_html +="</form></body></html>";

    iframe_doc.open();
    iframe_doc.write(iframe_html);
    $(iframe_doc).find('form').submit();
}

这样使用;-

$('#someid').on('click', function() {
    ajax_download('/download.action', {'para1': 1, 'para2': 2});
});

参数作为正确的后参数发送,就像来自输入一样,而不是像前面的示例那样作为 json 编码的字符串。

CAVEAT:请注意这些表格上的变量注入的可能性。可能有一种更安全的方法来编码这些变量。或者考虑逃离它们。

【讨论】:

  • 这是一个工作示例。谢谢。没有 iframe 但没有 window.location 是否可以做到这一点?
  • 我想您可以将隐藏的表单附加到 DOM 的底部。也可能值得探索的是 Shadow dom 的使用,尽管这在旧版浏览器上不一定得到很好的支持。
  • 在这段代码中我收到了这个错误。 Uncaught SecurityError: Blocked a frame with origin "http://foo.bar.com" from accessing a frame with origin "null". The frame requesting access has a protocol of "http", the frame being accessed has a protocol of "data". Protocols must match.
  • 如何将此表单映射到某个模型类?我有:@ResourceMapping() public void downloadFile(final ResourceRequest request, final ResourceResponse response, @ModelAttribute("downForm") FormModel model) 但它不起作用..
  • void :这可能是某种跨源安全问题。这可能是一个完整的堆栈溢出问题。 @bartex9:这在很大程度上取决于您使用哪种框架。但原则是获取名称和路径并将其存储,同时将文件本身推送到文件系统的 Web 可访问区域,或类似 amazon S3 的高可用性
【解决方案9】:

这就是我所做的,纯 javascript 和 html。没有测试,但这应该适用于所有浏览器。

Javascript 函数

var iframe = document.createElement('iframe');
iframe.id = "IFRAMEID";
iframe.style.display = 'none';
document.body.appendChild(iframe);
iframe.src = 'SERVERURL'+'?' + $.param($scope.filtro);
iframe.addEventListener("load", function () {
     console.log("FILE LOAD DONE.. Download should start now");
});

仅使用所有浏览器都支持的组件,无需额外 图书馆。

这是我的服务器端 JAVA Spring 控制器代码。

@RequestMapping(value = "/rootto/my/xlsx", method = RequestMethod.GET)
public void downloadExcelFile(@RequestParam(value = "param1", required = false) String param1,
    HttpServletRequest request, HttpServletResponse response)
            throws ParseException {

    Workbook wb = service.getWorkbook(param1);
    if (wb != null) {
        try {
            String fileName = "myfile_" + sdf.format(new Date());
            response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet");
            response.setHeader("Content-disposition", "attachment; filename=\"" + fileName + ".xlsx\"");
            wb.write(response.getOutputStream());
            response.getOutputStream().close();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
    }

【讨论】:

  • 看起来你的加载事件没有被调用 Content-disposition 附件内容(因为没有任何东西加载到 iframe 中),如果它适合你(你得到 console.log)请发布一个示例
  • 这是一个快速小提琴jsfiddle.net/y2xezyoj,一旦将pdf文件加载到iframe中,就会触发加载事件。这个小提琴不会下载,因为下载的关键是在服务器端“ response.setHeader("Content-disposition", "附件; filename=\"" + fileName + ".xlsx\"");"
  • 是的,在这种情况下它会起作用,但是如果文件被下载,即服务器发送 Content-Disposition: 附件,那么 load 事件将不会触发,这是我的观点
  • 你是完全正确的 load 事件在服务器完成处理开始发送文件后立即触发。这就是我一直在寻找的,1-阻止按钮并显示处理,以便用户可以获得正在发生的事情的反馈。 2 - 然后当服务器完成处理并即将发送文件 3 - (加载事件被触发)我解锁按钮并删除处理微调器 4 - 用户现在弹出保存文件或浏览器开始下载它定义的下载位置。对不起我的英语。
【解决方案10】:

AJAX接收文件后如何下载文件

文件创建时间长需要显示PRELOADER时方便

提交网络表单时的示例:

<script>
$(function () {
    $('form').submit(function () {
        $('#loader').show();
        $.ajax({
            url: $(this).attr('action'),
            data: $(this).serialize(),
            dataType: 'binary',
            xhrFields: {
                'responseType': 'blob'
            },
            success: function(data, status, xhr) {
                $('#loader').hide();
                // if(data.type.indexOf('text/html') != -1){//If instead of a file you get an error page
                //     var reader = new FileReader();
                //     reader.readAsText(data);
                //     reader.onload = function() {alert(reader.result);};
                //     return;
                // }
                var link = document.createElement('a'),
                    filename = 'file.xlsx';
                // if(xhr.getResponseHeader('Content-Disposition')){//filename 
                //     filename = xhr.getResponseHeader('Content-Disposition');
                //     filename=filename.match(/filename="(.*?)"/)[1];
                //     filename=decodeURIComponent(escape(filename));
                // }
                link.href = URL.createObjectURL(data);
                link.download = filename;
                link.click();
            }
        });
        return false;
    });
});
</script>

为了简化示例,注释掉了可选功能。

无需在服务器上创建临时文件。

在 jQuery v2.2.4 上可以。老版本会报错:

Uncaught DOMException: Failed to read the 'responseText' property from 'XMLHttpRequest': The value is only accessible if the object's 'responseType' is '' or 'text' (was 'blob').

【讨论】:

  • 要从 Content-Disposition 获取文件名,这个匹配对我有用:filename.match(/filename=(.*)/)[1](不带双引号或问号)-regex101.com/r/2AsD4y/2。但是,经过大量搜索,您的解决方案是唯一有效的解决方案。
  • 这是另一种获取文件名的更懒惰的方法 :) npmjs.com/package/content-disposition
【解决方案11】:
function downloadURI(uri, name) 
{
    var link = document.createElement("a");
    link.download = name;
    link.href = uri;
    link.click();
}

【讨论】:

  • 你能解释一下你的答案吗?这将有助于其他人了解您所做的事情,以便他们将您的技术应用于他们的情况。
  • 只是一个警告:Safari 和 IE 不支持 download 属性,因此您的文件最终将具有“未知”名称
【解决方案12】:

我尝试下载一个 CSV 文件,然后在下载完成后执行一些操作。所以我需要实现一个合适的callback函数。

使用window.location="..." 不是一个好主意,因为下载完成后我无法操作该程序。像这样,改变标题,所以这不是一个好主意。

fetch 是一个不错的选择,但 it cannot support IE 11。而window.URL.createObjectURL不支持IE 11,可以参考this

这是我的代码,它类似于 Shahrukh Alam 的代码。但是您应该注意window.URL.createObjectURL 可能会造成内存泄漏。您可以参考this。当响应到达时,数据将被存储到浏览器的内存中。因此,在您单击a 链接之前,该文件已经下载完毕。意思是下载后可以做任何事情。

$.ajax({
    url: 'your download url',
    type: 'GET',
}).done(function (data, textStatus, request) {
    // csv => Blob
    var blob = new Blob([data]);

    // the file name from server.
    var fileName = request.getResponseHeader('fileName');

    if (window.navigator && window.navigator.msSaveOrOpenBlob) { // for IE
    window.navigator.msSaveOrOpenBlob(blob, fileName);
    } else { // for others
    var 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);

    //Do something after download 
    ...

    }
}).then(after_download)
}

【讨论】:

    【解决方案13】:

    我的方法完全基于 jQuery。我的问题是它必须是一个 POST-HTTP 调用。我希望它由 jQuery 单独完成。

    解决办法:

    $.ajax({
        type: "POST",
        url: "/some/webpage",
        headers: {'X-CSRF-TOKEN': csrfToken},
        data: additionalDataToSend,
        dataType: "text",
        success: function(result) {
            let blob = new Blob([result], { type: "application/octetstream" }); 
    
            let a = document.createElement('a');
            a.href = window.URL.createObjectURL(blob);
            a.download = "test.xml";;
            document.body.appendChild(a);
            a.click();
            document.body.removeChild(a);
            window.URL.revokeObjectURL(a.href);
                            
            ...
        },
        error: errorDialog
    });
    

    解释:

    我和其他许多人所做的就是在网页上创建一个链接,指示应该下载目标并将http-request的结果作为目标。之后,我将链接附加到文档,而不是简单地单击链接并随后删除链接。您不再需要 iframe。

    神奇在于线条

    let blob = new Blob([result], { type: "application/octetstream" }); 
    a.href = window.URL.createObjectURL(blob);
    

    有趣的是,这个解决方案只适用于“blob”。正如您在其他答案中看到的那样,有些只是使用 blob,但没有解释为什么以及如何创建它。 如您所见,例如在Mozilla developer documentation 中,您需要一个文件、媒体资源或 blob 才能使函数“createObjectURL()”工作。问题是您的 http-response 可能不是其中的任何一个。 因此,您必须做的第一件事是将响应转换为 blob。这就是第一行的作用。然后,您可以将“createObjectURL”与新创建的 blob 一起使用。 如果您单击该链接,您的浏览器将打开一个文件保存对话框,您可以保存您的数据。显然,您可能无法为要下载的文件定义一个固定的文件名。然后你必须让你的回答更复杂,就像卢克的回答一样。

    不要忘记释放内存,尤其是在处理大文件时。有关更多示例和信息,您可以查看the details of the JS blob object

    【讨论】:

    • 谢谢!但应该是:revokeObjectURL() window.URL.revokeObjectURL(a.href);
    • 我正在返回一个 zip 文件,但是当我使用此方法时,我的 zip 文件无效。我必须从 ajax 调用中删除 dataType 才能使其工作。对于 blob 创建,我使用了您在上面使用的选项 application/zip,并尝试将其完全删除。
    • @scotts 谢谢。我已经更改了我的代码。
    【解决方案14】:

    在上述答案中添加更多内容以下载文件

    下面是一些生成字节数组的java spring 代码

    @RequestMapping(value = "/downloadReport", method = { RequestMethod.POST })
        public ResponseEntity<byte[]> downloadReport(
                @RequestBody final SomeObejct obj, HttpServletResponse response) throws Exception {
    
            OutputStream out = new ByteArrayOutputStream();
            // write something to output stream
            HttpHeaders respHeaders = new HttpHeaders();
            respHeaders.setContentType(MediaType.APPLICATION_OCTET_STREAM);
            respHeaders.add("X-File-Name", name);
            ByteArrayOutputStream bos = (ByteArrayOutputStream) out;
            return new ResponseEntity<byte[]>(bos.toByteArray(), respHeaders, HttpStatus.CREATED);
        }
    

    现在在使用 FileSaver.js 的 javascript 代码中,可以使用以下代码下载文件

    var json=angular.toJson("somejsobject");
    var url=apiEndPoint+'some url';
    var xhr = new XMLHttpRequest();
    //headers('X-File-Name')
    xhr.onreadystatechange = function() {
        if (this.readyState == 4 && this.status == 201) {
            var res = this.response;
            var fileName=this.getResponseHeader('X-File-Name');
            var data = new Blob([res]);
            saveAs(data, fileName); //this from FileSaver.js
        }
    }    
    xhr.open('POST', url);
    xhr.setRequestHeader('Authorization','Bearer ' + token);
    xhr.setRequestHeader('Content-Type', 'application/json');
    xhr.responseType = 'arraybuffer';
    xhr.send(json);
    

    以上将下载文件

    【讨论】:

      【解决方案15】:

      在 Rails 中,我是这样做的:

      function download_file(file_id) {
        let url       = '/files/' + file_id + '/download_file';
          $.ajax({
          type: 'GET',
          url: url,
          processData: false,
          success: function (data) {
             window.location = url;
          },
          error: function (xhr) {
           console.log(' Error:  >>>> ' + JSON.stringify(xhr));
          }
         });
       }
      

      诀窍在于 window.location 部分。控制器的方法如下:

      # GET /files/{:id}/download_file/
      def download_file
          send_file(@file.file,
                :disposition => 'attachment',
                :url_based_filename => false)
      end
      

      【讨论】:

      • 快速提问,这不会生成两次文件吗?一旦您发送 ajax 请求。然后,您也将页面重定向到相同的 URL。我们怎样才能消除它?
      • 不是我的情况。不过我只在 Chrome 上测试过。
      • 由于 coderhs 已经正确声明,该操作被调用了两次。
      • 我也被调用了两次。
      【解决方案16】:

      使用window.openhttps://developer.mozilla.org/en-US/docs/Web/API/Window/open

      例如,您可以将这行代码放在点击处理程序中:

      window.open('/file.txt', '_blank');
      

      它将打开一个新选项卡(因为 '_blank' 窗口名称)并且该选项卡将打开 URL。

      你的服务器端代码也应该是这样的:

      res.set('Content-Disposition', 'attachment; filename=file.txt');
      

      这样,浏览器应该提示用户将文件保存到磁盘,而不是只显示文件。它还会自动关闭刚刚打开的选项卡。

      【讨论】:

        【解决方案17】:

        好的,这是使用 MVC 时的工作代码,您正在从控制器获取文件

        假设您已经声明并填充了字节数组,您唯一需要做的就是使用 File 函数(使用 System.Web.Mvc)

        byte[] bytes = .... insert your bytes in the array
        return File(bytes, System.Net.Mime.MediaTypeNames.Application.Octet, "nameoffile.exe");
        

        然后,在同一个控制器中,添加这 2 个函数

        protected override void OnResultExecuting(ResultExecutingContext context)
            {
                CheckAndHandleFileResult(context);
        
                base.OnResultExecuting(context);
            }
        
            private const string FILE_DOWNLOAD_COOKIE_NAME = "fileDownload";
        
            /// <summary>
            /// If the current response is a FileResult (an MVC base class for files) then write a
            /// cookie to inform jquery.fileDownload that a successful file download has occured
            /// </summary>
            /// <param name="context"></param>
            private void CheckAndHandleFileResult(ResultExecutingContext context)
            {
                if (context.Result is FileResult)
                    //jquery.fileDownload uses this cookie to determine that a file download has completed successfully
                    Response.SetCookie(new HttpCookie(FILE_DOWNLOAD_COOKIE_NAME, "true") { Path = "/" });
                else
                    //ensure that the cookie is removed in case someone did a file download without using jquery.fileDownload
                    if (Request.Cookies[FILE_DOWNLOAD_COOKIE_NAME] != null)
                        Response.Cookies[FILE_DOWNLOAD_COOKIE_NAME].Expires = DateTime.Now.AddYears(-1);
            }
        

        然后你就可以调用你的控制器下载并获得“成功”或“失败”回调

        $.fileDownload(mvcUrl('name of the controller'), {
                    httpMethod: 'POST',
                    successCallback: function (url) {
                    //insert success code
        
                    },
                    failCallback: function (html, url) {
                    //insert fail code
                    }
                });
        

        【讨论】:

          【解决方案18】:
          The HTML Code:-
          
          '<button type="button" id="GetFile">Get File!</button>'
          
          
          The jQuery Code:-
          
          '$('#GetFile').on('click', function () {
              $.ajax({
                  url: 'https://s3-us-west-2.amazonaws.com/s.cdpn.io/172905/test.pdf',
                  method: 'GET',
                  xhrFields: {
                      responseType: 'blob'
                  },
                  success: function (data) {
                      var a = document.createElement('a');
                      var url = window.URL.createObjectURL(data);
                      a.href = url;
                      a.download = 'myfile.pdf';
                      document.body.append(a);
                      a.click();
                      a.remove();
                      window.URL.revokeObjectURL(url);
                  }
              });
          });'
          

          【讨论】:

          • 仅代码的答案应至少包含解释代码如何工作以及为何回答问题的说明。
          • 它给了我 -> VM2821:81 Uncaught TypeError:无法在“URL”上执行“createObjectURL”:重载解析失败
          【解决方案19】:

          我发现了一个修复,虽然它实际上并未使用 ajax,但它确实允许您使用 javascript 调用来请求下载,然后在下载实际开始时获得回调。如果链接运行服务器端脚本,在发送文件之前需要一点时间来编写文件,我发现这很有帮助。所以你可以提醒他们它正在处理,然后当它最终发送文件时删除该处理通知。这就是为什么我想尝试通过 ajax 加载文件的原因,这样我就可以在请求文件时发生一个事件,而在它实际开始下载时发生另一个事件。

          首页的js

          function expdone()
          {
              document.getElementById('exportdiv').style.display='none';
          }
          function expgo()
          {
             document.getElementById('exportdiv').style.display='block';
             document.getElementById('exportif').src='test2.php?arguments=data';
          }
          

          iframe

          <div id="exportdiv" style="display:none;">
          <img src="loader.gif"><br><h1>Generating Report</h1>
          <iframe id="exportif" src="" style="width: 1px;height: 1px; border:0px;"></iframe>
          </div>
          

          然后是另一个文件:

          <!DOCTYPE html>
          <html>
          <head>
          <script>
          function expdone()
          {
              window.parent.expdone();
          }
          </script>
          </head>
          <body>
          <iframe id="exportif" src="<?php echo "http://10.192.37.211/npdtracker/exportthismonth.php?arguments=".$_GET["arguments"]; ?>"></iframe>
          <script>document.getElementById('exportif').onload= expdone;</script>
          </body></html>
          

          我认为有一种方法可以使用 js 读取获取数据,因此不需要 php。但我不知道它,我使用的服务器支持 php,所以这对我有用。我想我会分享它以防它帮助任何人。

          【讨论】:

            【解决方案20】:

            如果服务器在响应中写回文件(包括 cookie,如果 您使用它们来确定文件下载是否开始),只需使用值创建一个表单并提交它:

            function ajaxPostDownload(url, data) {
                var $form;
                if (($form = $('#download_form')).length === 0) {
                    $form = $("<form id='download_form'" + " style='display: none; width: 1px; height: 1px; position: absolute; top: -10000px' method='POST' action='" + url + "'></form>");
                    $form.appendTo("body");
                }
                //Clear the form fields
                $form.html("");
                //Create new form fields
                Object.keys(data).forEach(function (key) {
                    $form.append("<input type='hidden' name='" + key + "' value='" + data[key] + "'>");
                });
                //Submit the form post
                $form.submit();
            }
            

            用法:

            ajaxPostDownload('/fileController/ExportFile', {
                DownloadToken: 'newDownloadToken',
                Name: $txtName.val(),
                Type: $txtType.val()
            });
            

            控制器方法:

            [HttpPost]
            public FileResult ExportFile(string DownloadToken, string Name, string Type)
            {
                //Set DownloadToken Cookie.
                Response.SetCookie(new HttpCookie("downloadToken", DownloadToken)
                {
                    Expires = DateTime.UtcNow.AddDays(1),
                    Secure = false
                });
            
                using (var output = new MemoryStream())
                {
                    //get File
                    return File(output.ToArray(), "application/vnd.ms-excel", "NewFile.xls");
                }
            }
            

            【讨论】:

              【解决方案21】:

              如果你想使用 jQuery File Download ,请在 IE 中注意这一点。 您需要重置响应,否则将无法下载

                  //The IE will only work if you reset response
                  getServletResponse().reset();
                  //The jquery.fileDownload needs a cookie be set
                  getServletResponse().setHeader("Set-Cookie", "fileDownload=true; path=/");
                  //Do the reset of your action create InputStream and return
              

              你的action可以实现ServletResponseAware访问getServletResponse()

              【讨论】:

                【解决方案22】:

                通过Ajax调用肯定是做不到的。

                但是,有一个解决方法。

                步骤:

                如果你使用 form.submit() 来下载文件,你可以做的是:

                1. 创建从客户端到服务器的 ajax 调用,并将文件流存储在会话中。
                2. 从服务器返回“成功”后,调用您的 form.submit() 以流式传输存储在会话中的文件流。

                这在您想决定是否需要在制作 form.submit() 后下载文件时很有帮助,例如:可能会出现在 form.submit() 上,服务器发生异常的情况您可能需要在客户端显示一条自定义消息,而不是崩溃,在这种情况下,此实现可能会有所帮助。

                【讨论】:

                  【解决方案23】:

                  还有另一种用 ajax 下载网页的解决方案。但我指的是必须先处理后下载的页面。

                  首先您需要将页面处理与结果下载分开。

                  1) ajax 调用只进行页面计算。

                  $.post("CalculusPage.php", { calculusFunction: true, ID: 29, data1: "a", data2: "b" }, 功能(数据,状态) { 如果(状态==“成功”) { /* 2) 在答案中,下载了使用先前计算的页面。例如,这可以是打印在 ajax 调用中计算的表的结果的页面。 */ window.location.href = 下载页面.php+"?ID="+29; } } ); // 例如:在 CalculusPage.php if ( !empty($_POST["calculusFunction"]) ) { $ID = $_POST["ID"]; $query = "INSERT INTO ExamplePage (data1, data2) VALUES ('".$_POST["data1"]."', '".$_POST["data2"]."') WHERE id = ".$ID; ... } // 例如:在DownloadPage.php $ID = $_GET["ID"]; $sede = "SELECT * FROM ExamplePage WHERE id = ".$ID; ... $filename="Export_Data.xls"; header("Content-Type: application/vnd.ms-excel"); header("Content-Disposition: inline; filename=$filename"); ...

                  我希望这个解决方案对许多人有用,就像对我一样。

                  【讨论】:

                    【解决方案24】:

                    在任何浏览器中都可以正常工作(我使用的是 asp.net core)

                                function onDownload() {
                    
                      const api = '@Url.Action("myaction", "mycontroller")'; 
                      var form = new FormData(document.getElementById('form1'));
                    
                      fetch(api, { body: form, method: "POST"})
                          .then(resp => resp.blob())
                          .then(blob => {
                              const url = window.URL.createObjectURL(blob);
                            $('#linkdownload').attr('download', 'Attachement.zip');
                              $('#linkdownload').attr("href", url);
                              $('#linkdownload')
                                  .fadeIn(3000,
                                      function() { });
                    
                          })
                          .catch(() => alert('An error occurred'));
                    
                    
                    
                    }
                     
                     <button type="button" onclick="onDownload()" class="btn btn-primary btn-sm">Click to Process Files</button>
                     
                     
                     
                     <a role="button" href="#" style="display: none" class="btn btn-sm btn-secondary" id="linkdownload">Click to download Attachments</a>
                     
                     
                     <form asp-controller="mycontroller" asp-action="myaction" id="form1"></form>
                     
                     
                            function onDownload() {
                                const api = '@Url.Action("myaction", "mycontroller")'; 
                                //form1 is your id form, and to get data content of form
                                var form = new FormData(document.getElementById('form1'));
                    
                                fetch(api, { body: form, method: "POST"})
                                    .then(resp => resp.blob())
                                    .then(blob => {
                                        const url = window.URL.createObjectURL(blob);
                                        $('#linkdownload').attr('download', 'Attachments.zip');
                                        $('#linkdownload').attr("href", url);
                                        $('#linkdownload')
                                            .fadeIn(3000,
                                                function() {
                    
                                                });
                                    })
                                    .catch(() => alert('An error occurred'));                 
                    
                            }
                    

                    【讨论】:

                      【解决方案25】:

                      我尝试了 Ajax 和 HttpRequest 方法来获取我的结果下载文件,但我失败了,最后我使用以下步骤解决了我的问题:

                      在我的 html 代码中实现了一个简单的隐藏表单:

                      <form method="post" id="post_form" style="display:none" action="amin.php" >
                          <input type="hidden" name="action" value="export_xlsx" />
                          <input type="hidden" name="post_form_data" value="" />
                      </form>
                      

                      带有“动作”名称的输入用于在我的 php 代码中调用函数, 带有“post_form_data”名称的输入,用于发送无法使用 GET 发送的表的长数据。将此数据编码为 json,并将 json 放入输入中:

                      var list = new Array();
                          $('#table_name tr').each(function() {
                              var row = new Array();
                              $(this).find('td').each(function() {
                                  row.push($(this).text());
                              });
                              list.push(row);
                          });
                      list    = JSON.stringify(list);
                      
                      $("input[name=post_form_data]").val(list);
                      

                      现在,表单已准备好输入我想要的值,只需要触发提交即可。

                      document.getElementById('post_form').submit();
                      

                      完成了! 虽然我的结果是一个文件(对我来说是 xlsx 文件),但页面不会被重定向,并且文件会立即在最后一页开始下载,因此无需使用 iframe 或 window.open 等。

                      如果您尝试做这样的事情,这应该是一个简单的技巧?。

                      【讨论】:

                        【解决方案26】:

                        我为这个问题苦苦挣扎了很长时间。最后,一个优雅的外部库建议 here 帮助了我。

                        【讨论】:

                          猜你喜欢
                          相关资源
                          最近更新 更多