<form action="upload.php" method="post" enctype="multipart/form-data">
<input type="hidden" name="MAX_FILE_SIZE" value="2000000">
<input type="file" name="files[]" multiple>
<input type="file" name="files[]" multiple>
<input type="file" name="files[]" multiple>
<button type="submit">上傳</button>
</form>
核心要素:
enctype="multipart/form-data":必須,否則 $_FILES 為空name="files[]":方括號讓 PHP 自動組裝為多維數組multiple:HTML5 屬性,允許單個 input 選多文件MAX_FILE_SIZE:客戶端提示上限(可繞過,服務端必驗證)Array
(
[files] => Array
(
[name] => Array
(
[0] => photo1.jpg
[1] => photo2.png
)
[type] => Array
(
[0] => image/jpeg
[1] => image/png
)
[tmp_name] => Array
(
[0] => /tmp/phpA1B2C3
[1] => /tmp/phpD4E5F6
)
[error] => Array
(
[0] => 0
[1] => 0
)
[size] => Array
(
[0] => 102400
[1] => 204800
)
)
)
function restructureFiles(array $files): array
{
$count = count($files['name']);
$restructured = [];
for ($i = 0; $i < $count; $i++) {
$restructured[] = [
'name' => $files['name'][$i],
'type' => $files['type'][$i],
'tmp_name' => $files['tmp_name'][$i],
'error' => $files['error'][$i],
'size' => $files['size'][$i],
];
}
return $restructured;
}
$files = restructureFiles($_FILES['files']);
<?php
$allowTypes = ['image/jpeg', 'image/png', 'image/gif', 'image/webp'];
$maxSize = 2 * 1024 * 1024; // 2MB
$uploadDir = __DIR__ . '/uploads/';
if (!is_dir($uploadDir)) {
mkdir($uploadDir, 0755, true);
}
$files = restructureFiles($_FILES['files']);
$results = [];
foreach ($files as $index => $file) {
// 1. 錯誤碼檢查
if ($file['error'] !== UPLOAD_ERR_OK) {
$results[$index] = ['success' => false, 'msg' => uploadErrorMessage($file['error'])];
continue;
}
// 2. 是否真實上傳文件
if (!is_uploaded_file($file['tmp_name'])) {
$results[$index] = ['success' => false, 'msg' => '非法上傳來源'];
continue;
}
// 3. MIME 類型驗證(防偽裝)
$finfo = finfo_open(FILEINFO_MIME_TYPE);
$realType = finfo_file($finfo, $file['tmp_name']);
finfo_close($finfo);
if (!in_array($realType, $allowTypes, true)) {
$results[$index] = ['success' => false, 'msg' => '文件類型不允許'];
continue;
}
// 4. 圖片二次驗證(防掛馬)
if ($realType !== 'image/gif' && !@getimagesize($file['tmp_name'])) {
$results[$index] = ['success' => false, 'msg' => '非有效圖片文件'];
continue;
}
// 5. 大小限制
if ($file['size'] > $maxSize) {
$results[$index] = ['success' => false, 'msg' => '文件超過大小限制'];
continue;
}
// 6. 生成安全文件名
$ext = pathinfo($file['name'], PATHINFO_EXTENSION);
$safeName = bin2hex(random_bytes(16)) . '.' . strtolower($ext);
$destPath = $uploadDir . $safeName;
// 7. 移動文件
if (move_uploaded_file($file['tmp_name'], $destPath)) {
$results[$index] = [
'success' => true,
'original_name' => $file['name'],
'saved_name' => $safeName,
'path' => $destPath,
'size' => $file['size'],
'type' => $realType
];
} else {
$results[$index] = ['success' => false, 'msg' => '文件保存失敗'];
}
}
// 輸出 JSON 結果
header('Content-Type: application/json');
echo json_encode(['files' => $results], JSON_UNESCAPED_UNICODE);
function uploadErrorMessage(int $code): string
{
return match ($code) {
UPLOAD_ERR_INI_SIZE => '超過 php.ini upload_max_filesize',
UPLOAD_ERR_FORM_SIZE => '超過表單 MAX_FILE_SIZE',
UPLOAD_ERR_PARTIAL => '文件只有部分被上傳',
UPLOAD_ERR_NO_FILE => '沒有文件被上傳',
UPLOAD_ERR_NO_TMP_DIR => '找不到臨時目錄',
UPLOAD_ERR_CANT_WRITE => '文件寫入失敗',
UPLOAD_ERR_EXTENSION => 'PHP 擴展阻斷了上傳',
default => '未知上傳錯誤',
};
}
post_max_size / upload_max_filesize 限制