檔案上傳後的儲存策略通常有兩種:檔案系統 與 資料庫 BLOB。檔案系統雖簡單,但面臨檔名衝突、目錄遍歷效能、權限管理、跨伺服器同步等難題。資料庫方案利用 ACID 特性與 SQL 查詢能力,可原子性地管理檔案中繼資料與二進位內容,便於權限控制、版本控管、備份還原,尤其適合中小型檔案(如文件、圖片)的集中管理系統。
| 檔案 | 功能 |
|---|---|
file.sql | 建立 receive 資料表結構 |
upload.php | 上傳表單頁面 (含 MAX_FILE_SIZE 隱藏欄位限制大小) |
submit.php | 接收上傳、讀取檔案內容、寫入資料庫 |
show_info.php | 顯示檔案基本資訊 (檔名、大小) |
show_add.php | 輸出檔案二進位內容,觸發瀏覽器下載 |
CREATE TABLE receive (
id INT NOT NULL AUTO_INCREMENT PRIMARY KEY,
file_data LONGBLOB, -- 檔案二進位內容 (支援 4GB)
file_type VARCHAR(100), -- MIME 類型
file_name VARCHAR(255), -- 原始檔名
file_size INT -- 檔案大小 (bytes)
);
關鍵點: 必須使用LONGBLOB(4GB) 而非BLOB(64KB)。PHP 預設upload_max_filesize=2M,大檔案需調整php.ini的upload_max_filesize、post_max_size、memory_limit、max_execution_time。
<form enctype="multipart/form-data" action="submit.php" method="post">
<input type="hidden" name="MAX_FILE_SIZE" value="1000000"> <!-- 限制 1MB,僅供瀏覽器參考 -->
<input type="file" name="myfile">
<input type="submit" name="submit" value="上傳">
</form>
enctype="multipart/form-data" 為檔案上傳必要屬性。MAX_FILE_SIZE 為客戶端提示,伺服器端仍需驗證 $_FILES['myfile']['size']。
<?php
if (!empty($_FILES['myfile']['tmp_name']) && $_FILES['myfile']['error'] === UPLOAD_ERR_OK) {
set_time_limit(60); // 大檔案需延長執行時間
$fp = fopen($_FILES['myfile']['tmp_name'], 'rb');
$file_data = addslashes(fread($fp, filesize($_FILES['myfile']['tmp_name']))); // 註1
fclose($fp);
@unlink($_FILES['myfile']['tmp_name']); // 刪除暫存檔
$file_type = $_FILES['myfile']['type'];
$file_name = $_FILES['myfile']['name'];
$file_size = $_FILES['myfile']['size'];
$conn = mysqli_connect('127.0.0.1', 'user', 'pass', 'test'); // 註2
$stmt = $conn->prepare('INSERT INTO receive (file_data, file_type, file_name, file_size) VALUES (?, ?, ?, ?)');
$stmt->bind_param('sssi', $file_data, $file_type, $file_name, $file_size);
$stmt->execute();
$id = $conn->insert_id;
$conn->close();
set_time_limit(30);
echo "上傳成功 <a href='show_info.php?id=$id'>查看資訊</a>";
} else {
echo "無檔案上傳或上傳錯誤: " . $_FILES['myfile']['error'];
}
?>
註1: 原始碼使用 addslashes 轉義後直接拼接 SQL,極度危險 (SQL注入、二進位資料損壞)。現代寫法必須使用預處理語句 (Prepared Statements) 綁定參數,如上範例所示。
註2: 已改用 mysqli 物件導向風格,並啟用錯誤報告模式。
<?php
$id = isset($_GET['id']) ? (int)$_GET['id'] : 0;
if (!$id) die('缺少 ID 參數');
$conn = mysqli_connect('127.0.0.1', 'user', 'pass', 'test');
$stmt = $conn->prepare('SELECT file_name, file_size FROM receive WHERE id=?');
$stmt->bind_param('i', $id);
$stmt->execute();
$stmt->bind_result($name, $size);
$stmt->fetch();
$stmt->close();
$conn->close();
echo "<hr>檔案資訊:<br>檔名: $name<br>大小: $size bytes<br><a href='show_add.php?id=$id'>下載檔案</a>";
?>
<?php
$id = isset($_GET['id']) ? (int)$_GET['id'] : 0;
if (!$id) die('缺少 ID 參數');
$conn = mysqli_connect('127.0.0.1', 'user', 'pass', 'test');
$stmt = $conn->prepare('SELECT file_data, file_type, file_name FROM receive WHERE id=?');
$stmt->bind_param('i', $id);
$stmt->execute();
$stmt->bind_result($data, $type, $name);
$stmt->fetch();
$stmt->close();
$conn->close();
if (!$data) die('檔案不存在');
// 關鍵:發送正確 HTTP 標頭觸發下載
header('Content-Type: ' . $type);
header('Content-Disposition: attachment; filename="' . rawurlencode($name) . '"');
header('Content-Length: ' . strlen($data));
header('Cache-Control: no-cache, must-revalidate');
header('Pragma: no-cache');
echo $data;
?>
Content-Disposition: attachment 強制瀏覽器彈出「另存新檔」對話框,rawurlencode 處理中文檔名相容性。Content-Length 讓瀏覽器顯示下載進度。
fread($fp, filesize) 一次載入巨大檔案至記憶體。上傳時以 fread($fp, 8192) 迴圈寫入資料庫 (需支援 LOAD DATA 或分塊 UPDATE);下載時以 echo fread($fp, 8192) 迴圈配合 flush() 串流輸出。$_FILES['type'],使用 finfo_file() 或 mime_content_type() 偵測真實類型,建立白名單。rawurlencode 編碼;上傳時可另存安全檔名 (UUID) 避免路徑遍歷。show_add.php 應整合應用層認證授權,防止未授權下載。file_size 或上傳日期分區。innodb_file_per_table 避免共享表空間膨脹。file_data 或 CDN 加速。本文以經典的 5 檔案範例為基礎,完整展示 PHP + MySQL 實作檔案上傳下載的資料庫儲存模式。從建表、表單、處理、資訊查詢、二進位輸出,每個環節都對應了關鍵技術點。現代化改造的核心在於:棄用 mysql_* 改用 mysqli/PDO 預處理、嚴格驗證上傳檔案、分塊處理大檔案、完善 HTTP 下載標頭、整合權限體系。掌握此模式,可快速構建文件管理、附件系統、簡易雲端硬碟等應用。