目前,网上一搜SpringBoot环境下载文件。有多种实现方式,大概率出来的会是基础版的,基础版的有几个坑,我这里分别将基础版以及基础版会出现的问题,从而引申各种解决方法。
话不多说,直接上源码:
@RestController
public class TestDownload {
@Value("${gen.base.path}")
private String baseFilePath;
@ApiOperation(value = "SpringBoot实现文件下载", notes = "基础版")
@RequestMApping(value = "/baseDownload", method = RequestMethod.GET)
@ResponseBody
public ResponseEntity<InputStreamResource> baseDownload(@RequestParam("fileId") String fileId) throws Exception {
FileSystemResource file = new FileSystemResource(baseFilePath+fileId+".pdf");
if (!file.exists()) {
throw new HaBizException(1,"请您输入正确的文件ID");
}
HttpHeaders headers = new HttpHeaders();
headers.add("Cache-Control", "no-cache, no-store, must-revalidate");
headers.add("Content-Disposition", String.format("attachment; filename=%s", file.getFilename()));
headers.add("Pragma", "no-cache");
headers.add("Expires", "0");
return ResponseEntity
.ok()
.headers(headers)
.contentLength(file.contentLength())
.contentType(MediaType.parseMediaType("application/octet-stream"))
.body(new InputStreamResource(file.getInputStream()));
}
}
这个基础版是有两个问题:
1、不能手动(主动)关闭文件流
2、下载文件较大时,jvm会内存溢出。
主动关闭文件流。
@ApiOperation(value = "SpringBoot实现文件下载", notes = "进阶版")
@RequestMapping(value = "/advancedDownload", method = RequestMethod.GET)
@ResponseBody
public ResponseEntity<StreamingResponseBody> advancedDownload(@RequestParam("fileId") String fileId) throws Exception {
FileSystemResource file = new FileSystemResource(baseFilePath+fileId);
if (!file.exists()) {
throw new HaBizException(1,"请您输入正确的文件ID");
}
InputStream inputStream = file.getInputStream();
StreamingResponseBody responseBody = outputStream -> {
int numberOfBytesToWrite;
byte[] data = new byte[1024];
while ((numberOfBytesToWrite = inputStream.read(data, 0, data.length)) != -1) {
outputStream.write(data, 0, numberOfBytesToWrite);
}
inputStream.close();
};
HttpHeaders headers = new HttpHeaders();
headers.add("Cache-Control", "no-cache, no-store, must-revalidate");
headers.add("Content-Disposition", String.format("attachment; filename=%s", file.getFilename()));
headers.add("Pragma", "no-cache");
headers.add("Expires", "0");
return ResponseEntity.ok()
.headers(headers)
.contentType(MediaType.APPLICATION_OCTET_STREAM)
.body(responseBody);
}
这个版本普遍适用,但是仍然不能解决下载文件较大导致内存溢出的问题,所以这里就来个功能更全一点的,使用缓存流,边读边写
使用缓存流,边读边写
@ApiOperation(value = "SpringBoot实现文件下载", notes = "使用缓存流,边读边写")
@RequestMapping(value = "/cacheDownload", method = RequestMethod.GET)
@ResponseBody
public void cacheDownload(@RequestParam("fileId") String fileId, HttpServletResponse response) throws Exception {
FileSystemResource file = new FileSystemResource(baseFilePath+fileId);
if (!file.exists()) {
throw new HaBizException(1,"请您输入正确的文件ID");
}
String filename = file.getFilename();
InputStream inputStream = null;
BufferedInputStream bufferedInputStream = null;
BufferedOutputStream bufferedOutputStream = null;
response.setHeader("Content-Disposition", "attachment;filename=" + URLEncoder.encode(filename, "UTF-8"));
try {
inputStream = file.getInputStream();
bufferedInputStream = new BufferedInputStream(inputStream);
bufferedOutputStream = new BufferedOutputStream(response.getOutputStream());
FileCopyUtils.copy(bufferedInputStream, bufferedOutputStream);
}catch(Exception e){
throw new HaBizException(1,"下载文件异常"+e);
}finally {
if(null!=inputStream){
inputStream.close();
}
if(null!=bufferedInputStream){
bufferedInputStream.close();
}
if(null!=bufferedOutputStream){
bufferedOutputStream.flush();
bufferedOutputStream.close();
}
}
}
当然,如果不想使用JAVA自带的BufferedInputStream当缓冲区的话,也可以自己写,例子如下
@RequestMapping("/baseCacheDownload")
public void baseCacheDownload(@RequestParam("fileId") String fileId, HttpServletResponse response) throws Exception {
FileSystemResource file = new FileSystemResource(baseFilePath+fileId);
if (!file.exists()) {
throw new HaBizException(1,"请您输入正确的文件ID");
}
InputStream inputStream = file.getInputStream();// 文件的存放路径
response.reset();
response.setContentType("application/octet-stream");
String filename = file.getFilename();
response.addHeader("Content-Disposition", "attachment; filename=" + URLEncoder.encode(filename, "UTF-8"));//①tips
ServletOutputStream outputStream = response.getOutputStream();
byte[] b = new byte[1024];
int len;
//缓冲区(自己可以设置大小):从输入流中读取一定数量的字节,并将其存储在缓冲区字节数组中,读到末尾返回-1
while ((len = inputStream.read(b)) > 0) {
outputStream.write(b, 0, len);
}
inputStream.close();
}
这里有一个技巧,
①tips处的代码,Content-disposition 为属性名。
attachment 表示以附件方式下载。
如果要在页面中打开,则改为 inline。
filename 如果为中文,则会出现乱码。
解决办法有两种:
1、 使用 fileName = new String(fileName.getBytes(), “ISO8859-1”) 语句
2、使用 fileName = HttpUtility.UrlEncode(filename, System.Text.Encoding.UTF8) 语句