前言
在企業級 Spring Boot 項目中,文件下載?是非常常見的功能場景:
用戶下載報表、合同、發票 PDF
下載圖片、音視頻資源
系統導出大規模 Excel/CSV 數據
然而,很多開發者在實現文件下載時,會遇到?下載失敗、文件損壞、性能瓶頸、斷點續傳不生效?等問題。
本文將結合 Spring Boot 實踐,詳細解析?文件下載的常見問題與最佳實踐,包括:
普通小文件下載
大文件下載與性能優化
斷點續傳(Range 支持)
文件下載的安全校驗(防盜鏈、防越權)
一、Spring Boot 文件下載的常見問題
在實際開發中,文件下載可能會遇到以下問題:
文件名亂碼:不同瀏覽器對?
Content-Disposition
?的解析差異導致大文件內存溢出:一次性讀入內存,OOM 或性能崩潰
斷點續傳失敗:未正確處理 HTTP Range 請求頭
安全風險:直接拼接文件路徑,可能被惡意訪問系統敏感文件
盜鏈問題:外部網站直接引用下載接口,導致帶寬浪費
二、小文件下載(基礎實現)
Spring Boot 提供了非常簡便的文件下載實現,直接使用?ResponseEntity
?即可:
@GetMapping("/download/{fileName}")
public?ResponseEntity<Resource>?downloadFile(@PathVariable String fileName)?throws?IOException?{Path path = Paths.get("files").resolve(fileName);Resource resource =?new?UrlResource(path.toUri());return?ResponseEntity.ok().header(HttpHeaders.CONTENT_DISPOSITION,?"attachment; filename=\""?+ fileName +?"\"").contentType(MediaType.APPLICATION_OCTET_STREAM).body(resource);
}
? 適合小文件下載(幾 MB 以內),但不適合大文件。
三、大文件下載與性能優化
如果文件較大(幾十 MB 甚至幾 GB),一次性加載到內存會非常危險,容易 OOM。 正確做法是?使用流式下載(Streaming):
@GetMapping("/download/stream/{fileName}")
public?void?downloadBigFile(@PathVariable String fileName, HttpServletResponse response)?throws?IOException?{File file =?new?File("files", fileName);response.setContentType("application/octet-stream");response.setHeader("Content-Disposition",?"attachment; filename="?+ URLEncoder.encode(fileName,?"UTF-8"));try?(BufferedInputStream bis =?new?BufferedInputStream(new?FileInputStream(file));OutputStream os = response.getOutputStream()) {byte[] buffer =?new?byte[1024?*?1024];?// 1MB 緩沖區int?len;while?((len = bis.read(buffer)) != -1) {os.write(buffer,?0, len);os.flush();}}
}
? 優點:
避免一次性加載,降低內存占用
下載過程更加穩定
四、斷點續傳(支持 Range 請求頭)
對于大文件,用戶可能因網絡中斷而下載失敗,重新下載會浪費帶寬。 HTTP 協議支持?Range 請求頭?來實現斷點續傳。
示例實現:
@GetMapping("/download/range/{fileName}")
public?void?downloadFileWithRange(@PathVariable String fileName,HttpServletRequest request,HttpServletResponse response)?throws?IOException?{File file =?new?File("files", fileName);long?fileLength = file.length();// 獲取 Range 頭String range = request.getHeader("Range");long?start =?0, end = fileLength -?1;if?(range !=?null?&& range.startsWith("bytes=")) {String[] parts = range.replace("bytes=",?"").split("-");start = Long.parseLong(parts[0]);if?(parts.length >?1) {end = Long.parseLong(parts[1]);}}long?contentLength = end - start +?1;response.setStatus(HttpServletResponse.SC_PARTIAL_CONTENT);response.setHeader("Accept-Ranges",?"bytes");response.setHeader("Content-Range",?"bytes "?+ start +?"-"?+ end +?"/"?+ fileLength);response.setHeader("Content-Length", String.valueOf(contentLength));response.setContentType("application/octet-stream");try?(RandomAccessFile raf =?new?RandomAccessFile(file,?"r");OutputStream os = response.getOutputStream()) {raf.seek(start);byte[] buffer =?new?byte[1024?*?1024];long?remaining = contentLength;int?len;while?((remaining >?0) && (len = raf.read(buffer,?0, (int) Math.min(buffer.length, remaining))) != -1) {os.write(buffer,?0, len);remaining -= len;}}
}
? 優點:支持斷點續傳(瀏覽器或下載工具可自動續傳)。
五、文件下載的安全校驗
文件下載接口是敏感的,如果未加保護,可能被惡意利用:
1.?越權訪問問題
直接拼接路徑:
/download/../../etc/passwd
?可能導致泄露系統文件解決方法:嚴格限制文件路徑,只允許訪問白名單目錄
String safeDir =?"files";
Path targetPath = Paths.get(safeDir).resolve(fileName).normalize();
if?(!targetPath.startsWith(Paths.get(safeDir))) {throw?new?SecurityException("非法文件路徑!");
}
2.?鑒權校驗
文件一般與用戶權限綁定,例如:
合同文件只能由合同所屬用戶下載
報表文件只能由管理員或指定角色下載
實現:在下載接口中加入?Spring Security 或自定義權限校驗。
@PreAuthorize("hasRole('ADMIN') or @fileService.canAccessFile(#fileName, authentication)")
@GetMapping("/download/secure/{fileName}")
public?ResponseEntity<Resource>?secureDownload(@PathVariable String fileName)?{// 校驗通過后下載
}
3.?防盜鏈(Referer 校驗)
防止外部網站直接引用下載接口,可以校驗請求頭:
String referer = request.getHeader("Referer");
if?(referer ==?null?|| !referer.startsWith("https://mydomain.com")) {throw?new?SecurityException("非法請求!");
}
六、最佳實踐總結
小文件下載:
ResponseEntity<Resource>
?即可大文件下載:使用?流式下載,避免內存溢出
斷點續傳:正確處理 Range 請求頭,返回?
206 Partial Content
安全性:
嚴格控制文件目錄
鑒權校驗(Spring Security / 自定義規則)
防盜鏈校驗
七、結語
Spring Boot 文件下載并不是一行代碼就能搞定的簡單功能,而是需要根據?文件大小、用戶體驗、安全性要求?做不同優化。
如果是?小文件,直接返回即可
如果是?大文件,必須使用流式下載,避免 OOM
如果要?提升用戶體驗,斷點續傳必不可少
如果是?企業級系統,一定要加入鑒權和防盜鏈
在實際項目中,可以結合?Nginx 靜態資源下載?+?Spring Boot 鑒權簽名?來進一步優化性能。