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


当前回答

在上面添加一些东西来下载文件

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

现在在javascript代码中使用filesver .js,可以下载带有下面代码的文件

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

以上将下载文件

其他回答

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>

我创建了一个小函数作为解决方案(灵感来自@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');
});

如果服务器在响应中写入文件(包括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");
    }
}

可以肯定的是,您不能通过Ajax调用来实现这一点。

然而,有一个变通办法。

步骤:

如果你使用form.submit()下载文件,你可以这样做:

创建一个从客户端到服务器的ajax调用,并将文件流存储在会话中。 当从服务器返回“成功”时,调用form.submit()来处理存储在会话中的文件流。

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

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