通过 jQuery.Ajax 下载文件

Download a file by jQuery.Ajax

提问人:hguser 提问时间:12/28/2010 最后编辑:Cœurhguser 更新时间:9/3/2022 访问量:1020829

问:

我在服务器端有一个用于文件下载的 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中,我看到数据是用二进制流检索的。我想知道如何打开用户可以在本地保存文件的文件下载窗口

JavaScript jQuery ajax JSP 下载

评论

1赞 Pekka 12/28/2010
如何使用PHP单击文件名时下载文件
1赞 Pekka 12/28/2010
尽管平台存在差异,但我还是将其标记为重复项,因为据我所知,解决方案是相同的(您不能也不需要通过 Ajax 执行此操作)。
2赞 hguser 12/28/2010
那么,在没有 Ajax 的情况下,只需使用 window.location=“download.action?para1=value1....”?
0赞 greendino 10/24/2023
没有一个答案是好的和容易阅读的,所有这些都需要一个该死的艰难调整。12 年的前端工程,仍然不是这件事的内置功能,这真是太棒了

答:

259赞 bluish 10/5/2011 #1

没有人发布这个@Pekka的解决方案......所以我会发布它。它可以帮助某人。

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

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

评论

4赞 swapnesh 3/1/2013
好东西。。。因为我正在努力处理下载文件提示和使用 jquery ajax..这个解决方案非常适合我.+1
50赞 brichins 4/25/2013
请注意,这要求服务器将 Content-Disposition 标头值设置为“attachment”,否则浏览器将重定向到(并显示)响应内容
26赞 Christopher King 8/15/2014
或者,使用以确保下载不会替换您当前的浏览器内容(无论 Content-Disposition 标头如何)。window.open(<url>, '_blank');
4赞 kofifus 7/1/2015
此解决方案的问题在于,如果操作失败/服务器返回错误,您的页面将被重定向到错误页面。要解决此问题,请使用 iFrame 解决方案
19赞 Atomosk 5/28/2019
这个解决方案的真正问题 - 问题是关于请求的。POST
766赞 John Culviner 4/2/2012 #2

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 文件下载,它允许使用带有 OnSuccess 和 OnFailure 回调的文件下载来提供“类似 Ajax”的体验,以提供更好的用户体验。看看我的博客文章,了解该插件解决的常见问题和一些使用它的方法,以及jQuery文件下载的实际演示。这是来源

这是一个使用带有 promise 的插件源代码的简单用例演示。演示页面还包括许多其他“更好的用户体验”示例。

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

根据您需要支持的浏览器,您可以使用比 jQuery 文件下载使用的 IFRAME 方法更显式控制的 https://github.com/eligrey/FileSaver.js/

评论

74赞 AnthonyVO 8/8/2012
我喜欢你构建的东西,但我怀疑要获得更多的 StackOverFlow 信用,你在这里的回答应该包含更多细节。特别是关于你是如何解决问题的。
15赞 Kevin B 9/24/2013
如果您能确切地提及这个“插件”如何绕过限制,而不是强迫我们去您的博客/插件源查看它,那就太好了。例如,它是否发布到 iframe?它是否需要远程脚本来保存文件并返回其 URL?
5赞 Kevin B 10/29/2013
@asgerhallas 当然,但如果所述链接消失,那将完全没用。
26赞 Kevin B 10/31/2013
我同意,博客是一个更好的地方,可以详细描述如何使用你的插件以及它是如何工作的。但是您至少可以简要概述一下该插件如何解决问题。例如,这通过让服务器设置一个 cookie 并让您的 javascript 不断查找该 cookie 直到它存在来解决问题。一旦它存在,我们可以假设下载已完成。有了这些信息,人们可以很容易地非常快速地推出自己的解决方案,并且答案不再 100% 依赖于您的博客/插件/jquery,并且可以应用于其他库。
2赞 John Culviner 1/14/2014
Royi,据我了解,AJAX 永远不支持导致文件下载弹出窗口保存到磁盘的文件下载。你有没有找到一种我不知道的方法?
27赞 Andrea Ligios 6/3/2013 #3

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 框架:动作下载文件作为附件

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

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

