从弹簧控制器下载文件

时间:2011-04-15 06:46:44

标签: java spring file download controller

我有一个要求,我需要从网站下载PDF。 PDF需要在代码中生成,我认为这将是freemarker和像iText这样的PDF生成框架的组合。有更好的方法吗?

但是,我的主要问题是如何允许用户通过Spring Controller下载文件?

14 个答案:

答案 0 :(得分:363)

@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");
    }

}

一般来说,当你有response.getOutputStream()时,你可以在那里写任何东西。您可以将此输出流作为将生成的PDF放入生成器的位置。另外,如果您知道要发送的文件类型,可以设置

response.setContentType("application/pdf");

答案 1 :(得分:279)

我能够使用Spring的内置支持和它的ResourceHttpMessageConverter来对此进行流式处理。如果可以确定mime-type

,这将设置content-length和content-type
@RequestMapping(value = "/files/{file_name}", method = RequestMethod.GET)
@ResponseBody
public FileSystemResource getFile(@PathVariable("file_name") String fileName) {
    return new FileSystemResource(myService.getFileFor(fileName)); 
}

答案 2 :(得分:75)

您应该能够直接在响应上写入文件。像

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

然后在response.getOutputStream()上将文件写为二进制流。请记住最后做response.flush(),并且应该这样做。

答案 3 :(得分:72)

使用Spring 3.0,您可以使用HttpEntity返回对象。如果使用它,那么您的控制器不需要HttpServletResponse对象,因此更容易测试。 除此之外,这个答案相对于Infeligo的答案

如果你的pdf框架的返回值是一个字节数组(阅读我的答案的第二部分以获取其他返回值)

@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框架的返回类型(documentBbody)不是字节数组(也没有ByteArrayInputStream)那么明智的是 NOT 首先使它成为一个字节数组。相反,最好使用:

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);
}

答案 4 :(得分:57)

如果你:

  • 在发送到响应之前,不希望将整个文件加载到byte[];
  • 希望/需要通过InputStream;
  • 发送/下载
  • 希望完全控制发送的Mime类型和文件名;
  • 让其他@ControllerAdvice为您挑选例外。

以下代码是您所需要的:

@RequestMapping(value = "/stuff/{stuffId}", method = RequestMethod.GET)
public ResponseEntity<InputStreamResource> downloadStuff(@PathVariable int stuffId)
                                                                  throws IOException {
    String fullPath = stuffService.figureOutFileNameFor(stuffId);
    File file = new File(fullPath);

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

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

另请注意,为了避免读取整个文件只是为了计算它的长度,你最好先存储它。请务必查看InputStreamResource的文档。

答案 5 :(得分:17)

此代码可以正常工作,可以在弹出控制器上单击jsp上的链接时自动下载文件。

@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();
    }

}

答案 6 :(得分:12)

下面的代码可以帮助我生成和下载文本文件。

@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);
}

答案 7 :(得分:5)

我可以很快想到的是,生成pdf并将其存储在webapp / downloads /&lt;来自代码的RANDOM-FILENAME&gt; .pdf并使用HttpServletRequest向此文件发送转发

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

或者您可以将视图解析器配置为

  <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>

然后返回

return "RANDOM-FILENAME";

答案 8 :(得分:3)

  1. 从处理程序方法中返回ResponseEntity<Resource>
  2. 明确指定Content-Type
  3. 如有必要,设置Content-Disposition
    1. 文件名
    2. 类型
      1. inline强制在浏览器中预览
      2. attachment强制下载
@Controller
public class DownloadController {
    @GetMapping("/downloadPdf.pdf")
    // 1.
    public ResponseEntity<Resource> downloadPdf() {
        FileSystemResource resource = new FileSystemResource("/home/caco3/Downloads/JMC_Tutorial.pdf");
        // 2.
        MediaType mediaType = MediaTypeFactory
                .getMediaType(resource)
                .orElse(MediaType.APPLICATION_OCTET_STREAM);
        HttpHeaders headers = new HttpHeaders();
        headers.setContentType(mediaType);
        // 3
        ContentDisposition disposition = ContentDisposition
                // 3.2
                .inline() // or .attachment()
                // 3.1
                .filename(resource.getFilename())
                .build();
        headers.setContentDisposition(disposition);
        return new ResponseEntity<>(resource, headers, HttpStatus.OK);
    }
}

说明

返回ResponseEntity<Resouce>

当您返回ResponseEntity<Resource>时,ResourceHttpMessageConverter会跳出来并写出适当的答复。

请注意Content-Type标头集可能有误(请参阅FileSystemResource is returned with content type json)。这就是为什么此答案建议显式设置Content-Type

明确指定Content-Type

一些选项是:

MediaTypeFactory允许发现适合MediaType的{​​{1}}(另请参见Resource文件)

必要时设置/org/springframework/http/mime.types

有时,有必要在浏览器中强制进行下载或使浏览器打开文件作为预览。您可以使用Content-Disposition标头来满足此要求:

HTTP上下文中的第一个参数是Content-Disposition(默认值,指示它可以显示在Web页面中或作为Web页面显示)或inline(指示应下载)。大多数浏览器会显示“另存为”对话框,并预先填充文件名参数的值)。

在Spring框架中,可以使用ContentDisposition

预览在浏览器中的文件:

attachment

要强制进行下载

ContentDisposition disposition = ContentDisposition
        .builder("inline") // Or .inline() if you're on Spring MVC 5.3+
        .filename(resource.getFilename())
        .build();

小心使用ContentDisposition disposition = ContentDisposition .builder("attachment") // Or .attachment() if you're on Spring MVC 5.3+ .filename(resource.getFilename()) .build();

由于InputStreamResource只能被读取一次,因此如果您返回InputStream,Spring不会写Content-Length头(这是InputStreamResource的代码段) :

ResourceHttpMessageConverter

在其他情况下,它可以正常工作:

@Override
protected Long getContentLength(Resource resource, @Nullable MediaType contentType) throws IOException {
    // Don't try to determine contentLength on InputStreamResource - cannot be read afterwards...
    // Note: custom InputStreamResource subclasses could provide a pre-calculated content length!
    if (InputStreamResource.class == resource.getClass()) {
        return null;
    }
    long contentLength = resource.contentLength();
    return (contentLength < 0 ? null : contentLength);
}

答案 9 :(得分:2)

如下所示

@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或下载示例here

答案 10 :(得分:1)

以下解决方案对我有用

    @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());
        }

    }

答案 11 :(得分:0)

如果它可以帮助任何人。您可以执行Infeligo所接受的答案所建议的操作,但只需在代码中多加一点即可进行强制下载。

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

答案 12 :(得分:0)

这可能是一个有用的答案。

Is it ok to export data as pdf format in frontend?

除此之外,将内容处置添加为附件(默认)将下载文件。如果要查看它,则需要将其设置为内联。

答案 13 :(得分:0)

就我而言,我是根据需要生成一些文件,因此还必须生成url。

对我来说,这样的事情:

@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));
}

非常重要的是produces中的mime类型,并且文件的名称是链接的一部分,因此您必须使用@PathVariable

HTML代码如下:

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

${file_name}是Thymeleaf在控制器中生成的,即:result_20200225.csv,因此整个url行为链接为:example.com/aplication/dbreport/files/result_20200225.csv

点击链接后,浏览器询问我如何处理文件-保存或打开。