首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> 網頁程式設計 >> PHP 程式設計 >> PHP 綜合知識

PHP 多檔案上傳完整實作:表單構建、陣列處理、安全驗證

     編輯:PHP 綜合知識

HTML 表單關鍵點

  1. enctype="multipart/form-data" — 必須,否則 $_FILES 為空。
  2. name="files[]" — 陣列命名,PHP 自動整理為結構化陣列。
  3. multiple 屬性 (HTML5) — 允許使用者一次選擇多個檔案。

現代單一輸入多檔案上傳 (推薦)

<form action="upload.php" method="post" enctype="multipart/form-data">
  <input type="file" name="files[]" multiple accept="image/*,.pdf">
  <button type="submit">上傳</button>
</form>

PHP 處理邏輯 (標準化 $_FILES 結構)

<?php
// upload.php
const UPLOAD_DIR = __DIR__ . '/uploads/';
const MAX_FILE_SIZE = 5 * 1024 * 1024; // 5MB
const ALLOWED_TYPES = ['image/jpeg', 'image/png', 'image/gif', 'application/pdf'];
const ALLOWED_EXTS = ['jpg', 'jpeg', 'png', 'gif', 'pdf'];

// 確保目錄存在
if (!is_dir(UPLOAD_DIR)) mkdir(UPLOAD_DIR, 0755, true);

$errors = [];
$uploaded = [];

// 標準化 $_FILES 結構 (處理單檔/多檔、多輸入欄位)
$files = [];
foreach ($_FILES as $fieldName => $fileData) {
    if (isset($fileData['name'][0])) {
        // 多檔案:name="files[]"
        $count = count($fileData['name']);
        for ($i = 0; $i < $count; $i++) {
            $files[] = [
                'name' => $fileData['name'][$i],
                'type' => $fileData['type'][$i],
                'tmp_name' => $fileData['tmp_name'][$i],
                'error' => $fileData['error'][$i],
                'size' => $fileData['size'][$i],
                'field' => $fieldName,
            ];
        }
    } else {
        // 單檔案:name="file"
        $files[] = $fileData + ['field' => $fieldName];
    }
}

foreach ($files as $file) {
    // 1. 錯誤碼檢查
    if ($file['error'] !== UPLOAD_ERR_OK) {
        $errors[] = "{$file['name']}: " . upload_error_message($file['error']);
        continue;
    }

    // 2. 檔案大小
    if ($file['size'] > MAX_FILE_SIZE) {
        $errors[] = "{$file['name']}: 檔案過大 (限制 " . (MAX_FILE_SIZE/1024/1024) . "MB)";
        continue;
    }

    // 3. MIME 型別驗證 (建議搭配 finfo 實際檢測)
    $finfo = new finfo(FILEINFO_MIME_TYPE);
    $mime = $finfo->file($file['tmp_name']);
    if (!in_array($mime, ALLOWED_TYPES, true)) {
        $errors[] = "{$file['name']}: 檔案類型不允許 ($mime)";
        continue;
    }

    // 4. 副檔名驗證 (雙重保險)
    $ext = strtolower(pathinfo($file['name'], PATHINFO_EXTENSION));
    if (!in_array($ext, ALLOWED_EXTS, true)) {
        $errors[] = "{$file['name']}: 副檔名不允許 ($ext)";
        continue;
    }

    // 5. 產生安全檔名 (避免覆蓋、路徑遍歷、特殊字元)
    $basename = pathinfo($file['name'], PATHINFO_FILENAME);
    $safeName = preg_replace('/[^a-zA-Z0-9_\-]/', '', $basename);
    $safeName = substr($safeName, 0, 50); // 限制長度
    $newName = $safeName . '_' . bin2hex(random_bytes(8)) . '.' . $ext;
    $dest = UPLOAD_DIR . $newName;

    // 6. 移動檔案 (原子操作)
    if (!move_uploaded_file($file['tmp_name'], $dest)) {
        $errors[] = "{$file['name']}: 儲存失敗";
        continue;
    }

    // 7. 可選:圖片二次驗證 (getimagesize) 、病毒掃描、縮圖生成
    if (str_starts_with($mime, 'image/')) {
        $imgInfo = @getimagesize($dest);
        if (!$imgInfo) {
            unlink($dest);
            $errors[] = "{$file['name']}: 非有效圖片檔案";
            continue;
        }
    }

    $uploaded[] = [
        'original' => $file['name'],
        'stored' => $newName,
        'path' => $dest,
        'size' => $file['size'],
        'mime' => $mime,
        'url' => '/uploads/' . $newName, // 前端訪問路徑
    ];
}

