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

PHP 檔案下載函式完整實作(支援斷點續傳)

     編輯:PHP 綜合知識

在 Web 專案中提供檔案下載是常見需求。單純使用 readfile() 雖簡單,但無法處理大檔案記憶體溢位、斷點續傳(HTTP Range)與下載速度控制。本文提供一個生產級的 download() 函式,支援 2GB 以上大檔、斷點續傳、流式輸出與記憶體安全。

一、核心函式碼

/**
 * 安全下載檔案(支援斷點續傳、大檔、流式輸出)
 *
 * @param string $filePath  伺服器端檔案絕對路徑
 * @param string $fileName  瀏覽器顯示的下載檔名
 * @return void
 */
function download($filePath, $fileName) {
    if (!is_file($filePath) || !is_readable($filePath)) {
        header('HTTP/1.1 404 Not Found');
        exit('檔案不存在或不可讀');
    }

    $fileSize = sprintf('%u', filesize($filePath)); // 相容 32 位系統 >2GB
    $fileName = rawurlencode($fileName); // 解決中文檔名亂碼

    // 基本下載標頭
    header('Content-Type: application/octet-stream');
    header('Content-Disposition: attachment; filename="' . $fileName . '"');
    header('Content-Transfer-Encoding: binary');
    header('Accept-Ranges: bytes'); // 宣告支援 Range
    header('Pragma: public');
    header('Cache-Control: must-revalidate, post-check=0, pre-check=0');

    // 處理 Range 斷點續傳
    $rangeStart = 0;
    $rangeEnd   = $fileSize - 1;

    if (isset($_SERVER['HTTP_RANGE'])) {
        $range = $_SERVER['HTTP_RANGE'];
        if (preg_match('/bytes=(\d+)-(\d*)/', $range, $matches)) {
            $rangeStart = intval($matches[1]);
            $rangeEnd   = $matches[2] ? intval($matches[2]) : $fileSize - 1;
            if ($rangeStart >= $fileSize || $rangeEnd >= $fileSize) {
                header('HTTP/1.1 416 Requested Range Not Satisfiable');
                header('Content-Range: bytes */' . $fileSize);
                exit;
            }
            header('HTTP/1.1 206 Partial Content');
            header('Content-Length: ' . ($rangeEnd - $rangeStart + 1));
            header('Content-Range: bytes ' . $rangeStart . '-' . $rangeEnd . '/' . $fileSize);
        }
    } else {
        header('Content-Length: ' . $fileSize);
    }

    header('Last-Modified: ' . gmdate('D, d M Y H:i:s', filemtime($filePath)) . ' GMT');
    header('Connection: close');

    // 流式輸出,避免佔用大量記憶體
    $handle = fopen($filePath, 'rb');
    if ($handle === false) {
        header('HTTP/1.1 500 Internal Server Error');
        exit('無法開啟檔案');
    }

    fseek($handle, $rangeStart);
    $bufferSize = 1024 * 1024; // 1MB 緩衝
    while (!feof($handle) && ($pos = ftell($handle)) <= $rangeEnd) {
        $readSize = min($bufferSize, $rangeEnd - $pos + 1);
        echo fread($handle, $readSize);
        ob_flush();
        flush();
    }
    fclose($handle);
    exit;
}

二、使用範例

// 呼叫方式
$filePath = '/var/www/uploads/report_2024.pdf';
$fileName = '2024年營運報告.pdf';
download($filePath, $fileName);

三、關鍵技術點解析

特性實作方式效益
大檔支援 sprintf('%u', filesize()) + 64 位整數 突破 2GB 限制
斷點續傳 解析 HTTP_RANGE,回應 206 Partial Content + Content-Range 支援下載工具多線程、網路中斷續傳
記憶體安全 分塊讀取(1MB)+ ob_flush()/flush() 固定記憶體佔用,不隨檔案大小增長
中文檔名 rawurlencode() + RFC 5987 相容 跨瀏覽器正確顯示
快取控制 Last-Modified、ETag(可自行加入) 瀏覽器/Proxy 正確快取

四、進階擴充建議

  1. 加入 ETag: 使用 md5_file($filePath) 或 filemtime() + filesize() 組合,配合 If-None-Match 實現 304 Not Modified。
  2. 限速下載: 在迴圈中加入 usleep() 根據頻寬計算延遲。
  3. 權限驗證: 在呼叫 download() 前驗證使用者登入狀態、下載權限、防盜鏈。
  4. 支援雲端儲存: 若檔案在 S3、OSS,可改用簽名 URL 導向,或用串流代理(Stream Wrapper)整合。

五、常見坑點與除錯

輸出檔案損壞/多出空白行
確認 PHP 檔案結尾 不要有 `?>` 標籤,且 download() 前後無任何 echo、空白、BOM。建議純 PHP 檔案省略結尾標籤。
Nginx + PHP-FPM 環境下大檔下載中斷
調整 fastcgi_read_timeout、proxy_read_timeout;關閉 gzip 對二進位流的壓縮。
瀏覽器顯示亂碼而非下載
檢查 Content-Type 是否為 application/octet-stream,且 Content-Disposition 格式正確。
  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved