完整上傳流程圖解
用戶選擇檔案 → 表單提交 (multipart/form-data)
↓
PHP 接收 → $_FILES 陣列暫存資訊
↓
驗證 (類型、大小、錯誤碼)
↓
move_uploaded_file(tmp_name, 目標路徑)
↓
成功/失敗回應
HTML 表單(必要屬性)
<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>
三大關鍵:
method="post":GET 無法傳檔案
enctype="multipart/form-data":編碼類型,必須
type="file":瀏覽器顯示「選擇檔案」按鈕
PHP 處理腳本 (upload_file.php)
<?php
// 簡單版:核心只有這兩行
$targetDir = 'uploads/';
$targetFile = $targetDir . basename($_FILES['file']['name']);
if (move_uploaded_file($_FILES['file']['tmp_name'], $targetFile)) {
echo "上傳成功!檔案儲存於:$targetFile";
} else {
echo "上傳失敗。";
}
生產環境安全版(必看)
<?php
function uploadFile(array $file, string $targetDir = 'uploads/'): array {
// 1. 檢查錯誤碼
if ($file['error'] !== UPLOAD_ERR_OK) {
return ['success' => false, 'error' => '上傳錯誤代碼: ' . $file['error']];
}
// 2. 檢查檔案大小 (2MB)
if ($file['size'] > 2 * 1024 * 1024) {
return ['success' => false, 'error' => '檔案超過 2MB 限制'];
}
// 3. 驗證 MIME 類型 (用 finfo,不可信 $_FILES['type'])
$finfo = new finfo(FILEINFO_MIME_TYPE);
$mime = $finfo->file($file['tmp_name']);
$allowed = ['image/jpeg', 'image/png', 'image/gif', 'application/pdf'];
if (!in_array($mime, $allowed)) {
return ['success' => false, 'error' => "不支援的檔案類型: $mime"];
}
// 4. 驗證副檔名
$ext = strtolower(pathinfo($file['name'], PATHINFO_EXTENSION));
$allowedExt = ['jpg','jpeg','png','gif','pdf'];
if (!in_array($ext, $allowedExt)) {
return ['success' => false, 'error' => "不允許的副檔名: $ext"];
}
// 5. 確保目錄存在且可寫
if (!is_dir($targetDir)) {
mkdir($targetDir, 0755, true);
}
// 6. 生成唯一檔名 (防覆蓋、防猜測、防中文亂碼)
$newName = bin2hex(random_bytes(16)) . '.' . $ext;
$targetPath = $targetDir . $newName;
// 7. 移動檔案
if (!move_uploaded_file($file['tmp_name'], $targetPath)) {
return ['success' => false, 'error' => '檔案移動失敗,請檢查目錄權限'];
}
// 8. 設定權限
chmod($targetPath, 0644);
return [
'success' => true,
'filename' => $newName,
'path' => $targetPath,
'size' => $file['size'],
'mime' => $mime
];
}
// 使用
$result = uploadFile($_FILES['file']);
if ($result['success']) {
echo "上傳成功:" . $result['path'];
} else {
echo "失敗:" . $result['error'];
}
$_FILES 陣列完整結構
| 鍵名 | 說明 | 範例 |
| name | 客戶端原始檔名 | photo.jpg |
| type | 瀏覽器報告 MIME (不可信) | image/jpeg |
| tmp_name | 伺服器暫存路徑 | /tmp/phpA1B2C3 |
| size | 檔案大小 (bytes) | 102400 |
| error | 錯誤代碼 (0=成功) | 0 |
常見錯誤代碼對照
| 常數 | 值 | 原因 | 解決 |
| UPLOAD_ERR_OK | 0 | 成功 | — |
| UPLOAD_ERR_INI_SIZE | 1 | 超過 php.ini upload_max_filesize | 調大 php.ini 或 .user.ini |
| UPLOAD_ERR_FORM_SIZE | 2 | 超過表單 MAX_FILE_SIZE | 移除隱藏欄位或調大 |
| UPLOAD_ERR_PARTIAL | 3 | 僅部分上傳 (網路中斷) | 重試、檢查網路 |
| UPLOAD_ERR_NO_FILE | 4 | 未選擇檔案 | 前端驗證必填 |
| UPLOAD_ERR_NO_TMP_DIR | 6 | 缺少暫存目錄 | php.ini 設定 upload_tmp_dir |
| UPLOAD_ERR_CANT_WRITE | 7 | 寫入磁碟失敗 | 檢查磁碟空間/權限 |
| UPLOAD_ERR_EXTENSION | 8 | PHP 擴展阻止 | 檢查 suhosin 等擴展 |
php.ini 關鍵設定
file_uploads = On
upload_max_filesize = 10M
post_max_size = 12M ; 必須 > upload_max_filesize
max_file_uploads = 20 ; 單次最多檔案數
upload_tmp_dir = /tmp/php_uploads ; 需存在且可寫
max_execution_time = 60
memory_limit = 128M
多檔案上傳
<!-- HTML: name 結尾加 [] -->
<input type="file" name="files[]" multiple />
<?php
// PHP: 遍歷陣列
foreach ($_FILES['files']['tmp_name'] as $index => $tmpName) {
$file = [
'name' => $_FILES['files']['name'][$index],
'type' => $_FILES['files']['type'][$index],
'tmp_name' => $tmpName,
'size' => $_FILES['files']['size'][$index],
'error' => $_FILES['files']['error'][$index],
];
$result = uploadFile($file);
// 處理結果...
}
進階主題導引
- 大檔案分片上傳:前端切片 (Blob.slice),後端合併,支援斷點續傳
- 雲端直傳:後端簽名 Policy,前端直傳 OSS/S3,減輕伺服器
- 圖片處理管線:上傳後自動壓縮、生成縮圖、水印、WebP 轉換
- 病毒掃描:整合 ClamAV 掃描暫存檔
- 進度條:Session 上傳進度 (
session.upload_progress) 或 XMLHttpRequest upload 事件