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

PHP 檔案下載實作完全指南:三種主流方案詳解

     編輯:PHP 綜合知識

核心原理:HTTP Header 控制瀏覽器行為

實現檔案下載的關鍵在於發送正確的 HTTP Header,告訴瀏覽器「這是一個附件,請彈出下載對話框」而非直接顯示。核心 Header 包含:

  • Content-Type: application/octet-stream —— 通用二進位流,強制下載
  • Content-Disposition: attachment; filename="xxx" —— 指定下載檔名
  • Content-Length: 檔案大小 —— 支援續傳與進度條

方案一:readfile() 一行搞定(推薦中小檔案)

PHP 內建 readfile() 直接將檔案輸出至輸出緩衝,記憶體效率高,程式碼最簡潔:

<?php
function downloadFile($filePath, $downloadName = '') {
    if (!is_file($filePath)) { http_response_code(404); exit('檔案不存在'); }

    $downloadName = $downloadName ?: basename($filePath);
    $fileSize = filesize($filePath);

    header('Content-Description: File Transfer');
    header('Content-Type: application/octet-stream');
    header('Content-Disposition: attachment; filename="' . $downloadName . '"');
    header('Content-Transfer-Encoding: binary');
    header('Expires: 0');
    header('Cache-Control: must-revalidate, post-check=0, pre-check=0');
    header('Pragma: public');
    header('Content-Length: ' . $fileSize);

    readfile($filePath);
    exit;
}

// 使用範例
downloadFile('/path/to/report.pdf', '2024_Q1_Report.pdf');
?>

方案二:分塊讀取 fread()(大檔案、低記憶體環境)

避免一次性將大檔案載入記憶體,適合幾百 MB 以上檔案:

<?php
function downloadLargeFile($filePath, $downloadName = '') {
    if (!is_file($filePath)) { http_response_code(404); exit('檔案不存在'); }

    $downloadName = $downloadName ?: basename($filePath);
    $fileSize = filesize($filePath);
    $handle = fopen($filePath, 'rb');

    header('Content-Type: application/octet-stream');
    header('Content-Disposition: attachment; filename="' . $downloadName . '"');
    header('Content-Length: ' . $fileSize);
    header('Accept-Ranges: bytes'); // 支援斷點續傳

    while (!feof($handle) && connection_status() === CONNECTION_NORMAL) {
        echo fread($handle, 8192); // 8KB 分塊輸出
        @ob_flush(); @flush();     // 立即推送給瀏覽器
    }
    fclose($handle);
    exit;
}
?>

關鍵細節:connection_status() 檢測用戶是否中斷下載,避免無謂資源佔用;ob_flush() + flush() 確保資料即時傳輸。

方案三:X-Sendfile / X-Accel-Redirect(生產環境最佳效能)

將檔案傳輸交給 Web Server(Nginx/Apache)處理,PHP 僅發送指令,完全不佔用 PHP 程序記憶體與執行緒:

<?php
// Nginx 需在 location 區塊啟用:proxy_request_buffering off; + X-Accel-Redirect
// Apache 需載入 mod_xsendfile 並啟用 XSendFile On

function downloadViaWebServer($internalPath, $downloadName = '') {
    $downloadName = $downloadName ?: basename($internalPath);
    header('Content-Disposition: attachment; filename="' . $downloadName . '"');
    // Nginx 使用內部路徑(需對應 internal location)
    header('X-Accel-Redirect: ' . $internalPath);
    // Apache 使用
    // header('X-Sendfile: ' . realpath($internalPath));
    exit;
}
?>

優勢:極高併發、支援斷點續傳、PHP 進程瞬間釋放。注意:需在 Web Server 端配置對應模組與權限。

常見坑點與解決方案

問題原因解決
下載檔案損壞/多出空白行PHP 標籤前後有輸出、BOM、錯誤訊息混入純 PHP 檔案省略 ?>;開啟 ob_start();確保無 BOM
中文檔名亂碼Header 編碼不一致使用 RFC 5987 格式:filename*=UTF-8'' . rawurlencode($name)
大檔案記憶體耗盡使用 file_get_contents() 或變數儲存整個檔案改用 readfile() 或分塊 fread()
無法續傳缺少 Accept-Ranges、Content-Range 處理實作 Range Request 解析或直接用 X-Sendfile

總結建議

  • 一般專案:readfile() 方案最平衡
  • 大檔案/高併發:優先配置 Nginx X-Accel-Redirect
  • 需權限控管:PHP 驗證權限後,再交給 Web Server 傳輸
  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved