檔案上傳是 Web 開發中最常見的功能之一,但也是安全漏洞高發區。本文系統整理 PHP 檔案上傳的核心知識點,涵蓋基礎實作、錯誤碼解析、MIME 類型驗證、重名處理、日期分目錄儲存、多檔案上傳、陣列結構轉換以及伺服器配置優化等實戰場景。
首先建立一個基本的 HTML 表單,注意必須設定 enctype="multipart/form-data":
<form action="upload_file.php" method="post" enctype="multipart/form-data">
<label for="file">選擇檔案:</label>
<input type="file" name="file" id="file" />
<br />
<input type="submit" name="submit" value="上傳" />
</form>
對應的 PHP 處理邏輯(upload_file.php):
<?php
if ($_FILES["file"]["size"] < 20000) {
if ($_FILES["file"]["error"] > 0) {
echo "錯誤代碼: " . $_FILES["file"]["error"] . "<br />";
} else {
echo "檔案名: " . $_FILES["file"]["name"] . "<br />";
echo "類型: " . $_FILES["file"]["type"] . "<br />";
echo "大小: " . ($_FILES["file"]["size"] / 1024) . " KB<br />";
echo "暫存路徑: " . $_FILES["file"]["tmp_name"] . "<br />";
if (file_exists("upload/" . $_FILES["file"]["name"])) {
echo $_FILES["file"]["name"] . " 已存在。";
} else {
move_uploaded_file($_FILES["file"]["tmp_name"], "upload/" . $_FILES["file"]["name"]);
echo "儲存於: upload/" . $_FILES["file"]["name"];
}
}
} else {
echo "檔案過大";
}
?>
$_FILES 陣列包含五個關鍵鍵值:
name:用戶端原始檔名type:瀏覽器提供的 MIME 類型(不可信)size:檔案大小(位元組)tmp_name:伺服器暫存路徑error:錯誤代碼(見下表)| 常數 | 值 | 說明 |
|---|---|---|
| UPLOAD_ERR_OK | 0 | 上傳成功 |
| UPLOAD_ERR_INI_SIZE | 1 | 超過 php.ini upload_max_filesize |
| UPLOAD_ERR_FORM_SIZE | 2 | 超過表單 MAX_FILE_SIZE |
| UPLOAD_ERR_PARTIAL | 3 | 僅部分上傳 |
| UPLOAD_ERR_NO_FILE | 4 | 未選擇檔案 |
| UPLOAD_ERR_NO_TMP_DIR | 6 | 找不到暫存目錄 |
| UPLOAD_ERR_CANT_WRITE | 7 | 寫入失敗 |
$_FILES['type'] 仅依賴副檔名,極易偽造(如將 .mp3 改為 .jpg)。正確做法是啟用 php_fileinfo 擴展,使用 finfo_file() 讀取真實 MIME:
$finfo = new finfo(FILEINFO_MIME_TYPE);
$mime = $finfo->file($_FILES['file']['tmp_name']);
$allowed = ['image/jpeg', 'image/png', 'image/gif'];
if (!in_array($mime, $allowed)) {
die('禁止的檔案類型');
}
採用「原檔名 + 4 位隨機字元」策略,確保唯一性:
if (file_exists("./upload/" . $_FILES["file"]["name"])) {
do {
$suffix = '';
$chars = '0123456789abcdefghijklmnopqrstuvwxyz';
for ($i = 0; $i < 4; $i++) {
$suffix .= $chars[rand(0, strlen($chars) - 1)];
}
$original = $_FILES['file']['name'];
$ext = substr($original, strrpos($original, '.') + 1);
$base = substr($original, 0, strrpos($original, '.'));
$filename = $base . $suffix . '.' . $ext;
} while (file_exists("./upload/" . $filename));
move_uploaded_file($_FILES["file"]["tmp_name"], "./upload/" . $filename);
} else {
move_uploaded_file($_FILES["file"]["tmp_name"], "upload/" . $_FILES["file"]["name"]);
}
利用 date() 動態生成年/月/日路徑,避免單目錄檔案過多影響效能:
$structure = './' . date("Y") . '/' . date("m") . '/' . date("d") . '/';
if (!mkdir($structure, 0777, true)) {
die('建立目錄失敗');
}
move_uploaded_file($_FILES["file"]["tmp_name"], $structure . $_FILES["file"]["name"]);
HTML 表單使用 name="pictures[]":
<form method="post" enctype="multipart/form-data">
<input type="file" name="pictures[]" />
<input type="file" name="pictures[]" />
<input type="file" name="pictures[]" />
<input type="submit" value="送出" />
</form>
PHP 處理預設陣列結構較難操作(按屬性分組),建議重組為「每個檔案一個關聯陣列」:
function diverse_array($vector) {
$result = [];
foreach ($vector as $key1 => $value1) {
foreach ($value1 as $key2 => $value2) {
$result[$key2][$key1] = $value2;
}
}
return $result;
}
$upload = diverse_array($_FILES["upload"]);
// 結果:$upload[0]['name'], $upload[0]['type']... 更直觀
單靠前端 <input type="hidden" name="MAX_FILE_SIZE"> 可被繞過,必須在多層配置:
max_execution_time = 30 ; 腳本最大執行秒數
max_input_time = 60 ; 接收資料最大秒數
memory_limit = 128M ; 記憶體上限
post_max_size = 8M ; POST 總資料上限(含所有欄位)
upload_max_filesize = 2M ; 單檔案上限
注意:post_max_size 必須 ≥ upload_max_filesize。
location / {
root html;
index index.html index.htm;
client_max_body_size 1000m; # 允許 1GB 請求體
}
掌握以上要點,即可構建安全、健壯、可擴展的 PHP 檔案上傳系統。實務上建議再搭配:檔案掃毒、儲存路徑非 Web 可訪問、資料庫記錄元資料等進階措施。