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

PHP + jQuery 多檔案上傳完整實作 (AJAX 非同步 + 進度條)

     編輯:PHP 相關知識

傳統 <input type="file" multiple> 配合 FormData + AJAX 即可實現現代化多檔上傳。本文提供前後端完整代碼:前端採用 jQuery + 原生 FormData (不需外掛),後端 PHP 處理 $_FILES 陣列、驗證類型大小、回傳 JSON 結果。

前端 HTML + JavaScript

<div id="uploader">
    <input type="file" id="fileInput" name="myfile[]" multiple accept="image/*,.pdf,.doc,.zip" />
    <button id="btnUpload" disabled>開始上傳</button>
    <div id="fileList"></div>
    <div id="progressBar" style="display:none;">
        <div id="progressFill" style="width:0%; height:20px; background:#4CAF50;"></div>
    </div>
    <div id="status"></div>
</div>

<script src="https://code.jquery.com/jquery-3.6.0.min.js"></script>
<script>
$(function() {
    let files = [];

    // 選檔事件
    $('#fileInput').on('change', function(e) {
        files = Array.from(e.target.files);
        renderFileList();
        $('#btnUpload').prop('disabled', files.length === 0);
    });

    // 渲染待上傳清單
    function renderFileList() {
        let html = '<ul>';
        files.forEach((f, i) => {
            html += `<li data-index="${i}">${f.name} (${(f.size/1024).toFixed(1)} KB) <span class="state">待上傳</span></li>`;
        });
        html += '</ul>';
        $('#fileList').html(html);
    }

    // 上傳按鈕
    $('#btnUpload').on('click', async function() {
        if (files.length === 0) return;
        $(this).prop('disabled', true);
        $('#progressBar').show();
        $('#status').html('');

        const formData = new FormData();
        files.forEach(f => formData.append('myfile[]', f));

        try {
            const xhr = new XMLHttpRequest();
            xhr.open('POST', 'upload.php', true);

            // 進度條
            xhr.upload.onprogress = function(e) {
                if (e.lengthComputable) {
                    const pct = Math.round((e.loaded / e.total) * 100);
                    $('#progressFill').css('width', pct + '%');
                }
            };

            xhr.onload = function() {
                if (xhr.status === 200) {
                    const res = JSON.parse(xhr.responseText);
                    handleResult(res);
                } else {
                    $('#status').html('<span style="color:red">伺服器錯誤: ' + xhr.status + '</span>');
                }
            };
            xhr.onerror = () => $('#status').html('<span style="color:red">網路錯誤</span>');
            xhr.send(formData);
        } catch (err) {
            $('#status').html('<span style="color:red">' + err.message + '</span>');
        }
    });

    function handleResult(res) {
        if (res.success) {
            $('#status').html('<span style="color:green">全部上傳成功</span>');
            $('#fileList li .state').text('成功').css('color', 'green');
            $('#fileInput').val(''); files = []; $('#btnUpload').prop('disabled', true);
        } else {
            $('#status').html('<span style="color:red">' + (res.message || '上傳失敗') + '</span>');
            if (res.errors) {
                res.errors.forEach(err => {
                    $(`#fileList li[data-index="${err.index}"] .state`).text('失敗: ' + err.msg).css('color', 'red');
                });
            }
        }
    }
});
</script>

後端 PHP (upload.php)

<?php
header('Content-Type: application/json; charset=utf-8');

$uploadDir = __DIR__ . '/uploads/';
if (!is_dir($uploadDir)) mkdir($uploadDir, 0755, true);

$allowedTypes = ['image/jpeg','image/png','image/gif','application/pdf','application/zip','application/msword'];
$maxSize = 5 * 1024 * 1024; // 5MB
$maxFiles = 20;

$response = ['success' => false, 'message' => '', 'errors' => [], 'files' => []];

// 檢查是否有檔案
if (empty($_FILES['myfile']['name'][0])) {
    $response['message'] = '未收到檔案';
    echo json_encode($response);
    exit;
}

$fileCount = count($_FILES['myfile']['name']);
if ($fileCount > $maxFiles) {
    $response['message'] = "超過最大檔案數限制 ($maxFiles)";
    echo json_encode($response);
    exit;
}

$allOk = true;
for ($i = 0; $i < $fileCount; $i++) {
    $err = null;
    $name = $_FILES['myfile']['name'][$i];
    $tmp = $_FILES['myfile']['tmp_name'][$i];
    $size = $_FILES['myfile']['size'][$i];
    $type = $_FILES['myfile']['type'][$i];
    $error = $_FILES['myfile']['error'][$i];

    // 驗證
    if ($error !== UPLOAD_ERR_OK) {
        $err = '上傳錯誤代碼: ' . $error;
    } elseif ($size > $maxSize) {
        $err = '檔案過大 (限制 ' . ($maxSize/1024/1024) . 'MB)';
    } elseif (!in_array($type, $allowedTypes)) {
        $err = '檔案類型不允許: ' . $type;
    } else {
        // 檔名安全化
        $ext = strtolower(pathinfo($name, PATHINFO_EXTENSION));
        $safeName = bin2hex(random_bytes(8)) . '.' . $ext;
        $dest = $uploadDir . $safeName;

        if (move_uploaded_file($tmp, $dest)) {
            $response['files'][] = ['original' => $name, 'saved' => $safeName, 'size' => $size, 'type' => $type];
        } else {
            $err = '伺服器儲存失敗';
        }
    }

    if ($err) {
        $allOk = false;
        $response['errors'][] = ['index' => $i, 'name' => $name, 'msg' => $err];
    }
}

$response['success'] = $allOk;
$response['message'] = $allOk ? '所有檔案上傳成功' : '部分檔案上傳失敗';
echo json_encode($response);

關鍵安全強化

  1. 檔名亂碼化:bin2hex(random_bytes(8)) 產生隨機檔名,防止路徑遍歷、覆蓋、特殊字元攻擊。
  2. MIME 類型白名單:僅信任 $_FILES['type'] 不夠,建議搭配 finfo_file() 偵測真實 MIME。
  3. 儲存目錄隔離:上傳目錄設定 php_flag engine off (Apache) 或 location ~ \.php$ { deny all; } (Nginx) 禁止腳本執行。
  4. 大小限制雙重檢查:PHP upload_max_filesize / post_max_size + 程式碼邏輯 $maxSize。
  5. CSRF 保護:AJAX 請求攜帶 Token 驗證 (Laravel 等框架內建)。

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

單檔 > 100MB 建議採用 Plupload、Uppy 或 WebUploader 等前端庫,配合後端分塊合併邏輯:

  • 前端將檔案切片 (5MB/片),並行上傳,附帶 chunk_index, total_chunks, file_hash。
  • 後端儲存切片至暫存目錄,全部到齊後合併 cat chunk_* > final_file 並驗證雜湊。
  • 支援暫停/續傳:前端記錄已上傳切片,重新上傳時跳過。
  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved