我们实现http文件下载,主要是在响应头中让Content-Disposition的值为attachment(意味着消息体应该被下载到本地;大多数浏览器会呈现一个“保存为”的对话框,将filename的值预填为下载后的文件名,假如它存在的话)
之前我都是使用在下载的方法里注入HttpServletResponse,然后往response里写数据,然后设置Header中加入Content-Disposition
今天刚好又需要写一个下载的功能,然而我不想注入什么HttpServletResponse,那样很不美观,从网上的资料了解到了ResponseEntity,从spring MVC的文档中了解到这个类就像@ResponseBody,但是多了响应头和状态码。刚好下载文件是需要设置响应头的,所以就用了这个。
然而构造方法是这样的
public ResponseEntity(T body, MultiValueMap<String, String> headers, HttpStatus status) {
super(body, headers);
Assert.notNull(status, "HttpStatus must not be null");
this.status = status;
}
这个body能不能直接用File呢,答案是不能。我试着下载了一个txt文件,确实会下载一个文件,但是这个文件的内容是文件的路径。我猜估计是掉了File的toString方法。具体干了啥,感兴趣的可以看看AbstractMessageConverterMethodProcessor的writeWithMessageConverters,debug一遍就清楚了。
不能直接用File,那该用什么呢,习惯性的在stackoverflow上搜了一下,有用Resource的,也有用Byte[]的。那么两者有啥区别呢。
用Resource,你返回的时候还是依赖文件的,你的Controller在返回之后还要进行一系列的操作,如果在这之前文件被删了,那么下载下拉的文件就是损坏的。一开始我是这么干的
try {
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.valueOf("application/force-download"));
headers.set("Content-Disposition", "attachment;fileName*=UTF-8''" + UriUtils.encode(file.getName(), "UTF-8"));
return new ResponseEntity<Resource>(new FileSystemResource(file), headers, HttpStatus.OK);
} finally {
// 这里删除文件
}
这个finally会在return语句的计算之后返回之前执行。所以文件被写到响应之前就被删了。所以我决定采用这种方式
try(FileInputStream fi = new FileInputStream(file)) {
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.valueOf("application/force-download"));
headers.set("Content-Disposition", "attachment;fileName*=UTF-8''" + UriUtils.encode(file.getName(), "UTF-8"));
return new ResponseEntity<byte[]>(IOUtils.toByteArray(fi), headers, HttpStatus.OK);
} finally {
// 这里删除文件
}
文件流要关闭哟,希望大家都用try-with-resources,这都9102年了答应我不要在finally里面close然后又try-catch了,我实在看不下去了