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

PHP 下載 JPG 文件為空/損壞的終極解決方案

     編輯:PHP 相關知識

問題現象

  • 瀏覽器直接打開圖片顯示紅叉(損壞)
  • 強制下載後文件大小正確,但無法打開(內容為空/亂碼)
  • 服務器端用 file_put_contents 保存同一文件卻正常

根因分析

輸出緩衝區污染 + 缺少二進制傳輸聲明。

  1. PHP 腳本執行前,可能已有空白字符、BOM、錯誤信息、Session 啟動輸出等寫入輸出緩衝區。
  2. 直接 echo file_get_contents() 會將這些「垃圾數據」混入圖片二進制流,導致文件頭損壞。
  3. 缺少 Content-Transfer-Encoding: binary 聲明,部分代理/瀏覽器可能嘗試文本模式傳輸破壞二進制數據。

錯誤代碼對比

<?php
// ❌ 錯誤示範
$filetype = 'image/pjpeg';
header("Content-type: {$filetype}");
header("Expires: 0");
header("Cache-Control: must-revalidate, post-check=0, pre-check=0");
header("Pragma: public");

$file_content = file_get_contents($filepath);
echo $file_content; // 垃圾數據混入!
exit;
?>

標準解決方案(三步走)

<?php
// 1. 設置標準下載 Header
$filetype = 'image/jpeg'; // 標準 MIME,推薦 jpeg 而非 pjpeg
header("Content-Type: {$filetype}");
header("Expires: 0");
header("Cache-Control: must-revalidate, post-check=0, pre-check=0");
header("Pragma: public");
header("Content-Transfer-Encoding: binary"); // 關鍵:聲明二進制傳輸
header('Content-Disposition: attachment; filename="' . basename($filepath) . '"'); // 強制下載並指定文件名
header('Content-Length: ' . filesize($filepath)); // 關鍵:告知文件大小,支持斷點續傳/進度條

// 2. 清空輸出緩衝區(核心步驟!)
ob_clean(); // 丟棄之前所有輸出(空白、BOM、錯誤信息等)
flush();    // 發送 Header 到客戶端(可選,視環境)

// 3. 直接輸出文件流(最高效、最安全)
readfile($filepath); // 內部使用流式讀取,不佔用 PHP 內存,比 file_get_contents+echo 更優
exit;
?>

關鍵函數對比

函數內存佔用適用場景推薦度
readfile()極低(流式)大文件下載、圖片/視頻輸出⭐⭐⭐⭐⭐
file_get_contents() + echo高(全載入內存)小文件、需修改內容後輸出⭐⭐
fpassthru()低已打開文件指針的流式輸出⭐⭐⭐

深度解析 ob_clean()

ob_clean() 丟棄當前輸出緩衝區的內容,但保留緩衝區開啟狀態。相當於 ASP.NET 的 Response.Clear()。
配合 flush() 可確保 Header 先行發送,避免「Header 已發送」錯誤。

完整生產級下載函數封裝

<?php
/**
 * 安全文件下載輸出
 * @param string $filePath 服務器絕對路徑
 * @param string $fileName 下載顯示文件名(可選)
 * @param string $mimeType MIME 類型(可選,自動檢測)
 */
function safeDownload($filePath, $fileName = '', $mimeType = '') {
    if (!is_file($filePath) || !is_readable($filePath)) {
        header('HTTP/1.1 404 Not Found');
        exit('File not found or not readable');
    }

    $fileName = $fileName ?: basename($filePath);
    $fileSize = filesize($filePath);
    $mimeType = $mimeType ?: mime_content_type($filePath); // 需 fileinfo 擴展

    // 防止中文文件名亂碼:RFC 5987 編碼
    $encodedName = rawurlencode($fileName);
    $headerValue = "attachment; filename*=UTF-8''{$encodedName}; filename=\"{$fileName}\"";

    header("Content-Type: {$mimeType}");
    header("Content-Transfer-Encoding: binary");
    header("Content-Length: {$fileSize}");
    header("Content-Disposition: {$headerValue}");
    header("Expires: 0");
    header("Cache-Control: must-revalidate, post-check=0, pre-check=0");
    header("Pragma: public");

    // 清理緩衝區並輸出
    if (ob_get_level()) {
        ob_clean();
    }
    flush();

    readfile($filePath);
    exit;
}

// 使用示例
safeDownload('/data/images/photo.jpg', '風景照.jpg', 'image/jpeg');
?>

排查清單(若仍失敗)

  1. 確認 $filePath 是絕對路徑且文件可讀(is_readable())。
  2. 檢查 PHP 是否開啟了 output_buffering(php.ini 或 ob_start()),必須配合 ob_clean()。
  3. 確保 無任何字符在 <?php 之前輸出(包括 BOM、空行、HTML)。
  4. 關閉錯誤顯示(ini_set('display_errors', 0)),防止 Warning 混入二進制流。
  5. Nginx + PHP-FPM 環境檢查 fastcgi_buffering off; 避免 Nginx 緩衝干擾大文件流。
  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved