从弹簧控制器下载文件

Mil*_*daD 354 java spring controller file download

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

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

Inf*_*igo 374

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

}
Run Code Online (Sandbox Code Playgroud)

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

response.setContentType("application/pdf");
Run Code Online (Sandbox Code Playgroud)

  • 使用Apache的'IOUtils`而不是Spring的`FileCopyUtils`的任何特殊原因? (31认同)
  • 这几乎就是我要说的内容,但您可能还应该将响应类型标头设置为适合该文件的内容. (4认同)
  • 这是一个更好的解决方案:http://stackoverflow.com/questions/16652760/return-generated-pdf-using-spring-mvc (4认同)
  • 是的,刚刚编辑了帖子.我生成了各种文件类型,因此我将其留给浏览器以通过其扩展名确定文件的内容类型. (2认同)
  • @Powerlord Spring 方法关闭流,Apache 则没有。对于 Servlet 响应输出流是否应该在控制器代码中或由 Servlet 容器关闭存在争议...... (2认同)

Sco*_*son 285

通过使用Spring的内置支持和ResourceHttpMessageConverter,我能够对此进行流式处理.如果它可以确定mime类型,这将设置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)); 
}
Run Code Online (Sandbox Code Playgroud)

  • 您可以将produce = MediaType.APPLICATION_OCTET_STREAM_VALUE添加到@RequestMapping以强制下载 (40认同)
  • 这有效.但文件(.csv文件)显示在浏览器中而未下载 - 如何强制浏览器下载? (10认同)
  • 您还应该将**<bean class ="org.springframework.http.converter.ResourceHttpMessageConverter"/>**添加到messageConverters列表中(<mvc:annotation-driven> <mvc:message-converters>) (8认同)
  • 我没有必要,但我认为你可以添加HttpResponse作为方法的参数,然后"response.setHeader("Content-Disposition","attachment; filename = somefile.pdf");" (8认同)
  • 有没有办法用这种方式设置`Content-Disposition`标题? (4认同)
  • 绝对最好的响应也见http://stackoverflow.com/questions/3526523/spring-mvc-pathvariable-getting-truncated for pathvariable被截断(在我的情况下是spring 3.0.7) (2认同)

lob*_*234 76

您应该能够直接在响应上写入文件.就像是

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

然后将文件写为二进制流response.getOutputStream().记得response.flush()最后做,应该这样做.

  • 是不是像这样设置内容类型的'Spring'方式?`@RequestMapping(value ="/ foo/bar",produce ="application/pdf")` (8认同)
  • @Francis如果你的应用程序下载不同的文件类型怎么办?Lobster1234的答案使您可以动态设置内容处置. (4认同)
  • 我猜不是,因为它不可扩展.我们目前正在支持十几种资源.我们可能会根据用户想要上传的内容支持更多的文件类型,在这种情况下,我们最终可能会有很多端点基本上做同样的事情.恕我直言,只有一个下载终点,它处理多种文件类型.@Francis (3认同)
  • 它绝对是"可扩展的",但我们同意不同意这是否是最佳做法 (3认同)
  • 这是真的@Rose,但我相信每种格式定义不同的终点会更好 (2认同)

Ral*_*lph 73

使用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);
}
Run Code Online (Sandbox Code Playgroud)

如果PDF Framework(documentBbody)的返回类型不是字节数组(也没有ByteArrayInputStream),那么最好不要先将它作为字节数组.相反,最好使用:

示例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);
}
Run Code Online (Sandbox Code Playgroud)

  • -1这将不必要地将整个文件加载到内存中,可以轻松地输出OutOfMemoryErrors. (10认同)
  • @FaisalFeroz:是的,这是正确的,但是文件文档无论如何都是在内存中创建的(请参阅问题:“需要在代码中生成 PDF”)。无论如何 - 你克服这个问题的解决方案是什么? (2认同)

acd*_*ior 58

如果你:

  • 不希望byte[]在发送到响应之前将整个文件加载到a中;
  • 想要/需要通过发送/下载InputStream;
  • 想要完全控制发送的Mime Type和文件名;
  • 让其他@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);
}
Run Code Online (Sandbox Code Playgroud)

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


Sun*_*nil 18

此代码可以正常工作,从弹簧控制器上单击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();
    }

}
Run Code Online (Sandbox Code Playgroud)


cac*_*co3 15

不要手工做任何事情,而是将工作委托给框架:

  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);
    }
}
Run Code Online (Sandbox Code Playgroud)

解释

返回 ResponseEntity<Resource>

当您返回 a 时ResponseEntity<Resource>,它会ResourceHttpMessageConverter启动并写入适当的响应。

resource可能是:

如果您需要从应用程序资源目录下载文件,请查看我的答案:它解释了如何使用ClassPathResource

请注意可能错误的Content-Type标头集(请参阅FileSystemResource 以内容类型 json 返回)。这就是为什么这个答案建议Content-Type明确设置。

Content-Type明确指定

一些选项是:

MediaTypeFactory允许探索MediaType适合Resource(见/org/springframework/http/mime.types文件)

Content-Disposition必要时设置

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

HTTP 上下文中的第一个参数是inline(默认值,表明它可以显示在网页内,或作为网页)或attachment(表明它应该被下载;大多数浏览器呈现“另存为”对话框,预填充文件名参数的值(如果存在)。

在 Spring FrameworkContentDisposition中可以使用 a。

要在浏览器中预览文件:

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

强制下载

ContentDisposition disposition = ContentDisposition
        .builder("attachment") // Or .attachment() if you're on Spring MVC 5.3+
        .filename(resource.getFilename())
        .build();
Run Code Online (Sandbox Code Playgroud)

InputStreamResource小心使用

由于 anInputStream只能读取一次,因此Content-Length如果您返回 an ,Spring 将不会写入标头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);
}
Run Code Online (Sandbox Code Playgroud)

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

~ $ curl -I localhost:8080/downloadPdf.pdf  | grep "Content-Length"
Content-Length: 7554270
Run Code Online (Sandbox Code Playgroud)

  • 这应该是公认的答案。它似乎提供了唯一干净的 ContentDisposition 处理并给出了清晰的解释。 (4认同)
  • 绝对是最佳答案 (2认同)
  • 完美运行,可与“springdoc-openapi-ui”v1.5.11、swagger-ui 配合使用。“下载”链接按预期显示,并带有“attachment()”标志。 (2认同)

小智 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);
}
Run Code Online (Sandbox Code Playgroud)


kal*_*yan 5

我可以很快想到的是,生成pdf并将其存储在代码中的webapp/downloads/<RANDOM-FILENAME> .pdf中,并使用HttpServletRequest将转发发送到此文件

request.getRequestDispatcher("/downloads/<RANDOM-FILENAME>.pdf").forward(request, response);
Run Code Online (Sandbox Code Playgroud)

或者,如果您可以配置您的视图解析器,如,

  <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>
Run Code Online (Sandbox Code Playgroud)

然后回来

return "RANDOM-FILENAME";
Run Code Online (Sandbox Code Playgroud)


归档时间:

查看次数:

413025 次

最近记录:

6 年,5 月 前