無論哪種模式,必須發送正確的回應標頭告訴瀏覽器「這是一個應該下載的檔案」:
header('Content-Type: application/octet-stream'); // 通用二進位串流
header('Content-Disposition: attachment; filename="export.csv"'); // 觸發下載並指定檔名
header('Content-Transfer-Encoding: binary'); // 二進位傳輸
// Content-Length 僅適用於實體檔案模式
<?php
$filePath = 'data/prefs.csv'; // 伺服器上的實體檔案路徑
if (!is_file($filePath)) {
http_response_code(404);
exit('檔案不存在');
}
// 必須在任何輸出前發送標頭
header('Content-Type: application/octet-stream');
header('Content-Disposition: attachment; filename="' . basename($filePath) . '"');
header('Content-Transfer-Encoding: binary');
header('Content-Length: ' . filesize($filePath)); // 讓瀏覽器顯示進度條
// 直接輸出檔案內容到輸出緩衝區 (記憶體效率高)
readfile($filePath);
exit; // 確保無額外輸出
<?php
// 例如從資料庫查詢組裝 CSV 內容
$csvData = "ID,Name,Email\n";
$csvData .= "1,張三,zhang@example.com\n";
$csvData .= "2,李四,li@example.com\n";
// ... 迴圈組裝更多行
$fileName = 'export_' . date('Ymd_His') . '.csv';
header('Content-Type: application/octet-stream');
header('Content-Disposition: attachment; filename="' . $fileName . '"');
// 動態內容無法預知長度,省略 Content-Length
// header('Content-Length: ' . strlen($csvData)); // 可選加上
echo $csvData;
exit;
<?php
$fileName = 'big_export_' . date('Ymd_His') . '.csv';
header('Content-Type: application/octet-stream');
header('Content-Disposition: attachment; filename="' . $fileName . '"');
header('Content-Transfer-Encoding: chunked'); // 分塊傳輸編碼
$handle = fopen('php://output', 'w');
// 寫入 BOM (可選,解決 Excel 開啟 UTF-8 亂碼)
fwrite($handle, "\xEF\xBB\xBF");
// 寫入標題列
fputcsv($handle, ['ID', '姓名', 'Email', '建立時間']);
// 模擬大量資料分批輸出
$batchSize = 1000;
$offset = 0;
while (true) {
// $rows = $db->query("SELECT * FROM users LIMIT $batchSize OFFSET $offset")->fetchAll();
$rows = get_batch_data($offset, $batchSize); // 假設函數
if (empty($rows)) break;
foreach ($rows as $row) {
fputcsv($handle, $row);
}
$offset += $batchSize;
// 可選:flush() 強制輸出到瀏覽器,減少伺服器記憶體壓力
// flush();
}
fclose($handle);
exit;
function get_batch_data($offset, $limit) {
// 實際對接資料庫查詢
static $dummy = [
[1, '張三', 'zhang@example.com', '2024-01-01'],
[2, '李四', 'li@example.com', '2024-01-02'],
];
return array_slice($dummy, $offset, $limit);
}
| 問題 | 原因 | 解決 |
|---|---|---|
| Excel 開啟中文亂碼 | 缺少 UTF-8 BOM | 輸出前寫入 \xEF\xBB\xBF 或改用 UTF-16LE 編碼 |
| 下載檔案內容包含 HTML 錯誤頁面 | 標頭前有空白/換行或錯誤輸出 | 確保 <?php 起始標籤前無任何字元,exit 結束腳本 |
| 大檔案下載中斷/記憶體超限 | 一次性讀入記憶體 | 使用 readfile() 或 fopen php://output 分塊輸出 |
| 檔名中文亂碼 | RFC 5987 編碼問題 | 使用 filename*=UTF-8'' . rawurlencode($name) 格式 |
$encodedName = rawurlencode($chineseName);
header("Content-Disposition: attachment; filename*=UTF-8''{$encodedName}; filename=\"fallback.csv\"");
掌握這兩種模式與進階技巧,即可滿足從簡單報表到大量資料匯出的各類 CSV 下載需求。