首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> Java 程式設計 >> Java 相關知識

Java Web 實現文件下載功能完整實戰指南

     編輯:Java 相關知識

核心原理

瀏覽器識別 Content-Disposition: attachment; filename=xxx 響應頭,彈出「另存為」對話框,而非在頁面顯示。關鍵步驟:

  1. 讀取文件輸入流
  2. 設置響應頭:Content-Disposition、Content-Length、Content-Type
  3. 輸出流寫入文件位元組

完整 Servlet 實現

@WebServlet("/download")
public class FileDownloadServlet extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) 
            throws ServletException, IOException {
        
        // 1. 文件路徑 (建議配置外部存儲目錄,非 WAR 內部)
        String filePath = "/data/uploads/report.pdf"; 
        File file = new File(filePath);
        if (!file.exists()) {
            resp.sendError(HttpServletResponse.SC_NOT_FOUND);
            return;
        }
        
        // 2. 文件名編碼 (RFC 5987 標準,兼容中文)
        String fileName = file.getName();
        String encodedName = URLEncoder.encode(fileName, StandardCharsets.UTF_8)
                                    .replace("+", "%20");
        String contentDisposition = String.format(
            "attachment; filename=\"%s\"; filename*=UTF-8''%s", 
            fileName, encodedName);
        
        // 3. 設置響應頭
        resp.reset();
        resp.setHeader("Content-Disposition", contentDisposition);
        resp.setHeader("Content-Length", String.valueOf(file.length()));
        resp.setContentType("application/octet-stream"); // 通用二進制流
        
        // 4. 流式傳輸 (緩衝 8KB,避免大文件 OOM)
        try (InputStream in = new BufferedInputStream(new FileInputStream(file));
             OutputStream out = new BufferedOutputStream(resp.getOutputStream())) {
            
            byte[] buffer = new byte[8192];
            int len;
            while ((len = in.read(buffer)) != -1) {
                out.write(buffer, 0, len);
            }
            out.flush();
        }
    }
}

前端調用方式

<!-- 方式 1:超連結 (最簡單) -->
<a href="${pageContext.request.contextPath}/download?fileId=123">下載報表</a>

<!-- 方式 2:JS 觸發 (需帶參數/權限校驗) -->
<button onclick="downloadFile(123)">下載</button>
<script>
function downloadFile(id) {
    window.location.href = '${pageContext.request.contextPath}/download?fileId=' + id;
}
</script>

<!-- 方式 3:AJAX + Blob (需處理二進制響應,較複雜,不推薦簡單下載) -->

進階增強

1. 防盜鏈 (Referer 校驗)

String referer = req.getHeader("Referer");
if (referer == null || !referer.startsWith("https://yourdomain.com")) {
    resp.sendRedirect("/403.html");
    return;
}

2. 斷點續傳 (Range 請求)

// 檢查 Range 頭
String range = req.getHeader("Range");
if (range != null) {
    resp.setStatus(HttpServletResponse.SC_PARTIAL_CONTENT); // 206
    // 解析 range: bytes=start-end
    // 設置 Content-Range, Content-Length
    // 只傳輸指定區間
}

3. 大文件異步下載 (生成臨時連結)

  • 前端請求 → 後端生成帶簽名、過期時間的下載 Token → 返回下載 URL
  • 獨立下載服務 (Nginx X-Accel-Redirect / Spring ResourceHttpRequestHandler) 直接轉發,釋放應用線程

4. Spring Boot Controller 寫法

@GetMapping("/download/{id}")
public ResponseEntity download(@PathVariable Long id) {
    FileInfo info = fileService.getById(id);
    Resource resource = new FileSystemResource(info.getPath());
    
    return ResponseEntity.ok()
        .header(HttpHeaders.CONTENT_DISPOSITION, 
            ContentDisposition.attachment().filename(info.getName(), StandardCharsets.UTF_8).build().toString())
        .contentLength(info.getSize())
        .contentType(MediaType.APPLICATION_OCTET_STREAM)
        .body(resource);
}

常見坑點避雷

問題原因解決
中文文件名亂碼瀏覽器編碼解析不一致雙重編碼 filename + filename* (RFC 5987)
大文件記憶體溢出一次性 readAllBytes流式分塊傳輸 8KB 緩衝
下載文件損壞/為 0 字節流未關閉/未 flushtry-with-resources 確保關閉
AJAX 無法觸發下載瀏覽器安全策略阻攔改用 window.location.href 或 <a> 標籤
WAR 部署路徑失效getRealPath 返回 null配置外部絕對路徑或資源庫

安全建議

  • 路徑遍歷防護:規範化路徑 file.getCanonicalPath(),校驗是否在允許目錄下。
  • 權限校驗:下載前驗證用戶身份、文件歸屬、下載次數限制。
  • 內容類型:已知類型設具體 MIME (如 application/pdf),未知用 application/octet-stream。
  • 日誌審計:記錄下載操作 (用戶、文件、時間、IP) 供審計追蹤。

總結

Java Web 文件下載核心在於正確設置三大響應頭 (Content-Disposition、Content-Length、Content-Type) 並採用緩衝流式傳輸。生產環境需補全:中文文件名雙重編碼、防盜鏈、權限校驗、路徑遍歷防護、大文件異步化、下載審計。Spring Boot 可直接使用 ResponseEntity<Resource> 簡化代碼,Nginx X-Accel-Redirect 可進一步卸載應用服務器帶寬。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved