2013-06-18 133 views
32

我正在使用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(); 
} 

有沒有人知道如何正確寫控制器的方法,以便下載響應作爲文件在客戶端?

+1

你發送通過Ajax請求?如果是這樣,請通過常規請求來完成。 –

回答

24

您無法通過XHR請求下載文件(這是Angular發出請求的方式)。請參閱Why threre is no way to download file using ajax request?您或者需要通過$window.open轉到網址,或者執行此處顯示的iframe技巧:JavaScript/jQuery to download file via POST with JSON data

+9

這似乎並不正確,因爲您可以使用Blob保存文件,例如,這裏:http://stackoverflow.com/questions/19327749/javascript-blob-filename-without-link –

+0

@ ilya-chernomordik警告:下載屬性不支持在IE11和IE11的支持,只是剛剛出來截至11/2016。 –

+0

您可以接收和發送二進制數據......可以轉換爲文件,對不對? https://developer.mozilla.org/en-US/docs/Web/API/XMLHttpRequest/Sending_and_Receiving_Binary_Data – Nathan

7

我已經與自己搏鬥過,試圖讓它在服務器上工作。不能。相反...

  1. 要澄清@ dnc253的回答,$window.open(URL)是具有角應用在另一個窗口中打開指定的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. 可以,實際上,直接從XHR請求下載的東西,但它需要完全支持HTML5文件API,通常是更多的麻煩比它的價值,除非你需要進行局部變換在將文件提供給用戶之前將文件放在該文件上。 (可惜的是,我沒有時間提供有關這方面的詳細信息,但還有其他關於使用它的SO帖子。)

+0

只是爲了澄清,這意味着對於來自DB的記錄的CSV導出,服務器端代碼將不得不處理POST,將CSV寫入服務器的文件系統並返回靜態鏈接以使用GET下載。那是對的嗎? – nyl66

+0

不是我的專長,但我相信有方法可以通過GET下載,而無需先寫入服務器的本地文件系統。這完全是服務器如何對請求作出反應的問題:它是否委託給某種'download-a-named-local-file'子系統,或者它是否簡單地打包一串比特*就好像它已被讀取從文件,然後將其返回給客戶端?要麼可以工作。只要正確的位以正確的順序到達,客戶端就不會在意。你如何到達目標和能力。 – XML

0

這是爲我工作:

  • 春控制器: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> 
    
+0

它在Chrome中工作,但不在Mozilla和IE中,你知道如何解決它嗎? – AngryJS

2

有可能使用XHR請求下載文件。您可以使用角度$ http加載文件,然後使用HTML5的Blob功能使瀏覽器保存它。有一個圖書館可以幫助您節省:FileSaver.js

+0

請記住,截至2016年8月,Safari功能存在重大問題(https://github.com/eligrey/FileSaver.js/issues/12),因爲Safari不支持命名要下載的文件:/ caniuse .com /#feat = download – ryanm

+0

看來Safari是新的IE兼容性問題:) –

+1

同意! Safari曾經這麼流行......;)我去了丹佛的'IE6葬禮'活動,幾滴淚水,充滿了笑容......也許在我們寄出去的時候還會有一次'野生動物園葬禮'一個更好的地方(垃圾!)哈。 – ryanm

0

// JAVA PART

@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> 
+0

請解釋一下! –

+0

@Szabolcs downloadXLS表單將使剩下的api調用將文件作爲流獲取並在新選項卡中打開,以便通過Content-dispostion內聯參數將其下載,通過CORS將其內容指示爲java腳本 –

0

我已經寫了下面的註釋瞭解代碼示例。有人使用,他們可以跟着它,因爲我相應地命名了這些文件。

  1. 如果服務器發送的響應斑點,那麼我們的客戶應該能夠生產。

  2. 由於我的目的是通過使用這些解決。我可以下載文件,因爲我用type:'application/*'來表示所有文件。

  3. 創建「downloadLink」變量只是在響應中使用的技術,因此,它會填充像點擊鏈接,然後響應來,然後它的href將被觸發。

controller.js 
 
//this function is in controller, which will be trigered on download button hit. \t 
 

 
    $scope.downloadSampleFile = function() { 
 
//create sample hidden link in document, to accept Blob returned in the response from back end 
 
    
 
\t \t var downloadLink = document.createElement("a"); 
 

 
\t \t document.body.appendChild(downloadLink); 
 
\t \t downloadLink.style = "display: none"; 
 

 
//This service is written Below how does it work, by aceepting necessary params 
 
\t \t downloadFile.downloadfile(data).then(function (result) { 
 

 
\t \t \t var fName = result.filename; 
 
\t \t \t var file = new Blob([result.data], {type: 'application/*'}); 
 
\t \t \t 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. 
 
      
 
\t \t \t downloadLink.href = fileURL; 
 
\t \t \t downloadLink.download = fName; 
 
\t \t \t downloadLink.click(); 
 
\t \t }); 
 
\t }; 
 

 

 

 

 
services.js 
 

 
.factory('downloadFile', ["$http", function ($http) { 
 
\t return { 
 
\t \t downloadfile : function() { 
 
\t \t \t return $http.get(//here server endpoint to which you want to hit the request 
 
       , { 
 
\t \t \t \t responseType: 'arraybuffer', 
 
\t \t \t \t params: { 
 
\t \t \t \t \t //Required params 
 
\t \t \t \t }, 
 
\t \t \t }).then(function (response, status, headers, config) { 
 
\t \t \t \t return response; 
 
\t \t \t }); 
 
\t \t }, 
 
\t }; 
 
}])