Yaylı kumandalardan dosya indirme


387

Web sitesinden PDF indirmem gereken bir gereksinim var. PDF, freemarker ve iText gibi bir PDF oluşturma çerçevesinin bir kombinasyonu olacağını düşündüğüm kod içinde oluşturulmalıdır. Daha iyi bir yol var mı?

Ancak benim asıl sorun, kullanıcının bir Spring Controller aracılığıyla bir dosyayı indirmesine nasıl izin verebilirim?


2
Spring Framework'ün 2011'den beri çok değiştiğini belirtmek gerekir, bu yüzden bunu reaktif bir şekilde de yapabilirsiniz - işte bir örnek
Krzysztof Skrzynecki

Yanıtlar:


397
@RequestMapping(value = "/files/{file_name}", method = RequestMethod.GET)
public void getFile(
    @PathVariable("file_name") String fileName, 
    HttpServletResponse response) {
    try {
      // get your file as InputStream
      InputStream is = ...;
      // copy it to response's OutputStream
      org.apache.commons.io.IOUtils.copy(is, response.getOutputStream());
      response.flushBuffer();
    } catch (IOException ex) {
      log.info("Error writing file to output stream. Filename was '{}'", fileName, ex);
      throw new RuntimeException("IOError writing file to output stream");
    }

}

Genel olarak konuşursanız, response.getOutputStream()orada istediğiniz her şeyi yazabilirsiniz. Bu çıkış akışını oluşturulan PDF'yi oluşturucunuza koyabileceğiniz bir yer olarak iletebilirsiniz. Ayrıca, hangi dosya türünü gönderdiğinizi biliyorsanız,

response.setContentType("application/pdf");

4
Bu hemen hemen söylemek üzereydim, ancak muhtemelen yanıt türü başlığını dosya için uygun bir şeye ayarlamanız gerekir.
GaryF

2
Evet, az önce yazıyı düzenledi. Oluşturulan çeşitli dosya türleri vardı, bu yüzden uzantısı ile dosyanın içerik türünü belirlemek için tarayıcıya bıraktı.
Infeligo

FlushBuffer'ı unuttunuz, mesajınız sayesinde, benimkinin neden çalışmadığını gördüm :-)
Jan Vladimir Mostert

35
IOUtilsSpring yerine Apache kullanmak için özel bir sebep var FileCopyUtilsmı?
Powerlord


290

Bu ResourceHttpMessageConverter ile ilkbaharda yerleşik desteği kullanarak bu satır akışı başardı. Bu, mime türünü belirleyebilirse içerik uzunluğunu ve içerik türünü ayarlar

@RequestMapping(value = "/files/{file_name}", method = RequestMethod.GET)
@ResponseBody
public FileSystemResource getFile(@PathVariable("file_name") String fileName) {
    return new FileSystemResource(myService.getFileFor(fileName)); 
}

10
Bu çalışıyor. Ancak dosya (.csv dosyası) tarayıcıda görüntülenir ve indirilmez - tarayıcıyı indirmeye nasıl zorlayabilirim?
chzbrgla

41
İndirmeye zorlamak için @RequestMapping'e produc = = MediaType.APPLICATION_OCTET_STREAM_VALUE ekleyebilirsiniz
David Kago

8
Ayrıca ekleyebilir gerekir <fasulye class = "org.springframework.http.converter.ResourceHttpMessageConverter" /> (: <message-dönüştürücüler mvc> <açıklama güdümlü mvc>) messageConverters listesine
Sllouyssgort

4
Content-DispositionÜstbilgiyi bu şekilde ayarlamanın bir yolu var mı ?
Ralph

8
Buna ihtiyacım yoktu, ancak yöntem için bir parametre olarak HttpResponse'yi ve sonra "response.setHeader (" Content-Disposition "," attachment; dosyaadı = somefile.pdf ");"
Scott Carlson

82

Dosyayı doğrudan yanıt üzerine yazabilmeniz gerekir. Gibi bir şey

response.setContentType("application/pdf");      
response.setHeader("Content-Disposition", "attachment; filename=\"somefile.pdf\""); 

ve sonra dosyayı ikili akış olarak yazın response.getOutputStream(). response.flush()Sonunda yapmayı unutmayın ve bunu yapmalısınız.


8
içerik türünü bu şekilde ayarlamanın 'Bahar' yolu değil mi? @RequestMapping(value = "/foo/bar", produces = "application/pdf")
Siyah

4
@Francis uygulamanız farklı dosya türleri indirirse ne olur? Lobster1234'ün yanıtı, içerik düzenini dinamik olarak ayarlamanızı sağlar.
Gül

2
@Rose, bu doğru, ancak format başına farklı bitiş noktaları tanımlamanın daha iyi bir uygulama olacağına inanıyorum
Black

3
Sanırım değil, çünkü ölçeklenebilir değil. Şu anda bir düzine tür kaynağı destekliyoruz. Kullanıcıların ne yüklemek istediklerine bağlı olarak daha fazla dosya türünü destekleyebiliriz, bu durumda temelde aynı şeyi yapan çok fazla uç noktaya ulaşabiliriz. IMHO yalnızca bir indirme bitiş noktası olmalıdır ve çok sayıda dosya türünü işler. @Francis
Rose

3
kesinlikle "ölçeklenebilir", ancak bunun en iyi uygulama olup olmadığına karar vermeyi kabul edebiliriz
Black

74

Spring 3.0 ile HttpEntitydönüş nesnesini kullanabilirsiniz . Bunu kullanırsanız, denetleyicinizin bir HttpServletResponsenesneye ihtiyacı yoktur ve bu nedenle test etmek daha kolaydır. Bunun dışında, bu cevap Infeligo'nun cevabına görecelidir .

Pdf çerçevenizin dönüş değeri bir bayt dizisiyse (diğer dönüş değerleri için cevabımın ikinci bölümünü okuyun) :

@RequestMapping(value = "/files/{fileName}", method = RequestMethod.GET)
public HttpEntity<byte[]> createPdf(
                 @PathVariable("fileName") String fileName) throws IOException {

    byte[] documentBody = this.pdfFramework.createPdf(filename);

    HttpHeaders header = new HttpHeaders();
    header.setContentType(MediaType.APPLICATION_PDF);
    header.set(HttpHeaders.CONTENT_DISPOSITION,
                   "attachment; filename=" + fileName.replace(" ", "_"));
    header.setContentLength(documentBody.length);

    return new HttpEntity<byte[]>(documentBody, header);
}

PDF'nizdeki Çerçevesi (dönüş tipi ise documentBbody) zaten bir bayt dizisi değildir (hayır da ve ByteArrayInputStream) o zaman akıllıca olur DEĞİL ilk önce bir bayt dizisi yapmak. Bunun yerine kullanmak daha iyidir:

ile örnek FileSystemResource:

@RequestMapping(value = "/files/{fileName}", method = RequestMethod.GET)
public HttpEntity<byte[]> createPdf(
                 @PathVariable("fileName") String fileName) throws IOException {

    File document = this.pdfFramework.createPdf(filename);

    HttpHeaders header = new HttpHeaders();
    header.setContentType(MediaType.APPLICATION_PDF);
    header.set(HttpHeaders.CONTENT_DISPOSITION,
                   "attachment; filename=" + fileName.replace(" ", "_"));
    header.setContentLength(document.length());

    return new HttpEntity<byte[]>(new FileSystemResource(document),
                                  header);
}

11
-1 bu gereksiz bir şekilde tüm dosyayı belleğe yükler OutOfMemoryErrors kolayca casue olabilir.
Faisal Feroz

1
@FaisalFeroz: evet bu doğru, ancak dosya belgesi yine de bellekte oluşturulmuş (şu soruya bakın: "PDF kod içinde oluşturulması gerekiyor"). Her neyse - bu sorunun üstesinden gelen çözümünüz nedir?
Ralph

1
Yanıt http durum kodunu belirtmenize izin veren bir HttpEntity süper özelliği olan ResponseEntity'yi de kullanabilirsiniz. Örnek:return new ResponseEntity<byte[]>(documentBody, headers, HttpStatus.CREATED)
Amr Mostafa

@Amr Mostafa: Öte yandan (ama anlıyorum) ResponseEntitybir alt sınıftır HttpEntity201 OLUŞTURULMAK verilere sadece bir görünüm döndürdüğümde kullanacağım şey değildir. ( 201 OLUŞTURULMAK için w3.org/Protocols/rfc2616/rfc2616-sec10.html adresine bakın )
Ralph

1
Beyaz boşlukları dosya adında alt çizgi ile değiştirmenizin bir nedeni var mı? Gerçek adı göndermek için tırnak işaretleri içine alabilirsiniz.
Alexandru Severin

63

Eğer sen:

  • byte[]Yanıtı göndermeden önce dosyanın tamamını bir dosyaya yüklemek istemiyorum ;
  • Bunu göndermek / indirmek istiyorum / gerekir InputStream;
  • Mime Tipi ve gönderilen dosya adı üzerinde tam kontrole sahip olmak ister;
  • Sizin için başka @ControllerAdviceistisnalar getirin (veya etmeyin).

Aşağıdaki kod ihtiyacınız olan şey:

@RequestMapping(value = "/stuff/{stuffId}", method = RequestMethod.GET)
public ResponseEntity<FileSystemResource> downloadStuff(@PathVariable int stuffId)
                                                                      throws IOException {
    String fullPath = stuffService.figureOutFileNameFor(stuffId);
    File file = new File(fullPath);
    long fileLength = file.length(); // this is ok, but see note below

    HttpHeaders respHeaders = new HttpHeaders();
    respHeaders.setContentType("application/pdf");
    respHeaders.setContentLength(fileLength);
    respHeaders.setContentDispositionFormData("attachment", "fileNameIwant.pdf");

    return new ResponseEntity<FileSystemResource>(
        new FileSystemResource(file), respHeaders, HttpStatus.OK
    );
}

Hakkında dosya uzunluğu parçası: File#length()Genel durumda yeterince iyi olmalı, ama çünkü bu gözlem yapmak düşündüm yavaş olabilir gelmez bu durumda daha önce depolanmış olması gerekir, (örneğin DB). Yavaş olabileceği durumlar şunlardır: dosya büyükse, özellikle dosya uzak bir sistemdeyse veya bunun gibi daha ayrıntılı bir şeyse - bir veritabanı.



InputStreamResource

Kaynağınız bir dosya değilse, örneğin verileri DB'den alırsanız, kullanmalısınız InputStreamResource. Misal:

    InputStreamResource isr = new InputStreamResource(new FileInputStream(file));
    return new ResponseEntity<InputStreamResource>(isr, respHeaders, HttpStatus.OK);

FileSystemResource sınıfının kullanılmasını önermiyor musunuz?
Stephane

Aslında, orada kullanmanın uygun olduğuna inanıyorum FileSystemResource. Kaynağınızın bir dosya olması bile önerilir . Bu örnekte, FileSystemResourceolduğu yerde InputStreamResourcekullanılabilir.
acdcjunior

Dosya uzunluğu hesaplama kısmı hakkında: Endişeleniyorsanız, olma. File#length()genel durumda yeterince iyi olmalıdır. Çünkü ben sadece söz yavaş olabilir gelmez dosya daha böyle özenli bir uzak sistem falan olduğunu, eğer özel - belki bir veritabanı ?. Ama sadece bir sorun haline gelirse (ya da zor kanıtlarınız varsa, daha önce değil) endişelenir. Ana nokta şudur: dosyayı daha önce yüklemeniz gerekiyorsa, dosyayı akış için çaba sarf ediyorsunuz, akış hiç fark etmiyor, ha?
acdcjunior

yukarıdaki kod neden benim için çalışmıyor? 0 bayt dosyasını indirir. Ben kontrol ve ByteArray & ResourceMessage dönüştürücüler var emin yaptı. Bir şey mi kaçırıyorum?
coding_idiot

ByteArray ve ResourceMessage dönüştürücüler için neden endişeleniyorsunuz?
acdcjunior

20

Bu kod, jsp'deki bir bağlantıyı tıklattığınızda bahar denetleyicisinden otomatik olarak bir dosyayı indirmek için iyi çalışıyor.

@RequestMapping(value="/downloadLogFile")
public void getLogFile(HttpSession session,HttpServletResponse response) throws Exception {
    try {
        String filePathToBeServed = //complete file name with path;
        File fileToDownload = new File(filePathToBeServed);
        InputStream inputStream = new FileInputStream(fileToDownload);
        response.setContentType("application/force-download");
        response.setHeader("Content-Disposition", "attachment; filename="+fileName+".txt"); 
        IOUtils.copy(inputStream, response.getOutputStream());
        response.flushBuffer();
        inputStream.close();
    } catch (Exception e){
        LOGGER.debug("Request could not be completed at this moment. Please try again.");
        e.printStackTrace();
    }

}

14

Aşağıdaki kod benim için bir metin dosyası oluşturmak ve indirmek için çalıştı.

@RequestMapping(value = "/download", method = RequestMethod.GET)
public ResponseEntity<byte[]> getDownloadData() throws Exception {

    String regData = "Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem Ipsum has been the industry's standard dummy text ever since the 1500s, when an unknown printer took a galley of type and scrambled it to make a type specimen book. It has survived not only five centuries, but also the leap into electronic typesetting, remaining essentially unchanged. It was popularised in the 1960s with the release of Letraset sheets containing Lorem Ipsum passages, and more recently with desktop publishing software like Aldus PageMaker including versions of Lorem Ipsum.";
    byte[] output = regData.getBytes();

    HttpHeaders responseHeaders = new HttpHeaders();
    responseHeaders.set("charset", "utf-8");
    responseHeaders.setContentType(MediaType.valueOf("text/html"));
    responseHeaders.setContentLength(output.length);
    responseHeaders.set("Content-disposition", "attachment; filename=filename.txt");

    return new ResponseEntity<byte[]>(output, responseHeaders, HttpStatus.OK);
}

5

Ne hızlı düşünebilirim, pdf oluşturmak ve koddan webapp / download / <RANDOM-FILENAME> .pdf saklamak ve HttpServletRequest kullanarak bu dosyaya bir ileri göndermek

request.getRequestDispatcher("/downloads/<RANDOM-FILENAME>.pdf").forward(request, response);

veya görünüm çözümleyicinizi aşağıdaki gibi yapılandırabiliyorsanız,

  <bean id="pdfViewResolver"
        class="org.springframework.web.servlet.view.InternalResourceViewResolver">
    <property name="viewClass"
              value="org.springframework.web.servlet.view.JstlView" />
    <property name="order" value=”2″/>
    <property name="prefix" value="/downloads/" />
    <property name="suffix" value=".pdf" />
  </bean>

o zaman geri dön

return "RANDOM-FILENAME";

1
İki görünüm çözümleyicisine ihtiyacım varsa, aynı zamanda çözümleyicinin adını da döndürebilir veya denetleyicide nasıl seçebilirim?
azerafati

3

Aşağıdaki çözüm benim için çalışıyor

    @RequestMapping(value="/download")
    public void getLogFile(HttpSession session,HttpServletResponse response) throws Exception {
        try {

            String fileName="archivo demo.pdf";
            String filePathToBeServed = "C:\\software\\Tomcat 7.0\\tmpFiles\\";
            File fileToDownload = new File(filePathToBeServed+fileName);

            InputStream inputStream = new FileInputStream(fileToDownload);
            response.setContentType("application/force-download");
            response.setHeader("Content-Disposition", "attachment; filename="+fileName); 
            IOUtils.copy(inputStream, response.getOutputStream());
            response.flushBuffer();
            inputStream.close();
        } catch (Exception exception){
            System.out.println(exception.getMessage());
        }

    }

2

aşağıdaki gibi bir şey

@RequestMapping(value = "/download", method = RequestMethod.GET)
public void getFile(HttpServletResponse response) {
    try {
        DefaultResourceLoader loader = new DefaultResourceLoader();
        InputStream is = loader.getResource("classpath:META-INF/resources/Accepted.pdf").getInputStream();
        IOUtils.copy(is, response.getOutputStream());
        response.setHeader("Content-Disposition", "attachment; filename=Accepted.pdf");
        response.flushBuffer();
    } catch (IOException ex) {
        throw new RuntimeException("IOError writing file to output stream");
    }
}

PDF'yi görüntüleyebilir veya buradan örnekleri indirebilirsiniz


1

Eğer herkese yardım ederse. Infeligo'nun kabul ettiği yanıtı önerebilir, ancak bu ekstra biti zorla indirme koduna koyabilirsiniz.

response.setContentType("application/force-download");


0

Benim durumumda istek üzerine bazı dosya üretiyorum, bu yüzden de url oluşturulmalıdır.

Benim için böyle bir şey çalışıyor:

@RequestMapping(value = "/files/{filename:.+}", method = RequestMethod.GET, produces = "text/csv")
@ResponseBody
public FileSystemResource getFile(@PathVariable String filename) {
    String path = dataProvider.getFullPath(filename);
    return new FileSystemResource(new File(path));
}

Çok önemli mime türü producesve aynı zamanda, bu dosya adı bağlantının bir parçası olduğundan kullanmanız gerekir @PathVariable.

HTML kodu şöyle görünür:

<a th:href="@{|/dbreport/files/${file_name}|}">Download</a>

Nerede ${file_name}denetleyicisi Thymeleaf tarafından oluşturulan ve mesafesindedir yani: result_20200225.csv, tüm url behing link böylece: example.com/aplication/dbreport/files/result_20200225.csv.

Bağlantı tarayıcısına tıkladıktan sonra bana dosya ile ne yapacağımı sorar - kaydet veya aç.

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.