【问题标题】:Spring - download response as a fileSpring - 将响应下载为文件
【发布时间】:2013-06-15 03:57:33
【问题描述】:

我正在使用 AngularJS 和 Spring 编写应用程序。我想向服务器发送请求并将控制器返回的响应作为文件下载。在控制器中,我有 csv 文件的内容(作为字符串),即1;2;3;4(1 行,4 列)。将此响应下载为文件的最简单方法是什么?

下面,我发布了我的简化代码。 在 Spring 控制器中:

@RequestMapping(value = "/csv", method = GET)
@ResponseBody
public String getCsvFile() {
    return getCsvContent();
}

在 javascript (AngularJS) 中

return $http({method: 'GET', url: 'csv/'});

我也尝试写入响应流(如下),设置标头,但在客户端我总是将此内容作为字符串获取 - 而不是作为要下载的文件。

@RequestMapping(value = "/csv", method = GET)
@ResponseBody
public void getCsvFile(HttpServletResponse response) {
    response.setContentType("application/csv");
    response.setHeader("Content-Disposition", "attachment; filename=file.csv");
    response.setContentLength(getCsvContent().getBytes().length);
    ServletOutputStream out = response.getOutputStream();
    out.write(getCsvContent());
    out.flush();
    out.close();
}

有谁知道如何正确编写控制器的方法以便在客户端将响应下载为文件?

【问题讨论】:

  • 您是通过 Ajax 发送请求吗?如果是这样,请通过常规请求进行。

标签: spring angularjs


【解决方案1】:

您不能通过 XHR 请求下载文件(这是 Angular 发出请求的方式)。请参阅Why threre is no way to download file using ajax request? 您要么需要通过$window.open 访问 URL,要么执行此处显示的 iframe 技巧:JavaScript/jQuery to download file via POST with JSON data

【讨论】:

【解决方案2】:

我自己也遇到过这个问题,试图让它在服务器上运行。不能。而是……

  1. 为了澄清@dnc253 的回答,$window.open(URL) 是一种让 Angular 应用程序在另一个窗口中打开给定 URL 的方法。 (它实际上只是通用window.open() 的可测试角度代理。)这是一个很好的解决方案,可以保留您的历史记录,并下载文件并可能在新的浏览器窗口中呈现它(如果支持)。但它经常遇到弹出窗口阻止程序,这对可靠性来说是一个巨大的问题。用户通常根本不了解他们发生了什么。因此,如果您不介意立即使用 当前 窗口下载文件,您可以简单地使用同样有效的通用 javascript 方法:location.href = "uriString",这对我来说就像一个魅力。 Angular 甚至没有意识到发生了什么。一旦我的 POST/PUT 操作完成,我就会在 Promise 处理程序中调用它。如果需要,让 POST/PUT 返回要调用的 URL(如果您还不能推断它)。您将获得与用户响应 PUT/POST 下载时相同的行为。例如:

    $http.post(url, payload).then(function(returnData){
        var uriString = parseReturn(returnData);
        location.href="uriString"
    })
    
  2. 1234563使其可供用户使用。 (遗憾的是,我没有时间提供这方面的详细信息,但还有其他关于使用它的 SO 帖子。)

【讨论】:

  • 澄清一下,这意味着对于从数据库中导出记录的 CSV,服务器端代码必须处理 POST,将 CSV 写入服务器的文件系统并返回静态链接使用 GET 下载它。对吗?
  • 不是我的专长,但我相信有一些方法可以通过 GET 进行下载,而无需先写入服务器的本地文件系统。这完全是服务器如何对请求做出反应的问题:它是委托给某种“下载命名本地文件”子系统,还是只是简单地打包一个比特流好像 它已经从文件中读取,然后返回给客户端?任何一个都可以工作。只要正确的位以正确的顺序到达,客户端就不会关心。如何达到目标只取决于目标和能力。
【解决方案3】:

以防万一你们需要, 这里有几个链接可以帮助你:

  1. download csv file from web api in angular js
  2. Export javascript data to CSV file without server interaction

干杯

【讨论】:

    【解决方案4】:

    它对我有用:

    • 弹簧控制器:DownloadController.java

      package com.mycompany.myapp.controller;
      
      import java.io.File;
      import java.io.FileInputStream;
      import java.io.IOException;
      import java.io.InputStream;
      import java.io.OutputStream;
      
      import javax.servlet.http.HttpServletRequest;
      import javax.servlet.http.HttpServletResponse;
      
      import org.apache.commons.io.IOUtils;
      import org.slf4j.Logger;
      import org.slf4j.LoggerFactory;
      import org.springframework.http.HttpStatus;
      import org.springframework.http.ResponseEntity;
      import org.springframework.web.bind.annotation.ExceptionHandler;
      import org.springframework.web.bind.annotation.RequestMapping;
      import org.springframework.web.bind.annotation.RequestMethod;
      import org.springframework.web.bind.annotation.RequestParam;
      import org.springframework.web.bind.annotation.RestController;
      
      import com.mycompany.myapp.exception.TechnicalException;
      
      
      @RestController
      public class DownloadController {
      
          private final Logger log = LoggerFactory.getLogger(DownloadController.class);
      
          @RequestMapping(value = "/download", method = RequestMethod.GET)
          public void download(@RequestParam ("name") String name, final HttpServletRequest request, final HttpServletResponse response) throws TechnicalException {
              log.trace("name : {}", name);
      
              File file = new File ("src/main/resources/" + name);
              log.trace("Write response...");
              try (InputStream fileInputStream = new FileInputStream(file);
                      OutputStream output = response.getOutputStream();) {
      
                  response.reset();
      
                  response.setContentType("application/octet-stream");
                  response.setContentLength((int) (file.length()));
      
                  response.setHeader("Content-Disposition", "attachment; filename=\"" + file.getName() + "\"");
      
                  IOUtils.copyLarge(fileInputStream, output);
                  output.flush();
              } catch (IOException e) {
                  log.error(e.getMessage(), e);
              }
      
          }
      
      }
      
    • AngularJs 服务:download.service.js

      (function() {
          'use strict';
      
          var downloadModule = angular.module('components.donwload', []);
      
          downloadModule.factory('downloadService', ['$q', '$timeout', '$window',
              function($q, $timeout, $window) {
                  return {
                      download: function(name) {
      
                          var defer = $q.defer();
      
                          $timeout(function() {
                                  $window.location = 'download?name=' + name;
      
                              }, 1000)
                              .then(function() {
                                  defer.resolve('success');
                              }, function() {
                                  defer.reject('error');
                              });
                          return defer.promise;
                      }
                  };
              }
          ]);
      })();
      
    • AngularJs 配置:app.js

      (function() {
          'use strict';
      
          var myApp = angular.module('myApp', ['components.donwload']);
         /* myApp.config([function () {
      
          }]);
          myApp.run([function () {
      
          }]);*/
      
      
      })();
      
    • AngularJs 控制器:download.controller.js

      (function() {
          'use strict';
      
          angular.module('myApp')
              .controller('DownloadSampleCtrl', ['downloadService', function(downloadService) {
                  this.download = function(fileName) {
                      downloadService.download(fileName)
                          .then(function(success) {
                              console.log('success : ' + success);
                          }, function(error) {
                              console.log('error : ' + error);
                          });
                  };
              }]);
      })();
      
    • index.html

      <!DOCTYPE html>
      <html ng-app="myApp">
      
      <head>
          <title>My App</title>
          <link rel="stylesheet" href="bower_components/normalize.css/normalize.css" />
          <link rel="stylesheet" href="assets/styles/main.css" />
          <link rel="icon" href="favicon.ico">
      </head>
      
      <body>
          <div ng-controller="DownloadSampleCtrl as ctrl">
              <button ng-click="ctrl.download('fileName.txt')">Download</button>
          </div>
      
          <script src="bower_components/angular/angular.min.js"></script>
      
          <!-- App config -->
          <script src="scripts/app/app.js"></script>
          <!-- Download Feature -->
          <script src="scripts/app/download/download.controller.js"></script>
          <!-- Components -->
          <script src="scripts/components/download/download.service.js"></script>
      </body>
      
      </html>
      

    【讨论】:

    • chrome 下可以,Mozilla 和 IE 不行,你知道怎么解决吗?
    【解决方案5】:

    可以使用 XHR 请求下载文件。您可以使用 angular $http 加载文件,然后使用 HTML5 的 Blob 功能让浏览器保存它。有一个库可以帮你保存:FileSaver.js.

    【讨论】:

    • 请记住,截至 2016 年 8 月,存在重大的 Safari 功能问题 (github.com/eligrey/FileSaver.js/issues/12),因为 Safari 不支持命名要下载的文件:/caniuse.com/#feat=download
    • 看来Safari是兼容性问题的新IE :)
    • 同意! Safari 曾经很时髦.. ;) 我参加了在丹佛举行的“IE6 葬礼”活动,流了几滴眼泪,满脸笑容。也许有一天我们也应该举办“Safari 葬礼”一个更好的地方(垃圾!)哈。
    【解决方案6】:

    //JAVA部分

    @RequestMapping(value = "/report-excel", method = RequestMethod.GET)
        public ResponseEntity<byte[]> getReportExcel(@RequestParam("bookingStatusType") String bookingStatusType,
                @RequestParam("endDate") String endDate, @RequestParam("product") String product, @RequestParam("startDate") String startDate)throws IOException, ParseException {
    
    //Generate Excel from DTO using any logic after that do the following
    byte[] body = wb.getBytes();
    HttpHeaders header = new HttpHeaders();
            header.setContentType(new MediaType("application", "xlsx"));
            header.set("Content-Disposition", "inline; filename=" + fileName);
            header.setCacheControl("must-revalidate, post-check=0, pre-check=0");
            header.setContentLength(body.length);
    
     return new ResponseEntity<byte[]>(body, header, HttpStatus.OK);
    }
    
    
    
    //HTML PART
    <html>
    <head>
    <title>Test</title>
    <meta http-equiv="content-type" content="application/x-www-form-urlencoded; charset=UTF-8">
    </head>
    <body>
      <form name="downloadXLS" method="get" action="http://localhost:8080/rest/report-excel" enctype="multipart/form-data">
        <input type="text" name="bookingStatusType" value="SALES"></input>
        <input type="text" name="endDate" value="abcd"></input>
        <input type="text" name="product" value="FLIGHT"></input>
        <input type="text" name="startDate" value="abcd"></input>
        <input onclick="document.downloadXLS.submit()" value="Submit"></input>
      </form>
    </body>
    </html>
    

    【讨论】:

    • @Szabolcs downloadXLS 表单将调用 rest api 以将文件作为流获取并在新选项卡中打开以通过 Content-dispostion inline 的参数下载它,通过 CORS 指示到 java 脚本跨度>
    【解决方案7】:

    我在下面写了 cmets 来理解代码示例。有些人如果使用,他们可以关注它,因为我相应地命名了文件。

    1. 如果服务器在响应中发送 blob,那么我们的客户端应该能够生成它。

    2. 因为我的目的是通过使用这些来解决的。我可以下载文件,因为我对所有文件都使用了 type: 'application/*' 。

    3. 创建的“downloadLink”变量只是用于响应的技术,它会像点击链接一样填充,然后响应来,然后触发它的href。

    controller.js
    //this function is in controller, which will be trigered on download button hit.	
    
      $scope.downloadSampleFile = function() {
    //create sample hidden link in document, to accept Blob returned in the response from back end
        
    		var downloadLink = document.createElement("a");
    
    		document.body.appendChild(downloadLink);
    		downloadLink.style = "display: none";
    
    //This service is written Below how does it work, by aceepting necessary params
    		downloadFile.downloadfile(data).then(function (result) {
    
    			var fName = result.filename;
    			var file = new Blob([result.data], {type: 'application/*'});
    			var fileURL = (window.URL || window.webkitURL).createObjectURL(file);
    
              
    //Blob, client side object created to with holding browser specific download popup, on the URL created with the help of window obj.
              
    			downloadLink.href = fileURL;
    			downloadLink.download = fName;
    			downloadLink.click();
    		});
    	};
    
    
    
    
    services.js
    
    .factory('downloadFile', ["$http", function ($http) {
    	return {
    		downloadfile : function () {
    			return $http.get(//here server endpoint to which you want to hit the request
                  , {
    				responseType: 'arraybuffer',
    				params: {
    					//Required params
    				},
    			}).then(function (response, status, headers, config) {
    				return response;
    			});
    		},
    	};
    }])

    【讨论】:

      猜你喜欢
      • 2019-02-05
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2016-11-16
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多