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

Java Web 檔案下載完整解決方案:從基礎串流到工具類封裝

     編輯:Java 相關知識

檔案下載兩種場景

  1. 靜態資源直連:檔案位於 Web 公開目錄,瀏覽器直接請求 URL 下載 (如 /files/report.pdf)。
  2. 動態串流下載:檔案儲存於非公開路徑、需權限控管、或即時生成 (報表、Excel),需透過 HttpServletResponse.getOutputStream() 輸出。

通用下載工具類 DownloadUtils

public class DownloadUtils {
    private static final String DEFAULT_ENCODING = "UTF-8";
    private static final int BUFFER_SIZE = 8192;

    // 核心方法:InputStream -> Response
    public static void download(HttpServletResponse response, InputStream inputStream,
                                String fileName, String encoding) throws IOException {
        if (inputStream == null || fileName == null) return;

        response.setContentType("application/octet-stream;charset=" + encoding);
        response.setCharacterEncoding(encoding);
        response.setHeader("Content-Disposition", 
            "attachment;filename=" + URLEncoder.encode(fileName, encoding));

        try (BufferedInputStream bis = new BufferedInputStream(inputStream);
             BufferedOutputStream bos = new BufferedOutputStream(response.getOutputStream())) {

            byte[] buffer = new byte[BUFFER_SIZE];
            int len;
            while ((len = bis.read(buffer)) != -1) {
                bos.write(buffer, 0, len);
            }
            bos.flush();
        }
    }

    // 重載:File -> Response
    public static void download(HttpServletResponse response, File file, String fileName) throws IOException {
        if (file == null || !file.exists() || file.isDirectory()) return;
        if (fileName == null) fileName = file.getName();
        try (FileInputStream fis = new FileInputStream(file)) {
            download(response, fis, fileName, DEFAULT_ENCODING);
        }
    }

    // 重載:路徑字串 -> Response
    public static void download(HttpServletResponse response, String filePath, String fileName) throws IOException {
        download(response, new File(filePath), fileName);
    }

    // 設定全域編碼
    public static void setDefaultEncoding(String encoding) { /* ... */ }
}

Controller / Servlet 整合範例

Spring MVC Controller

@Controller
@RequestMapping("/file")
public class FileController {

    @GetMapping("/download")
    public void download(@RequestParam String path, 
                         @RequestParam(required = false) String fileName,
                         HttpServletResponse response) throws IOException {
        DownloadUtils.download(response, path, fileName);
    }

    @GetMapping("/export-excel")
    public void exportExcel(HttpServletResponse response) throws IOException {
        // 即時生成 Excel 串流
        ByteArrayOutputStream baos = excelService.generateExcel();
        DownloadUtils.download(response, new ByteArrayInputStream(baos.toByteArray()), 
                               "報表_" + LocalDate.now() + ".xlsx", "UTF-8");
    }
}

傳統 Servlet

@WebServlet("/download/servlet")
public class DownloadServlet extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws IOException {
        String filePath = "C:/data/export.zip";
        DownloadUtils.download(resp, filePath);
    }
}

防盜鏈下載範例 (圖片)

@WebServlet("/secure/image")
public class SecureImageServlet extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws IOException {
        String referer = req.getHeader("Referer");
        if (referer == null || !referer.startsWith("https://mysite.com")) {
            resp.sendRedirect("/error/hotlink.html");
            return;
        }

        String fileName = URLEncoder.encode("內部圖片.jpg", "UTF-8");
        resp.setHeader("Content-Disposition", "attachment;filename=" + fileName);
        resp.setContentType("image/jpeg");

        try (InputStream in = getServletContext().getResourceAsStream("/images/internal.jpg");
             OutputStream out = resp.getOutputStream()) {
            byte[] buf = new byte[4096];
            int len;
            while ((len = in.read(buf)) != -1) out.write(buf, 0, len);
        }
    }
}

關鍵技術細節

項目說明
Content-Typeapplication/octet-stream 強制下載;已知類型可設定具體 MIME (如 application/pdf)
Content-Dispositionattachment 觸發下載;inline 瀏覽器嘗試直接開啟
中文檔名編碼RFC 5987: filename*=UTF-8''%E6%96%87%E4%BB%B6.txt;相容舊瀏覽器用 ISO-8859-1
緩衝區大小4KB-8KB 平衡記憶體與系統呼叫次數
大檔案支援使用 RandomAccessFile + Range 實現斷點續傳 (見文章 236447)

最佳實踐清單

  • 輸出流務必在 finally 或 try-with-resources 中關閉。
  • 檔案不存在時回傳 404,而非空回應。
  • 敏感檔案下載前驗證權限 (Spring Security @PreAuthorize 或 Servlet Filter)。
  • 生產環境建議由 Nginx X-Accel-Redirect 或 Apache mod_xsendfile 直接服務靜態檔案,減輕 Java 進程負載。
  • 即時生成報表時,使用 ByteArrayOutputStream 或臨時檔案避免記憶體溢位。
  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved