通过Spring MVC使用ajax() POST请求下载文件

11

我尝试下载一个文件。这个操作由ajax() POST请求触发。该请求以JSON格式发送数据到控制器。控制器生成文件(字节)并将其发送回来。

JavaScript:

function getLicenseFile() {
    $.ajax({
        type: 'POST',
        url: '<%=request.getContextPath()%>/licenses/rest/downloadLicenseFile',
        dataType: 'json',
        contentType: 'application/json;charset=UTF-8',
        data: ko.mapping.toJSON(licenseModel),
        success: function (data) {
            console.log("in sucess")
        },
        error:function (xhr, ajaxOptions, thrownError){
            console.log("in error")
        } 
    });
}  

控制器:

@RequestMapping(value = "/licenses/rest/downloadLicenseFile", method = RequestMethod.POST)
@ResponseStatus(value=HttpStatus.OK)
@ResponseBody
public void createLicenseFile(@Valid @RequestBody License license, HttpServletResponse response) throws Exception {

    logger.debug("Contoller License in: "+ license);

    byte[] licensedata = licenseEncodeDefaultService.createLicenseFile(license);
    logger.debug("licenseData: " + new String(licensedata));

    response.setHeader("Content-Disposition", "attachment; filename=\"" + license.getCustomer() + ".license\"");
    response.getOutputStream().write(licensedata);
    response.flushBuffer();
}

