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

Java Web 實現檔案下載完整代碼與最佳實踐

     編輯:Java 相關知識

核心實現 (Servlet 原生 API)

@WebServlet("/download")
public class FileDownloadServlet extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) 
            throws ServletException, IOException {
        
        String filePath = "/var/data/report.pdf"; // 實際路徑
        File file = new File(filePath);
        
        if (!file.exists()) {
            resp.sendError(HttpServletResponse.SC_NOT_FOUND);
            return;
        }

        // 1. 設定回應 Header
        resp.reset(); // 重置緩衝區
        resp.setContentType("application/octet-stream"); // 通用二進制流
        resp.setContentLengthLong(file.length()); // 支援大檔案
        
        // 2. 處理檔案名編碼 (RFC 5987 / RFC 6266)
        String fileName = file.getName();
        String encodedName = encodeFileName(fileName, req.getHeader("User-Agent"));
        resp.setHeader("Content-Disposition", 
            "attachment; filename=\"" + encodedName + "\"; filename*=UTF-8''" + encodedName);

        // 3. 高效傳輸 (NIO 零拷貝 Java 8+)
        try (FileChannel channel = FileChannel.open(file.toPath(), StandardOpenOption.READ);
             java.io.OutputStream out = resp.getOutputStream()) {
            
            // 直接通道傳輸,避免 JVM 堆記憶體拷貝
            long transferred = 0;
            long size = file.length();
            while (transferred < size) {
                transferred += channel.transferTo(transferred, size - transferred, 
                    ((java.nio.channels.WritableByteChannel) out));
            }
            out.flush();
        }
    }

    /** 跨瀏覽器檔案名編碼 */
    private String encodeFileName(String fileName, String userAgent) throws UnsupportedEncodingException {
        String encoded = URLEncoder.encode(fileName, StandardCharsets.UTF_8.name())
            .replaceAll("\\+", "%20"); // 空格編碼為 %20 而非 +
        
        // 舊版 IE/Edge 特殊處理 (可選)
        if (userAgent != null && userAgent.contains("MSIE")) {
            return encoded;
        }
        return encoded; // 現代瀏覽器支援 filename*=UTF-8''
    }
}

Spring MVC / Spring Boot 簡化寫法

@GetMapping("/download/{id}")
public ResponseEntity<Resource> download(@PathVariable String id) {
    File file = storageService.getFile(id);
    if (file == null || !file.exists()) {
        return ResponseEntity.notFound().build();
    }
    
    Resource resource = new FileSystemResource(file);
    String encodedName = URLEncoder.encode(file.getName(), StandardCharsets.UTF_8)
        .replaceAll("\\+", "%20");
    
    return ResponseEntity.ok()
        .contentType(MediaType.APPLICATION_OCTET_STREAM)
        .contentLength(file.length())
        .header(HttpHeaders.CONTENT_DISPOSITION, 
            "attachment; filename=\"" + encodedName + "\"; filename*=UTF-8''" + encodedName)
        .body(resource);
}

關鍵技術點深度解析

1. Content-Disposition 編碼標準

  • filename="...":舊標準,僅支援 ASCII/ISO-8859-1,中文會亂碼
  • filename*=UTF-8''...:RFC 5987/6266 標準,現代瀏覽器 (Chrome, Firefox, Edge, Safari) 完美支援 UTF-8
  • **雙重寫法** 兼容新舊:attachment; filename="fallback"; filename*=UTF-8''real

2. 大檔案傳輸優化

方案記憶體CPU適用場景
byte[] 讀入記憶體高 (OOM 風險)低< 10MB 小檔
BufferedInputStream + BufferedOutputStream低 (緩衝區)中通用中型檔
FileChannel.transferTo() (零拷貝)極低極低> 100MB 大檔、高併發
HttpServletResponse.sendFile() (Servlet 5.0+)容器優化容器優化Tomcat 10+/Jetty 11+

3. 斷點續傳支援 (Range Request)

生產環境大檔下載建議實作 HTTP Range 請求:

// 簡易邏輯
String range = req.getHeader("Range"); // bytes=0-1023
if (range != null) {
    resp.setStatus(HttpServletResponse.SC_PARTIAL_CONTENT); // 206
    resp.setHeader("Accept-Ranges", "bytes");
    resp.setHeader("Content-Range", "bytes " + start + "-" + end + "/" + total);
    // 只傳輸指定區間
}

4. 安全性考量

  • **路徑遍歷防護**:嚴格驗證 filePath 在允許目錄下,使用 file.getCanonicalPath().startsWith(allowedDir)
  • **權限控管**:下載前檢查用戶是否有權限存取該檔案
  • **敏感資訊洩漏**:錯誤訊息不要洩漏伺服器真實路徑

Struts 2 時代寫法 (歷史參考)

public class DownloadAction extends ActionSupport {
    private String fileName;
    private InputStream inputStream;
    
    public String execute() {
        File file = new File("/data/" + fileName);
        inputStream = new FileInputStream(file);
        return SUCCESS;
    }
    
    public InputStream getInputStream() { return inputStream; }
    // struts.xml 配置 result type="stream"
}

總結

  1. 設定正確的 Content-Type、Content-Length、Content-Disposition
  2. 檔案名**必須**做 UTF-8 編碼並採用 filename* 標準
  3. 大檔案使用 FileChannel.transferTo() 零拷貝或容器 sendFile
  4. 生產環境加上 Range 斷點續傳、權限檢查、路徑遍歷防護
  5. Spring Boot 使用 ResponseEntity<Resource> 最簡潔
  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved