不要手动做任何事情,而是将工作委托给框架:
- 从处理程序方法返回
ResponseEntity<Resource>
- 明确指定
Content-Type
- 必要时设置
Content-Disposition:
- 文件名
- 类型
-
inline 强制在浏览器中预览
-
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<Resource>
当您返回 ResponseEntity<Resource> 时,ResourceHttpMessageConverter 会启动并写入适当的响应。
resource 可能是:
如果您需要从应用程序资源目录下载文件,请查看 my answer:它解释了如何使用 ClassPathResource 在类路径中定位资源
注意Content-Type 标头集可能有误(请参阅FileSystemResource is returned with content type json)。这就是为什么这个答案建议明确设置Content-Type。
明确指定Content-Type:
一些选项是:
MediaTypeFactory 允许发现适合Resource 的MediaType(另请参见/org/springframework/http/mime.types 文件)
必要时设置Content-Disposition:
有时需要在浏览器中强制下载或让浏览器打开文件作为预览。您可以使用Content-Disposition 标头来满足此要求:
HTTP 上下文中的第一个参数是inline(默认值,表示它可以显示在网页内,也可以作为网页显示)或attachment(表示应该下载它;大多数浏览器显示“另存为”对话框,预填充文件名参数的值(如果存在)。
在 Spring Framework 中,可以使用 ContentDisposition。
在浏览器中预览文件:
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 只能读取一次,因此如果您返回InputStreamResource,Spring 将不会写入Content-Length 标头(这里是来自ResourceHttpMessageConverter 的sn-p 代码):
@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);
}
在其他情况下它工作正常:
~ $ curl -I localhost:8080/downloadPdf.pdf | grep "Content-Length"
Content-Length: 7554270