问题

  • 浏览器应该打开下载框,但没有发生
  • 响应在ajax函数的错误部分处理(但HTTP状态为OK

那我做错了什么或者正确的做法是什么?


1
你设置了 dataType: 'json',但是发送了某种许可文件?此外,我认为你不能使用ajax来下载文件。 - Musa
1
尝试使用Ajax将文件下载并保存到本地目录是不可行的,而且需要比直接指向文件URL本身更多的代码。 - charlietfl
2
找到了一个可行的解决方案: 什么是正确的沟通方式?我应该回答自己的问题吗? - derlinuxer
可以回答自己的问题,这是可以接受的。 - nickdos
1
@derlinuxer,你能分享一下你找到的解决方案吗?我也遇到了完全相同的问题... - will824
显示剩余3条评论
5个回答

17

只需在响应中发送文件的URL,然后在success回调函数中“访问”它即可。

function getLicenseFile() {
    $.ajax({
        type: 'POST',
        url: '<%=request.getContextPath()%>/licenses/rest/downloadLicenseFile',
        dataType: 'json',
        contentType: 'application/json;charset=UTF-8',
        data: ko.mapping.toJSON(licenseModel),
        success: function (data) {
            window.open(data.fileUrl);
            // or window.location.href = data.fileUrl;
        },
        error:function (xhr, ajaxOptions, thrownError) {
            console.log("in error");
        } 
    });
}

data.fileUrl应该在响应中由服务器设置,告诉客户端从哪里获取文件。

因此,您的服务器将发送带有JSON的响应,如下所示:

{
    "fileUrl": "http://mysite.com/files/0123456789"
}

嗯,通常我不想在服务器端保存文件系统中的文件,并且我希望有一个干净的下载URL。只需在内存中生成并返回字节即可。但也许我真的需要两个控制器方法来完成这个任务。第一个方法生成文件(保存到服务器FS)并以JSON格式返回文件名。第二个方法发送文件数据。这样我就可以避免出现干净的下载路径。我会检查一下。 - derlinuxer
window.open(data.fileUrl); 在异步环境下无法工作。window.location.href = data.fileUrl; 完美运行! - lekant

8

@will824 正如你所要求的,我将发布我自己的解决方案。

我在控制器中使用了一个变通方法,并且将文件暂时保存在文件系统中(/tmp)。 我将函数分为2个步骤。 创建和下载。 这不是很好,但对我来说已经足够了。

控制器(创建一个文件,在服务器文件系统中保存):

@RequestMapping(value = "/licenses/rest", method = RequestMethod.PUT)
@ResponseStatus(value=HttpStatus.OK)
@ResponseBody
public String createLicenseFile(@Valid @RequestBody License license) throws Exception {

    // create encrypted license file and send the name back to view
    String fileName =  licenseEncodeDefaultService.createLicenseFile(license);
    return fileName;

}

控制器(下载文件):

@RequestMapping(value = "/licenses/downloadFile/{file}", method = RequestMethod.GET)
public void downloadLicenseFile(@PathVariable("file") String file, HttpServletResponse response) throws Exception {

    // create full filename and get input stream
    File licenseFile = new File ("/tmp/" + file);
    InputStream is = new FileInputStream(licenseFile);

    // set file as attached data and copy file data to response output stream
    response.setHeader("Content-Disposition", "attachment; filename=\"" + file + ".license\"");
    FileCopyUtils.copy(is, response.getOutputStream());

    // delete file on server file system
    licenseFile.delete();

    // close stream and return to view
    response.flushBuffer();
}

JavaScript:

function getLicenseFile() {
    //console.log(ko.mapping.toJSON(licenseModel));
    $.ajax({
        type : 'PUT',
        url : '${pageContext.request.contextPath}/licenses/rest',
        dataType : 'text',
        contentType : 'application/json;charset=UTF-8',
        data : ko.mapping.toJSON(licenseModel),
        success : function(data) {
            window.location.href = '${pageContext.request.contextPath}/licenses/downloadFile/'
                    + data;
        },
        error : function(xhr, ajaxOptions, thrownError) {
            // error handling
        }
    });
}

非常感谢您的回复。我同意这使得一个简单的1步过程需要去2次服务器,但是您的解决方案与我计划实施的方案更或多或少相似,尽管如此,我不被允许去2次服务器,所以只能使用一个带有iframe的不可见表单进行1步调用,并接受如果出现错误用户将永远不知道发生了什么的风险。 - will824

4
如果你想在不改变URL的情况下下载文件,你可以通过编程方式调用 form.submit(),而不是使用AJAX。 JavaScript:
function downloadFileUsingForm(url) { 
    var form = document.createElement("form");
    form.method = "post";
    form.action = url;
    document.body.appendChild(form);
    form.submit();
    document.body.removeChild(form);
}
downloadFileUsingForm("/YourController/DownloadFile");

控制器:

[HttpPost]
public ActionResult DownloadFile()
{
    string content = "Some Values";
    byte[] bytes = System.Text.UTF8Encoding.UTF8.GetBytes(content);
    return File(bytes, System.Net.Mime.MediaTypeNames.Application.Octet, "file.txt");
}

这是我找到的最好的答案,适用于我正在处理的情况。 - Poly
1
作为一个旁注:问题是关于Spring MVC(因此是Java),但是你的控制器示例是使用C#和.NET。 - informatik01
提交表单不会发送 JSON,而问题要求的是 JSON,因此这个答案是不正确的。 - Plamen

2

正如评论所说,你无法使用ajax调用实现此功能,但是可以使用纯Javascript实现。

function getLicenseFile() {
    var downloadUrl = "${pageContext.request.contextPath}/licenses/rest/downloadLicenseFile";
    // (optionally) provide the user with a message that the download is starting
    window.location.href = downloadUrl;
}

请注意使用${pageContext.request.contextPath},而不是<%=request.getContextPath()%>。这是首选的方式。

谢谢,但文件尚未在服务器端。它将通过POST请求使用给定的JSON数据动态创建。没有JSON数据就无法下载文件。 - derlinuxer
是的,我错过了那个。在这种情况下,我会使用标准表单POST。不需要JavaScript。 - nickdos

0

Ajax不会帮助你,尝试使用隐藏表单方法。

<form action='../servletname' method='POST' id='formid'>
                <input type='hidden' value='' name='name' id='id'/>
                <input type='hidden' value=' ' name='name'  id='id' />
            </form>

在下载按钮上点击时,通过表单字段传递您的JSON,然后提交表单

$('#formid').submit();

然后在服务器端

response.setContentType("application/vnd.ms-excel");
            response.setHeader("Content-Disposition", "attachment; filename=filnemae.fileformat");

 ServletOutputStream out = res.getOutputStream();

在输出流上写入后关闭或刷新

如果您正在通过POST发送大量数据,请在server.xml中更新postsize


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接