// 回應
header('Content-Type: application/json');
echo json_encode([
    'success' => empty($errors),
    'uploaded' => $uploaded,
    'errors' => $errors,
], JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT);

function upload_error_message(int $code): string {
    return match ($code) {
        UPLOAD_ERR_INI_SIZE, UPLOAD_ERR_FORM_SIZE => '超過大小限制',
        UPLOAD_ERR_PARTIAL => '上傳不完整',
        UPLOAD_ERR_NO_FILE => '未選擇檔案',
        UPLOAD_ERR_NO_TMP_DIR => '伺服器臨時目錄缺失',
        UPLOAD_ERR_CANT_WRITE => '寫入失敗',
        UPLOAD_ERR_EXTENSION => '擴充模組中斷上傳',
        default => '未知錯誤',
    };
}
?>

前端 AJAX 體驗增強

<script>
document.querySelector('form').addEventListener('submit', async (e) => {
    e.preventDefault();
    const formData = new FormData(e.target);
    const res = await fetch('upload.php', { method: 'POST', body: formData });
    const data = await res.json();
    if (data.success) {
        alert(`上傳成功 ${data.uploaded.length} 個檔案`);
        data.uploaded.forEach(f => {
            // 顯示縮圖/連結
            const div = document.createElement('div');
            div.innerHTML = `${f.original} (${(f.size/1024).toFixed(1)} KB)`;
            document.body.appendChild(div);
        });
    } else {
        alert('上傳失敗:\n' + data.errors.join('\n'));
    }
});
</script>

進階:分塊上傳 (大檔案/斷點續傳)

對於 >100MB 檔案,需前端切片 (Blob.slice)、後端合併:

  • 前端:Uppy、Dropzone、FilePond、Resumable.js。
  • 後端:接收分塊、臨時儲存、合併驗證 (MD5/SHA256)。
  • 推薦庫:league/flysystem + 自定義分塊合併邏輯,或使用 tus-php 實作 tus.io 標準。

安全性核心清單

層面措施
檔案型別finfo 偵測真實 MIME + 副檔名白名單
檔案大小PHP upload_max_filesize/post_max_size + 程式碼二次檢查
檔名安全隨機重命名、移除特殊字元、禁止路徑遍歷 (../)
儲存位置非 Web 可直接執行目錄、或 Nginx location /uploads { internal; }
執行權限上傳目錄禁用腳本執行 (PHP、CGI 等)
病毒掃描ClamAV 整合 (clamav 擴充或 clamscan 呼叫)
CSRF表單 Token / SameSite Cookie
速率限制IP/用戶上傳頻率與總量限制

常見陷阱與除錯

  • $_FILES 為空 → 檢查 enctype、post_max_size、upload_max_filesize、表單 method="POST"。
  • 大檔案上傳失敗 → 調整 max_execution_time、memory_limit、Nginx client_max_body_size。
  • 檔名亂碼 → 瀏覽器編碼不一,建議強制 UTF-8、重命名存儲。
  • 權限錯誤 → 上傳目錄擁有者為 PHP-FPM 用戶 (www-data)、權限 0755。

結語

PHP 多檔案上傳的核心在於正確構建 $_FILES 陣列結構、嚴格驗證 (MIME/大小/副檔名)、安全重命名與儲存。現代開發建議採用 單一 multiple 輸入欄位 + AJAX 非同步上傳 + JSON 回應,前端體驗佳、後端邏輯簡潔。大檔案需求請採用分塊上傳庫 (Uppy/tus-php)。安全性方面,**MIME 偵測 + 非執行目錄 + 病毒掃描** 三層防護為基礎標準。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved