Spring Boot 與前端文件下載問題:大文件、斷點續傳與安全校驗

前言

在企業級 Spring Boot 項目中,文件下載?是非常常見的功能場景:

  • 用戶下載報表、合同、發票 PDF

  • 下載圖片、音視頻資源

  • 系統導出大規模 Excel/CSV 數據

然而,很多開發者在實現文件下載時,會遇到?下載失敗、文件損壞、性能瓶頸、斷點續傳不生效?等問題。

本文將結合 Spring Boot 實踐,詳細解析?文件下載的常見問題與最佳實踐,包括:

  1. 普通小文件下載

  2. 大文件下載與性能優化

  3. 斷點續傳(Range 支持)

  4. 文件下載的安全校驗(防盜鏈、防越權)


一、Spring Boot 文件下載的常見問題

在實際開發中,文件下載可能會遇到以下問題:

  1. 文件名亂碼:不同瀏覽器對?Content-Disposition?的解析差異導致

  2. 大文件內存溢出:一次性讀入內存,OOM 或性能崩潰

  3. 斷點續傳失敗:未正確處理 HTTP Range 請求頭

  4. 安全風險:直接拼接文件路徑,可能被惡意訪問系統敏感文件

  5. 盜鏈問題:外部網站直接引用下載接口,導致帶寬浪費


二、小文件下載(基礎實現)

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("非法請求!");
}

六、最佳實踐總結

  1. 小文件下載ResponseEntity<Resource>?即可

  2. 大文件下載:使用?流式下載,避免內存溢出

  3. 斷點續傳:正確處理 Range 請求頭,返回?206 Partial Content

  4. 安全性

    • 嚴格控制文件目錄

    • 鑒權校驗(Spring Security / 自定義規則)

    • 防盜鏈校驗


七、結語

Spring Boot 文件下載并不是一行代碼就能搞定的簡單功能,而是需要根據?文件大小、用戶體驗、安全性要求?做不同優化。

  • 如果是?小文件,直接返回即可

  • 如果是?大文件,必須使用流式下載,避免 OOM

  • 如果要?提升用戶體驗,斷點續傳必不可少

  • 如果是?企業級系統,一定要加入鑒權和防盜鏈

在實際項目中,可以結合?Nginx 靜態資源下載?+?Spring Boot 鑒權簽名?來進一步優化性能。


本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/diannao/99003.shtml
繁體地址,請注明出處:http://hk.pswp.cn/diannao/99003.shtml
英文地址,請注明出處:http://en.pswp.cn/diannao/99003.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

主板硬件研發基礎--HDMI工作原理

HDMI 接口 技術原理:HDMI 接口采用 TMDS 技術傳輸數字信號,不僅可以傳輸高清視頻信號,還能同時傳輸多聲道音頻信號。它支持 EDID 和 DDC2B,設備之間能夠自動協商并選擇最合適的視頻 / 音頻格式,實現 “即插即用” 功能。 接口類型:常見的有標準 HDMI 接口、Mini-HDMI 接口…

`Object.groupBy`將數組中的數據分到對象中

Object.groupBy 將一個對象或者數組的元素按照規則分組&#xff0c; 返回一個新對象&#xff0c; Object.groupBy(items, callbackFn) items&#xff1a;要分組的對象或數組&#xff08;通常是數組&#xff09;。 callbackFn(element, index, array)&#xff1a;回調函數&#…

反序列化漏洞詳解

用途限制聲明&#xff0c;本文僅用于網絡安全技術研究、教育與知識分享。文中涉及的滲透測試方法與工具&#xff0c;嚴禁用于未經授權的網絡攻擊、數據竊取或任何違法活動。任何因不當使用本文內容導致的法律后果&#xff0c;作者及發布平臺不承擔任何責任。滲透測試涉及復雜技…

SQL數據分析原代碼--創建表與簡單查詢

CREATE TABLE&#xff1a;創建表&#xff0c;定義字段名、類型、注釋INSERT INTO&#xff1a;插入數據&#xff0c;支持單條或批量插入SELECT&#xff1a;查詢數據&#xff0c;*表示所有字段&#xff0c;AS可起別名&#xff0c;DISTINCT去重WHERE&#xff1a;條件篩選&#xff…

k8s查詢ServiceAccount有沒有列出 nodes 的權限

要檢查 ServiceAccount xxxxxx:default 是否具有列出 nodes 的權限&#xff0c;可以使用以下方法&#xff1a;1. **使用 kubectl auth can-i 命令**這是最直接的方法&#xff0c;可以檢查特定用戶或 ServiceAccount 是否具有特定權限&#xff1a;kubectl auth can-i list nodes…

調試Python程序時,控制臺一直打印SharedMemory read faild

from tkinter import filedialog filedialog.askopenfilename()在使用 tkinter 時&#xff0c;只要一處罰&#xff0c;控制臺就不停打印 SharedMemory read faild &#xff0c;雖然能用&#xff0c;但是大大的破壞了調試體驗&#xff0c;看到如下的提示&#xff0c;你說煩不煩&…

QRCode React 完全指南:現代化二維碼生成解決方案

前言 在數字化時代&#xff0c;二維碼已經成為連接線上線下的重要橋梁。無論是分享鏈接、支付碼、還是身份驗證&#xff0c;二維碼都扮演著不可或缺的角色。qrcode.react 是一個專門為 React 應用設計的二維碼生成庫&#xff0c;它能夠快速、靈活地生成各種樣式的二維碼&#…

xxe外部實體注入漏洞