最好将带有 OGNL 的标记指向使用标记创建的 URL<s:a><s: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 标头写入响应,指定文件需要下载()且不被浏览器打开()。您还需要指定内容类型,并且可能需要添加文件名和长度(以帮助浏览器绘制逼真的进度条)。attachmentinline

例如,下载 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),您不需要直接向响应写入任何内容;只需使用 Stream 结果类型并在 struts.xml 中配置它即可: 示例

<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 设置为内联,但目标不能是当前窗口位置;您必须以 JavaScript 创建的新窗口、页面中的窗口或使用 “discussed” target=“_blank” 即时创建的新窗口为目标:<iframe>

<!-- 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">
    download
</a>

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

评论

2赞 Amiga500 5/29/2015
先生,您的输入:“内容处置”、“内联;挽救了可怜的程序员的一天:)
1赞 Andrew Koster 5/20/2019
这是唯一提到“window.open”的答案(其中一条评论提到了它)。
0赞 Muflix 8/7/2019
如果你有很多参数,它不起作用,因为你会得到错误。too long url
24赞 ndpu 1/16/2014 #4

我创建了一些功能作为解决方法解决方案(受@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');
});

评论

0赞 Shayne 1/20/2014
不过,这会以一种非常奇怪的方式将数据发送到服务器。我想知道是否可以更改它以创建合规的 POST?
15赞 Shayne 1/20/2014 #5

好的,基于 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});
});

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

注意:警惕这些形式可能出现的可变注入。可能有一种更安全的方法来对这些变量进行编码。或者考虑逃避它们。

评论

0赞 Marek Bar 10/15/2014
这是一个工作示例。谢谢。是否可以在没有 iframe 但没有 window.location 的情况下做到这一点?
0赞 Shayne 10/17/2014
我想您可以将隐藏的形式附加到 DOM 的底部。同样值得探索的是使用 Shadow dom ,尽管这在较旧的浏览器上不一定得到很好的支持。
0赞 void 1/16/2016
在此代码中,我收到此错误。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.
0赞 bartex9 1/12/2017
如何将此表单映射到某个模型类?我有:但它不起作用..@ResourceMapping() public void downloadFile(final ResourceRequest request, final ResourceResponse response, @ModelAttribute("downForm") FormModel model)
0赞 Shayne 1/13/2017
void :这可能是某种跨域安全问题。这可能是一个完整的堆栈溢出问题。@bartex9 : 这很大程度上取决于你使用什么样的框架。但原则是采用名称和路径并存储它们,同时将文件本身推送到文件系统的 Web 可访问区域,或者类似于 Amazon S3 以实现高可用性
2赞 Yannick Richard 1/31/2014 #6

好的,这是使用 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
            }
        });
47赞 Luke Madhanga 4/29/2015 #7

您可以使用 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 取自此处,必须按照此函数使用

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 回退

评论

1赞 0x777 11/15/2017
这似乎在firefox中不起作用...有什么想法吗?a.click()
0赞 0x777 11/15/2017
在某些浏览器中,您可能需要将 添加到 dom 中才能使此代码正常工作和/或删除该部分:arevokeObjectURLdocument.body.appendChild(a)
0赞 apil.tamang 3/22/2019
挽救了我的一天(也可能是一份工作:))无论如何都不是 javascript 专家......更多 Java 家伙。但是,我不知道为什么简单的“createObjectURL(new Blob([atob(base64)]))”不起作用!它根本没有,而所有的直觉都说它必须。咕噜咕噜......
0赞 Muflix 8/7/2019
在行,它会抛出一个错误。我正在使用 Chrome 版本 75.0.3770.142,但我不知道这里出了什么问题。var bytechars = atob(base64)JavaScript runtime error: InvalidCharacterError
0赞 Alireza Fattahi 5/11/2015 #8

如果您想使用jQuery文件下载,请注意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

您的操作可以实现以访问ServletResponseAwaregetServletResponse()

4赞 dario nascimento 5/15/2015 #9

在上面的答案中添加一些关于下载文件的内容

下面是一些生成字节数组的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);

以上将下载文件

9赞 manukyanv07 6/29/2015 #10

这是我所做的,纯 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");
});

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

enter image description here enter image description here

这是我的服务器端 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();
        }
    }
    }

评论

0赞 kofifus 6/30/2015
似乎没有为内容处置附件内容调用您的加载事件(因为没有加载任何内容到 iframe 中),如果它适合您(您获得控制台 .log)请发布示例
0赞 manukyanv07 7/1/2015
这是一个快速的小提琴 jsfiddle.net/y2xezyoj 一旦 pdf 文件加载到 iframe 中,它就会触发加载事件屁股。这个小提琴不会下载,因为下载的密钥在服务器端 “response.setHeader(”Content-disposition“, ”attachment;文件名=\“” + 文件名 + “.xlsx\”“);”
1赞 kofifus 7/1/2015
是的,在这种情况下它会起作用,但是如果下载了文件,即服务器发送 Content-Disposition: 附件,那么加载事件将不会触发,这是我的观点
0赞 manukyanv07 7/3/2015
您是完全正确的,加载事件在服务器完成处理后立即触发,开始发送文件。这就是我一直在寻找的,1-阻止按钮并显示处理,以便用户可以获得正在发生的事情的反馈。2 - 然后,当服务器完成处理并即将发送文件 3-(触发加载事件)时,我解锁按钮并删除处理微调器 4 - 用户现在弹出保存文件或浏览器开始在定义的下载位置下载它。对不起,我的英语。
5赞 EL missaoui habib 4/13/2016 #11
function downloadURI(uri, name) 
{
    var link = document.createElement("a");
    link.download = name;
    link.href = uri;
    link.click();
}

评论

0赞 Wai Ha Lee 4/13/2016
你能解释一下你的答案吗?这将有助于其他人了解你做了什么,这样他们就可以将你的技术应用到他们的情况中。
2赞 Yangshun Tay 5/16/2016
只是一个警告:Safari 和 IE 不支持该属性,因此您的文件最终将具有名称“未知”download
17赞 Naren Yellavula 8/9/2016 #12

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

"将 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));
});

上面的片段只是在做以下事情

  • 使用 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调整为上面的1。您需要根据您的文件类型进行设置。您可以使用此技术下载任何类型的文件。

评论

0赞 Tsahi Asher 10/4/2018
“我们不能通过Ajax下载文件,必须使用XMLHttpRequest”。根据定义,XMLHttpRequest 是 AJAX。否则,现代网络浏览器的绝佳解决方案。对于不支持的 IE,我正在考虑将其与专有的 msSaveOrOpenBlob 方法结合使用。HTMLAnchorElement.download
43赞 João Marcos 3/16/2017 #13

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

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

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

评论

5赞 fabio.sang 10/3/2017
谢谢,我使用了这个解决方案。像魅力一样工作。此外,如果未从响应中获取 Blob,只需创建一个新的 Blob。
6赞 startsWith_R 6/7/2018
带有 IE 处理链接的更好版本
0赞 alexventuraio 10/23/2019
如果您使用的是 IE11,@startsWith_R 中的链接确实很有帮助
4赞 aarkerio 6/1/2017 #14

在 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

评论

2赞 coderhs 6/4/2018
快速提问,这不会生成两次文件吗?发送 ajax 请求后。然后,您也可以将页面重定向到相同的 URL。我们怎样才能消除它?
0赞 aarkerio 6/4/2018
不是我的情况。不过,我只在 Chrome 上测试过它。
0赞 Sven 6/15/2018
由于 coderhs 已经正确声明,该操作被调用两次。
0赞 CSquared 2/6/2020
我也为我打了两次电话。
1赞 Kit Ramos 1/23/2018 #15

我发现了一个修复程序,虽然它实际上没有使用 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,所以这对我有用。以为我会分享它,以防它对任何人有帮助。

-1赞 Aman Srivastava 5/28/2018 #16

可以肯定的是,您无法通过 Ajax 调用来做到这一点。

但是,有一个解决方法。

步骤:

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

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

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

-1赞 Eerik Sven Puudist 3/30/2019 #17

我在这个问题上挣扎了很长时间。最后,这里建议的一个优雅的外部图书馆帮助了我。

4赞 Andrew Koster 5/20/2019 #18

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

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

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

它将打开一个新选项卡(因为“_blank”窗口名称),该选项卡将打开 URL。

您的服务器端代码也应该有如下内容:

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

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

