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

然而,我的主要问题是如何允许用户通过Spring控制器下载文件?


当前回答

在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),那么它将是明智的不让它成为一个字节数组。相反,最好使用:

InputStreamResource, PathResource(从Spring 4.0开始)或 FileSystemResource,

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

其他回答

如下图所示

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

我能够通过使用Spring的ResourceHttpMessageConverter中的内置支持来进行流处理。如果可以确定mime类型,这将设置内容长度和内容类型

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

如果这对谁有帮助的话。你可以按照Infeligo给出的答案去做,但只需要在强制下载的代码中添加额外的内容即可。

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

我能很快想到的是,生成pdf并将其存储在webapp/downloads/< RANDOM-FILENAME>.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";

如果你:

不想在发送到响应之前将整个文件加载到字节[]中; 希望/需要通过InputStream发送/下载; 想要有Mime类型和文件名发送的完全控制; 让其他@ControllerAdvice为你(或不)拾取异常。

下面的代码是你需要的:

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

More on setContentLength(): First of all, the content-length header is optional per the HTTP 1.1 RFC. Still, if you can provide a value, it is better. To obtain such value, know that File#length() should be good enough in the general case, so it is a safe default choice. In very specific scenarios, though, it can be slow, in which case you should have it stored previously (e.g. in the DB), not calculated on the fly. Slow scenarios include: if the file is very large, specially if it is on a remote system or something more elaborated like that - a database, maybe.



InputStreamResource

如果你的资源不是一个文件,例如你从DB中获取数据,你应该使用InputStreamResource。例子:

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