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


当前回答

这是使用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
            }
        });

其他回答

你可以用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的回退

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

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

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

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

“将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以上。您需要根据您的文件类型设置它。您可以使用这种技术下载任何类型的文件。

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

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

以上将下载文件

我的方法完全基于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);

The interesting point is that this solution is only working with a "blob". As you can see in other answers, some are simply using a blob but not explaining why and how to create it. As you can read e.g. in the Mozilla developer documentation you need a file, media ressource or blob for the function "createObjectURL()" to work. The problem is that your http-response might not be any of those. Therefore the first thing you must do is to convert your response to a blob. This is what the first line does. Then you can use the "createObjectURL" with your newly created blob. If you than click the link your browser will open a file-save dialog and you can save your data. Obviously it s possible that you cannot define a fixed filename for your file to download. Then you must make your response more complex like in the answer from Luke.

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