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

PHP 檔案下載兩種實作方式:直接輸出與分塊讀取

     編輯:PHP 相關知識

實作檔案下載需正確設定 HTTP 標頭,並依檔案大小選擇適當讀取策略,避免記憶體耗盡。

方式一:readfile() 直接輸出(中小檔案)

<?php
function downloadFile(string $filePath, string $downloadName = ''): void
{
    if (!is_file($filePath) || !is_readable($filePath)) {
        http_response_code(404);
        exit('檔案不存在或不可讀');
    }

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

    // 清除輸出緩衝,防止前有輸出導致標頭失效
    if (ob_get_level()) ob_end_clean();

    header('Content-Type: application/octet-stream');
    header('Content-Disposition: attachment; filename="' . rawurlencode($downloadName) . '"');
    header('Content-Length: ' . $fileSize);
    header('Accept-Ranges: bytes');
    header('Cache-Control: private, must-revalidate');
    header('Pragma: public');
    header('Expires: 0');

    readfile($filePath); // 直接輸出至瀏覽器
    exit;
}

// 使用
downloadFile('/var/www/files/report.pdf', 'monthly_report_' . date('Ymd') . '.pdf');

方式二:分塊讀取(大檔案/記憶體受限)

<?php
function downloadLargeFile(string $filePath, string $downloadName = '', int $chunkSize = 8192): void
{
    if (!is_file($filePath) || !is_readable($filePath)) {
        http_response_code(404);
        exit('檔案不存在');
    }

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

    if (ob_get_level()) ob_end_clean();

    header('Content-Type: application/octet-stream');
    header('Content-Disposition: attachment; filename="' . rawurlencode($downloadName) . '"');
    header('Content-Length: ' . $fileSize);
    header('Accept-Ranges: bytes');

    $handle = fopen($filePath, 'rb');
    if (!$handle) {
        http_response_code(500);
        exit('無法開啟檔案');
    }

    while (!feof($handle) && connection_status() === CONNECTION_NORMAL) {
        echo fread($handle, $chunkSize);
        @ob_flush();
        @flush();
    }

    fclose($handle);
    
    // 檢查連線是否中斷
    if (connection_aborted()) {
        // 記錄未完成下載...
    }
    exit;
}

關鍵標頭說明

標頭用途
Content-Type: application/octet-stream通用二進位串流,強制下載而非瀏覽器開啟
Content-Disposition: attachment提示下載對話框,指定檔名
Content-Length告知瀏覽器檔案大小,支援進度條
Accept-Ranges: bytes支援斷點續傳
Cache-Control: private避免 Proxy 快取敏感檔案

中文檔名編碼最佳實踐

// 相容大多數瀏覽器
$encodedName = rawurlencode($chineseName); // RFC 5987 標準
header("Content-Disposition: attachment; filename*=UTF-8''{$encodedName}; filename=\"fallback.txt\"");

安全檢查清單

  • ✅ 驗證檔案路徑在允許目錄內(防止路徑遍歷 ../../etc/passwd)
  • ✅ 檢查使用者權限(登入狀態、擁有權、下載次數限制)
  • ✅ 記錄下載日誌(用戶、檔案、IP、時間、完成狀態)
  • ✅ 大檔案支援 Range 標頭實作斷點續傳
  • ✅ 設定 set_time_limit(0) 防止大檔案傳輸超時
  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved