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


当前回答

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>

最好使用<s:a>标记,用OGNL指向用<s: URL >标记创建的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报头写入响应,指定文件需要下载(附件),而不是由浏览器打开(内联)。您还需要指定Content Type,并且您可能希望添加文件名和长度(以帮助浏览器绘制一个真实的进度条)。

例如,当下载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,例如,用于直接流的黑客),您不需要直接向响应写入任何内容;简单地使用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创建的新窗口,一个<iframe>在页面中,或者一个新窗口创建的飞行与"discussed" target="_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>

其他回答

function downloadURI(uri, name) 
{
    var link = document.createElement("a");
    link.download = name;
    link.href = uri;
    link.click();
}

这是使用MVC时的工作代码,你从控制器中获取文件

假设你有你的字节数组声明和填充,你唯一需要做的是使用文件函数(使用System.Web.Mvc)

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

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

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

然后你将能够调用你的控制器来下载并获得"success"或"failure"回调

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

            },
            failCallback: function (html, url) {
            //insert fail code
            }
        });

还有另一种解决方案下载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");

    ...

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

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

没有人贴出这个@Pekka的解决方案…所以我会把它贴出来。它可以帮助别人。

您不需要通过Ajax来实现这一点。只使用

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