0赞 netluke 5/21/2019 #19

还有另一种解决方案可以在 Ajax 中下载网页。但我指的是一个必须首先处理然后下载的页面。

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

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

$.post("CalculusPage.php", { calculusFunction: true, ID: 29, data1: "a", data2: "b" },

       function(data, status) 
       {
            if (status == "success") 
            {
                /* 2) In the answer the page that uses the previous calculations is downloaded. For example, this can be a page that prints the results of a table calculated in the ajax call. */
                window.location.href = DownloadPage.php+"?ID="+29;
            }               
       }
);

// For example: in the CalculusPage.php

    if ( !empty($_POST["calculusFunction"]) ) 
    {
        $ID = $_POST["ID"];

        $query = "INSERT INTO ExamplePage (data1, data2) VALUES ('".$_POST["data1"]."', '".$_POST["data2"]."') WHERE id = ".$ID;
        ...
    }

// For example: in the 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");

    ...

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

5赞 Shahrukh Alam 1/3/2020 #20

HTML代码:

<button type="button" id="GetFile">Get File!</button>

jQuery代码:

$('#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);
        }
    });
});

评论

1赞 Roberto Caboni 1/3/2020
仅代码答案应至少有一个最小描述,解释代码的工作原理以及它为什么回答问题。
3赞 fatherazrael 1/17/2021
它给了我 -> VM2821:81 Uncaught TypeError:无法在“URL”上执行“createObjectURL”:重载解析失败
5赞 Jess Chen 1/8/2020 #21

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

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

fetch是一个很好的选择,但它不支持 IE 11。并且不能支持IE 11,你可以参考这个window.URL.createObjectURL

这是我的代码,它类似于Shahrukh Alam的代码。但是您应该注意,这可能会造成内存泄漏。你可以参考这个。当响应到达时,数据将存储到浏览器的内存中。因此,在单击链接之前,文件已下载。这意味着您可以在下载后执行任何操作。window.URL.createObjectURLa

$.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)
}
6赞 Mike S 2/12/2020 #22

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

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

提交 Web 表单时的示例:

<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').

评论

0赞 jstuardo 5/18/2020
要从 Content-Disposition 获取文件名,此匹配对我有用:(不带双引号或问号) - regex101.com/r/2AsD4y/2。但是,您的解决方案是在搜索大量后唯一有效的解决方案。filename.match(/filename=(.*)/)[1]
0赞 Martin Kovachev 7/7/2020
这是获取文件名:)的另一种更懒惰的方法npmjs.com/package/content-disposition
0赞 Marinpietri #23

也就是说,它在任何浏览器中都能正常工作(我使用的是 asp.net 核心)

            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'));                 

        }
1赞 Pierre 8/27/2020 #24

如果服务器在响应中写回文件(包括 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");
    }
}
13赞 M46 3/12/2021 #25

我的方法完全基于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开发人员文档中,您需要一个文件,媒体资源或blob才能使函数“createObjectURL()”正常工作。问题是你的 http-response 可能不是其中任何一个。 因此,您必须做的第一件事是将响应转换为 Blob。这是第一行的作用。然后,可以将“createObjectURL”与新创建的 blob 一起使用。 如果您单击该链接,您的浏览器将打开一个文件保存对话框,您可以保存您的数据。显然,您可能无法为要下载的文件定义固定文件名。然后你必须让你的回答更复杂,就像路加的回答一样。

不要忘记释放内存,尤其是在处理大文件时。有关更多示例和信息,可以查看 JS blob 对象的详细信息

评论

0赞 scotts 9/25/2021
谢谢!但它应该是:revokeObjectURL() 窗口。URL.revokeObjectURL(a.href);
0赞 Zoey 10/22/2021
我返回了一个 zip 文件,但是当我使用此方法时,我的 zip 文件无效。我必须从 ajax 调用中删除 dataType 才能使其工作。对于 blob 创建,我使用了您上面使用的选项 application/zip,并尝试将其完全删除。
0赞 M46 10/23/2021
@scotts 谢谢。我更改了我的代码。
1赞 Saghachi 1/26/2022 #26

我尝试了 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>

带有“action”名称的输入用于在我的 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 等。

如果你想做这样的事情,这应该是一个简单的技巧😉。