目前,網(wǎng)上一搜SpringBoot環(huán)境下載文件。有多種實(shí)現(xiàn)方式,大概率出來(lái)的會(huì)是基礎(chǔ)版的,基礎(chǔ)版的有幾個(gè)坑,我這里分別將基礎(chǔ)版以及基礎(chǔ)版會(huì)出現(xiàn)的問(wèn)題,從而引申各種解決方法。
基礎(chǔ)版
話不多說(shuō),直接上源碼:
@RestController
public class TestDownload {
@Value("${gen.base.path}")
private String baseFilePath;
@ApiOperation(value = "SpringBoot實(shí)現(xiàn)文件下載", notes = "基礎(chǔ)版")
@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,"請(qǐng)您輸入正確的文件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()));
}
}
這個(gè)基礎(chǔ)版是有兩個(gè)問(wèn)題:
1、不能手動(dòng)(主動(dòng))關(guān)閉文件流
2、下載文件較大時(shí),jvm會(huì)內(nèi)存溢出。
2. 進(jìn)階版
主動(dòng)關(guān)閉文件流。
@ApiOperation(value = "SpringBoot實(shí)現(xiàn)文件下載", notes = "進(jìn)階版")
@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,"請(qǐng)您輸入正確的文件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);
}
這個(gè)版本普遍適用,但是仍然不能解決下載文件較大導(dǎo)致內(nèi)存溢出的問(wèn)題,所以這里就來(lái)個(gè)功能更全一點(diǎn)的,使用緩存流,邊讀邊寫
3. 高級(jí)版
使用緩存流,邊讀邊寫
@ApiOperation(value = "SpringBoot實(shí)現(xiàn)文件下載", 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,"請(qǐng)您輸入正確的文件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();
}
}
}
當(dāng)然,如果不想使用JAVA自帶的BufferedInputStream當(dāng)緩沖區(qū)的話,也可以自己寫,例子如下
@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,"請(qǐng)您輸入正確的文件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;
//緩沖區(qū)(自己可以設(shè)置大小):從輸入流中讀取一定數(shù)量的字節(jié),并將其存儲(chǔ)在緩沖區(qū)字節(jié)數(shù)組中,讀到末尾返回-1
while ((len = inputStream.read(b)) > 0) {
outputStream.write(b, 0, len);
}
inputStream.close();
}
這里有一個(gè)技巧,
①tips處的代碼,Content-disposition 為屬性名。
attachment 表示以附件方式下載。
如果要在頁(yè)面中打開(kāi),則改為 inline。
filename 如果為中文,則會(huì)出現(xiàn)亂碼。
解決辦法有兩種:
1、 使用 fileName = new String(fileName.getBytes(), “ISO8859-1”) 語(yǔ)句
2、使用 fileName = HttpUtility.UrlEncode(filename, System.Text.Encoding.UTF8) 語(yǔ)句