https://owasp.org/www-project-top-ten XXE基礎 xxe外部實體注入 外部實體 xml&#xff08;用于傳輸和存儲數據&#xff09; html&#xff08;用于顯示數據&#xff09; 注入&#xff1a; SQL注入&#xff1a;用戶輸入數據被當做代碼執行 1輸入點 2.輸入數據可以結合到數據庫…

ros2獲取topic信息解析

ros2 ros_discovery_info topic 發布邏輯疑問&#xff1a; 在運行ros2 topic info -v /topic時&#xff0c;運行的是p3&#xff0c;如何與p1進程通訊的呢&#xff1f; 進程間理論上應該是IPC

FFmpeg合成mp4

本章主要介紹如何使用FFmpeg來將一個音頻文件和一個視頻文件合成一個MP4文件&#xff0c;以及在這個過程中我們如何對編碼過程進行封裝以及sample_rate 重采樣的過程&#xff08;由于提供的音頻文件的編碼類型為S16&#xff0c;所以我們需要轉化為MP4支持的FLTP浮點類型&#x…

第十九章 使用LAMP架構部署動態網站環境

第十九章 使用LAMP架構部署動態網站環境 文章目錄第十九章 使用LAMP架構部署動態網站環境一、安裝Httpd服務1、安裝httpd服務2、啟動httpd服務3、設置允許通過防火墻4、驗證http服務是否成功二、安裝Mariadb服務1、安裝Mariadb服務2、啟動Mariadb服務三、安裝PHP服務1、列出可用…

Selenium應用中的核心JavaScript操作技巧

Selenium是一款強大的瀏覽器自動化測試工具&#xff0c;其操作瀏覽器的能力部分來自于其內嵌的JavaScript執行引擎。這使得Selenium不僅能夠模擬用戶在瀏覽器中的各種操作&#xff0c;還能執行復雜的JavaScript腳本&#xff0c;以實現更為精細的控制。本文將探討如何通過Seleni…

《Linux 基礎指令實戰:新手入門的命令行操作核心教程(第一篇)》

前引&#xff1a;當你第一次面對 Linux 系統中那片閃爍著光標、只有黑白字符的終端界面時&#xff0c;或許會和很多初學者一樣感到些許茫然&#xff1a;這些由字母和符號組成的 “指令” 究竟該如何輸入&#xff1f;它們又能完成哪些神奇的操作&#xff1f;其實&#xff0c;Lin…

03.【Linux系統編程】基礎開發工具1(yum軟件安裝、vim編輯器、編輯器gcc/g++)

目錄 1. 軟件包管理器 1.1 什么是軟件包 1.2 Linux軟件生態 1.3 yum具體操作 1.3.1 查看軟件包 1.3.2 安裝軟件 1.3.3 卸載軟件 1.3.4 注意事項(測試網絡) 1.3.5 yum指令集總結 1.4 yum源目錄、安裝源 2. Vim編輯器的使用 2.1 Linux編輯器-vim使用 2.2 vim的基本概…

3DMAX自動材質開關插件AutoMaterial安裝和使用方法

3DMAX自動材質開關AutoMaterial&#xff0c;是一個3dMax腳本插件&#xff0c;它根據材質編輯器中當前活動的材質自動將材質應用于3dMax中新創建的對象&#xff0c;也適用于您復制的沒有材質的對象。它作為一個開關&#xff0c;可以綁定到按鈕或菜單來打開和關閉它。該工具的創建…

Linux內核調優實戰指南

內核調優通常通過修改內核運行時參數來實現&#xff0c;這些參數的配置文件是 Linux 系統中核心的性能調整點。 內核調優配置文件名稱 /etc/sysctl.conf: 這是最傳統和主要的內核參數配置文件。系統啟動時或手動執行 sysctl -p 命令時會讀取并應用其中的設置。/etc/sysctl.d/*.…

Java基礎常見知識點

Java 中 和 equals() 的區別詳解_java中與equals的區別及理解-CSDN博客https://blog.csdn.net/m0_64432106/article/details/142026852深入理解Java中方法的參數傳遞機制 - 悟小天 - 博客園https://www.cnblogs.com/sum-41/p/10799555.html浮點型精度是什么意思&#xff1f;為…

OD C卷 -【高效貨運】

文章目錄高效貨運高效貨運 貨車的額定載貨量為wt&#xff1b;貨物A單件重量為wa&#xff0c;單件運費利潤為pa;貨物B單件重量wb&#xff0c;單件運費利潤為pb;每次出車必須包含A、B貨物&#xff0c;且單件貨物都不可分割&#xff0c;總重量達到額定的載貨量wt;每次出車能夠獲取…

手動解壓并讀取geo 文件 series_matrix_table_begin series_matrix_table_end之間的數據

手動解壓并讀取geo 文件 series_matrix_table_begin series_matrix_table_end之間的數據 1. 手動解壓并讀取文件內容 file_path <- “K:/download/geo/raw_data/GEO/GSE32967_series_matrix.txt.gz” 使用latin1編碼讀取文件所有行 con <- gzfile(file_path, “r”) all_…

主板硬件研發基礎--DP/DP++

現在的主板大多數使用的是比DP功能更加強大的DP++。 DisplayPort++(DP++)是 DisplayPort 技術的增強版,旨在提升與多種視頻接口的兼容性和連接性能。以下是關于它的詳細介紹: 功能特性 多協議兼容:DP++ 接口不僅支持 DisplayPort 標準的信號傳輸,還可以通過內部的轉換電…